添加项目文件。
parent
542d55a56d
commit
39e2692417
@ -0,0 +1,16 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
using Hcrm.IService;
|
||||||
|
|
||||||
|
namespace Hcrm.Api.Auth;
|
||||||
|
|
||||||
|
public class CurrentUserAccessor : ICurrentUserAccessor
|
||||||
|
{
|
||||||
|
private readonly ILoginTokenService _loginToken;
|
||||||
|
|
||||||
|
public CurrentUserAccessor(ILoginTokenService loginToken)
|
||||||
|
{
|
||||||
|
_loginToken = loginToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountModel? CurrentUser => _loginToken.GetCurrentUser();
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
using Hcrm.IService;
|
||||||
|
using Hcrm.Model;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace Hcrm.Api.Auth;
|
||||||
|
|
||||||
|
public class LoginTokenService : ILoginTokenService
|
||||||
|
{
|
||||||
|
private const string CaptchaKey = "session_Code";
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
|
||||||
|
public LoginTokenService(IHttpContextAccessor httpContextAccessor, IMemoryCache cache)
|
||||||
|
{
|
||||||
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCookie(AccountModel account)
|
||||||
|
{
|
||||||
|
var cookieValue = Md5Helper.Md5String(
|
||||||
|
Md5Helper.Md5String(account.tb_Employee_ID.Trim()) + Guid.NewGuid());
|
||||||
|
|
||||||
|
var ctx = _httpContextAccessor.HttpContext
|
||||||
|
?? throw new InvalidOperationException("No HttpContext");
|
||||||
|
|
||||||
|
ctx.Response.Cookies.Append(AuthConstants.CookieToken, cookieValue, new CookieOptions
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
IsEssential = true,
|
||||||
|
SameSite = SameSiteMode.Lax
|
||||||
|
});
|
||||||
|
|
||||||
|
_cache.Set(cookieValue, account, TimeSpan.FromMinutes(AuthConstants.SessionMinutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Logout()
|
||||||
|
{
|
||||||
|
var ctx = _httpContextAccessor.HttpContext;
|
||||||
|
if (ctx == null) return;
|
||||||
|
|
||||||
|
if (ctx.Request.Cookies.TryGetValue(AuthConstants.CookieToken, out var cookieValue)
|
||||||
|
&& !string.IsNullOrEmpty(cookieValue))
|
||||||
|
{
|
||||||
|
_cache.Remove(cookieValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Response.Cookies.Delete(AuthConstants.CookieToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountModel? GetCurrentUser()
|
||||||
|
{
|
||||||
|
var ctx = _httpContextAccessor.HttpContext;
|
||||||
|
if (ctx == null) return null;
|
||||||
|
|
||||||
|
if (!ctx.Request.Cookies.TryGetValue(AuthConstants.CookieToken, out var cookieValue)
|
||||||
|
|| string.IsNullOrWhiteSpace(cookieValue))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cache.TryGetValue(cookieValue, out AccountModel? account) ? account : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCaptcha(string code) =>
|
||||||
|
_httpContextAccessor.HttpContext?.Session.SetString(CaptchaKey, code);
|
||||||
|
|
||||||
|
public string? GetCaptcha() =>
|
||||||
|
_httpContextAccessor.HttpContext?.Session.GetString(CaptchaKey);
|
||||||
|
|
||||||
|
public void ClearCaptcha() =>
|
||||||
|
_httpContextAccessor.HttpContext?.Session.Remove(CaptchaKey);
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using Hcrm.Api.Auth;
|
||||||
|
using Hcrm.IService;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Hcrm.Api;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddHcrmApi(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<ILoginTokenService, LoginTokenService>();
|
||||||
|
services.AddScoped<ICurrentUserAccessor, CurrentUserAccessor>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
using Hcrm.IService;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
namespace Hcrm.Api.Filters;
|
||||||
|
|
||||||
|
public class LoginRequiredFilter : IAsyncActionFilter
|
||||||
|
{
|
||||||
|
private readonly ILoginTokenService _loginToken;
|
||||||
|
|
||||||
|
public LoginRequiredFilter(ILoginTokenService loginToken)
|
||||||
|
{
|
||||||
|
_loginToken = loginToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||||
|
{
|
||||||
|
var endpoint = context.HttpContext.GetEndpoint();
|
||||||
|
if (endpoint?.Metadata.GetMetadata<NoLoginRequiredAttribute>() != null)
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_loginToken.GetCurrentUser() == null)
|
||||||
|
{
|
||||||
|
context.Result = new JsonResult(ApiResult.NotLoggedIn())
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status200OK
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
namespace Hcrm.Api.Filters;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
|
public sealed class NoLoginRequiredAttribute : Attribute;
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
using Hcrm.Api;
|
||||||
|
using Hcrm.Api.Filters;
|
||||||
|
using Hcrm.Repository;
|
||||||
|
using Hcrm.Service;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddHcrmRepository(builder.Configuration);
|
||||||
|
builder.Services.AddHcrmService();
|
||||||
|
builder.Services.AddHcrmApi();
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
builder.Services.AddDistributedMemoryCache();
|
||||||
|
builder.Services.AddSession(options =>
|
||||||
|
{
|
||||||
|
options.Cookie.Name = ".Hcrm.Session";
|
||||||
|
options.IdleTimeout = TimeSpan.FromMinutes(30);
|
||||||
|
});
|
||||||
|
builder.Services.AddControllers(options => options.Filters.Add<LoginRequiredFilter>())
|
||||||
|
.AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||||
|
options.JsonSerializerOptions.DictionaryKeyPolicy = null;
|
||||||
|
});
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
builder.Services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddDefaultPolicy(policy =>
|
||||||
|
policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().SetIsOriginAllowed(_ => true));
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseCors();
|
||||||
|
app.UseSession();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
app.UseRouting();
|
||||||
|
app.MapControllers();
|
||||||
|
app.MapGet("/", () => Results.Redirect("/login/index.html"));
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.Formats.Gif;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
|
namespace Hcrm.Api.Services;
|
||||||
|
|
||||||
|
public static class CaptchaImageService
|
||||||
|
{
|
||||||
|
private static readonly Font Font = SystemFonts.CreateFont("Arial", 16, FontStyle.Bold);
|
||||||
|
|
||||||
|
public static string MakeCode(int length = 4)
|
||||||
|
{
|
||||||
|
const string chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||||
|
var random = Random.Shared;
|
||||||
|
return new string(Enumerable.Range(0, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToBase64Gif(string code)
|
||||||
|
{
|
||||||
|
using var image = new Image<Rgba32>(Math.Max(80, code.Length * 18), 32);
|
||||||
|
image.Mutate(ctx =>
|
||||||
|
{
|
||||||
|
ctx.Fill(Color.White);
|
||||||
|
var random = Random.Shared;
|
||||||
|
for (var i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
ctx.DrawLine(Color.Silver, 1f,
|
||||||
|
new PointF(random.Next(image.Width), random.Next(image.Height)),
|
||||||
|
new PointF(random.Next(image.Width), random.Next(image.Height)));
|
||||||
|
}
|
||||||
|
ctx.DrawText(code, Font, Color.DarkRed, new PointF(8, 6));
|
||||||
|
});
|
||||||
|
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
image.Save(ms, new GifEncoder());
|
||||||
|
return Convert.ToBase64String(ms.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DBContainer": "Server=49.234.67.43;Database=KHCRM_SLYY2022_Dev;User Id=sa;Password=kc!@#2020!;MultipleActiveResultSets=True;TrustServerCertificate=True;Connect Timeout=30"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>HCRM 登录</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
min-height: 100vh; display: flex; align-items: center; justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #1a6fb5 0%, #0d3d6b 100%);
|
||||||
|
font-family: "Microsoft YaHei", sans-serif;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
width: 380px; background: #fff; border-radius: 8px;
|
||||||
|
padding: 36px 32px; box-shadow: 0 8px 32px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
h1 { font-size: 22px; color: #1a6fb5; text-align: center; margin-bottom: 28px; }
|
||||||
|
.field { margin-bottom: 16px; }
|
||||||
|
label { display: block; font-size: 13px; color: #555; margin-bottom: 6px; }
|
||||||
|
input {
|
||||||
|
width: 100%; height: 38px; border: 1px solid #ddd; border-radius: 4px;
|
||||||
|
padding: 0 10px; font-size: 14px; outline: none;
|
||||||
|
}
|
||||||
|
input:focus { border-color: #1a6fb5; }
|
||||||
|
.captcha-row { display: flex; gap: 8px; align-items: center; }
|
||||||
|
.captcha-row input { flex: 1; }
|
||||||
|
.captcha-img {
|
||||||
|
width: 100px; height: 38px; cursor: pointer; border: 1px solid #ddd;
|
||||||
|
border-radius: 4px; background: #f5f5f5;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%; height: 40px; margin-top: 8px; border: none; border-radius: 4px;
|
||||||
|
background: #1a6fb5; color: #fff; font-size: 15px; cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover { background: #155d9a; }
|
||||||
|
button:disabled { background: #9bbad4; cursor: not-allowed; }
|
||||||
|
.msg { margin-top: 12px; font-size: 13px; text-align: center; min-height: 18px; }
|
||||||
|
.msg.error { color: #e53935; }
|
||||||
|
.msg.ok { color: #43a047; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>康策 HCRM</h1>
|
||||||
|
<div class="field">
|
||||||
|
<label>用户名</label>
|
||||||
|
<input id="userName" type="text" placeholder="工号或姓名" autocomplete="username" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>密码</label>
|
||||||
|
<input id="password" type="password" placeholder="密码" autocomplete="current-password" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>验证码</label>
|
||||||
|
<div class="captcha-row">
|
||||||
|
<input id="code" type="text" placeholder="验证码" maxlength="6" />
|
||||||
|
<img id="captchaImg" class="captcha-img" title="点击刷新" alt="验证码" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="btnLogin" type="button">登 录</button>
|
||||||
|
<p id="msg" class="msg"></p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
async function loadCaptcha() {
|
||||||
|
const res = await fetch('/Account/CheckCodeImage', { credentials: 'include' });
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.status === 1 && json.data) {
|
||||||
|
$('captchaImg').src = 'data:image/gif;base64,' + json.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function login() {
|
||||||
|
$('btnLogin').disabled = true;
|
||||||
|
$('msg').className = 'msg';
|
||||||
|
$('msg').textContent = '登录中...';
|
||||||
|
const body = new URLSearchParams({
|
||||||
|
UserName: $('userName').value.trim(),
|
||||||
|
Password: $('password').value,
|
||||||
|
code: $('code').value.trim()
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await fetch('/Account/Login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body,
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.status === 1) {
|
||||||
|
$('msg').className = 'msg ok';
|
||||||
|
$('msg').textContent = '登录成功,正在进入店铺装修...';
|
||||||
|
setTimeout(() => { location.href = '/shop/index.html'; }, 400);
|
||||||
|
} else {
|
||||||
|
$('msg').className = 'msg error';
|
||||||
|
$('msg').textContent = json.message || '登录失败';
|
||||||
|
loadCaptcha();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$('msg').className = 'msg error';
|
||||||
|
$('msg').textContent = '网络错误';
|
||||||
|
} finally {
|
||||||
|
$('btnLogin').disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('captchaImg').onclick = loadCaptcha;
|
||||||
|
$('btnLogin').onclick = login;
|
||||||
|
$('password').onkeydown = e => { if (e.key === 'Enter') login(); };
|
||||||
|
loadCaptcha();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
namespace Hcrm.DTO;
|
||||||
|
|
||||||
|
public class AccountModel
|
||||||
|
{
|
||||||
|
public string tb_Employee_ID { get; set; } = string.Empty;
|
||||||
|
public string EmployeeID { get; set; } = string.Empty;
|
||||||
|
public string TrueName { get; set; } = string.Empty;
|
||||||
|
public string? Photo { get; set; }
|
||||||
|
public string? HospitalID { get; set; }
|
||||||
|
public string? HospitalName { get; set; }
|
||||||
|
public string? DepartmentID { get; set; }
|
||||||
|
public string[]? DepartmentIDList { get; set; }
|
||||||
|
public string? RoleID { get; set; }
|
||||||
|
public string? RoleCode { get; set; }
|
||||||
|
public string? ConsultGroupID { get; set; }
|
||||||
|
public string? WardAreaID { get; set; }
|
||||||
|
public string? MedicalUnit { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using Hcrm.Model;
|
||||||
|
|
||||||
|
namespace Hcrm.DTO;
|
||||||
|
|
||||||
|
public class ApiResult
|
||||||
|
{
|
||||||
|
public int status { get; set; }
|
||||||
|
public string? message { get; set; }
|
||||||
|
public object? data { get; set; }
|
||||||
|
|
||||||
|
public static ApiResult Success(object? data = null, string? message = null) =>
|
||||||
|
new() { status = AuthConstants.SuccessStatus, message = message, data = data };
|
||||||
|
|
||||||
|
public static ApiResult Fail(string message) =>
|
||||||
|
new() { status = AuthConstants.FailStatus, message = message, data = null };
|
||||||
|
|
||||||
|
public static ApiResult NotLoggedIn(string message = "未登录或登录已过期") =>
|
||||||
|
new() { status = AuthConstants.NotLoggedInCode, message = message, data = null };
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
namespace Hcrm.DTO;
|
||||||
|
|
||||||
|
public class EmployeeDto
|
||||||
|
{
|
||||||
|
public string tb_Employee_ID { get; set; } = string.Empty;
|
||||||
|
public string EmployeeID { get; set; } = string.Empty;
|
||||||
|
public string EmployeeName { get; set; } = string.Empty;
|
||||||
|
public int? Sex { get; set; }
|
||||||
|
public string? Photo { get; set; }
|
||||||
|
public string? Mobile { get; set; }
|
||||||
|
public string? Telephone { get; set; }
|
||||||
|
public string? HospitalID { get; set; }
|
||||||
|
public string? HospitalName { get; set; }
|
||||||
|
public string? DepartmentID { get; set; }
|
||||||
|
public string? ConsultGroupID { get; set; }
|
||||||
|
public SysRoleDto? Role { get; set; }
|
||||||
|
public int? LookMobile { get; set; }
|
||||||
|
public int? LookCredentialCode { get; set; }
|
||||||
|
public bool LookName { get; set; } = true;
|
||||||
|
public bool IsDoctor { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SysRoleDto
|
||||||
|
{
|
||||||
|
public string tb_SysRole_ID { get; set; } = string.Empty;
|
||||||
|
public string? RCode { get; set; }
|
||||||
|
public string? RoleName { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using Hcrm.Model;
|
||||||
|
|
||||||
|
namespace Hcrm.IRepository;
|
||||||
|
|
||||||
|
public interface IAccountRepository
|
||||||
|
{
|
||||||
|
Task<int?> GetLoginPromptAsync(CancellationToken ct = default);
|
||||||
|
Task<bool> EmployeeExistsAsync(string userName, CancellationToken ct = default);
|
||||||
|
Task<TbEmployee?> GetEmployeeByCredentialsAsync(string userName, string hashedPassword, CancellationToken ct = default);
|
||||||
|
Task<TbEmployee?> GetEmployeeByIdAsync(string employeeId, CancellationToken ct = default);
|
||||||
|
Task<TbEmployee?> GetEmployeeByIdOrCodeAsync(string employeeId, CancellationToken ct = default);
|
||||||
|
Task<bool> UpdateEmployeePasswordAsync(string employeeId, string hashedPassword, CancellationToken ct = default);
|
||||||
|
Task<string[]> GetDepartmentIdsAsync(string employeeId, CancellationToken ct = default);
|
||||||
|
Task<TbSysRole?> GetRoleByIdAsync(string roleId, CancellationToken ct = default);
|
||||||
|
Task<string?> GetHospitalNameAsync(string hospitalId, CancellationToken ct = default);
|
||||||
|
Task<TbDataRight?> GetDataRightByEmployeeIdAsync(string employeeId, CancellationToken ct = default);
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
|
||||||
|
namespace Hcrm.IService;
|
||||||
|
|
||||||
|
public interface IAccountService
|
||||||
|
{
|
||||||
|
Task<ApiResult> LoginAsync(string userName, string password, string? code, bool requireCaptcha, CancellationToken ct = default);
|
||||||
|
Task<ApiResult> IsLoginAsync(CancellationToken ct = default);
|
||||||
|
Task<ApiResult> LogoutAsync();
|
||||||
|
Task<ApiResult> ModifyPwdAsync(string employeeId, string password, string newPassword, CancellationToken ct = default);
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
|
||||||
|
namespace Hcrm.IService;
|
||||||
|
|
||||||
|
public interface ICurrentUserAccessor
|
||||||
|
{
|
||||||
|
AccountModel? CurrentUser { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
|
||||||
|
namespace Hcrm.IService;
|
||||||
|
|
||||||
|
public interface ILoginTokenService
|
||||||
|
{
|
||||||
|
void SetCookie(AccountModel account);
|
||||||
|
void Logout();
|
||||||
|
AccountModel? GetCurrentUser();
|
||||||
|
void SetCaptcha(string code);
|
||||||
|
string? GetCaptcha();
|
||||||
|
void ClearCaptcha();
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public static class AuthConstants
|
||||||
|
{
|
||||||
|
public const string CookieToken = "kc_cookie_token";
|
||||||
|
public const int SessionMinutes = 1440;
|
||||||
|
public const int NotLoggedInCode = 401;
|
||||||
|
public const int SuccessStatus = 1;
|
||||||
|
public const int FailStatus = 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public static class Md5Helper
|
||||||
|
{
|
||||||
|
public static string Md5String(string input)
|
||||||
|
{
|
||||||
|
if (input == null) return string.Empty;
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(input);
|
||||||
|
var hash = MD5.HashData(bytes);
|
||||||
|
var sb = new StringBuilder(hash.Length * 2);
|
||||||
|
foreach (var b in hash) sb.Append(b.ToString("x2"));
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbDataRight
|
||||||
|
{
|
||||||
|
public string tb_DataRight_ID { get; set; } = string.Empty;
|
||||||
|
public string? EmployeeID { get; set; }
|
||||||
|
public int? LookMobile { get; set; }
|
||||||
|
public int? LookCredentialCode { get; set; }
|
||||||
|
public bool LookName { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbDepartment
|
||||||
|
{
|
||||||
|
public string tb_Department_ID { get; set; } = string.Empty;
|
||||||
|
public string? DepartName { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbEmployee
|
||||||
|
{
|
||||||
|
public string tb_Employee_ID { get; set; } = string.Empty;
|
||||||
|
public string? EmployeeID { get; set; }
|
||||||
|
public string? EmployeeName { get; set; }
|
||||||
|
public string? Password { get; set; }
|
||||||
|
public int? Sex { get; set; }
|
||||||
|
public string? HeadURL { get; set; }
|
||||||
|
public string? Mobile { get; set; }
|
||||||
|
public string? Telephone { get; set; }
|
||||||
|
public string? RoleID { get; set; }
|
||||||
|
public int? OnJob { get; set; }
|
||||||
|
public string? DepartmentID { get; set; }
|
||||||
|
public string? HospitalID { get; set; }
|
||||||
|
public string? ConsultGroupID { get; set; }
|
||||||
|
public string? WardAreaID { get; set; }
|
||||||
|
public string? MedicalUnit { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbEmployeeDepartMapping
|
||||||
|
{
|
||||||
|
public string tb_EmployeeDepartMapping_ID { get; set; } = string.Empty;
|
||||||
|
public string? tb_Employee_ID { get; set; }
|
||||||
|
public string? DepartmentID { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbHospital
|
||||||
|
{
|
||||||
|
public string tb_Hospital_ID { get; set; } = string.Empty;
|
||||||
|
public string? HospitalName { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbSysConfig
|
||||||
|
{
|
||||||
|
public string tb_SysConfig_ID { get; set; } = string.Empty;
|
||||||
|
public int? LoginPrompt { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
namespace Hcrm.Model;
|
||||||
|
|
||||||
|
public class TbSysRole
|
||||||
|
{
|
||||||
|
public string tb_SysRole_ID { get; set; } = string.Empty;
|
||||||
|
public string? RCode { get; set; }
|
||||||
|
public string? RoleName { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using Hcrm.IRepository;
|
||||||
|
using Hcrm.Repository.Repositories;
|
||||||
|
using Hcrm.Repository.SqlSugar;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Hcrm.Repository;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddHcrmRepository(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddScoped<ISqlSugarClient>(_ => SqlSugarClientFactory.CreateClient(configuration));
|
||||||
|
services.AddScoped<IAccountRepository, AccountRepository>();
|
||||||
|
services.AddScoped<IShopRenovateRepository, ShopRenovateRepository>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
using Hcrm.Model;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Hcrm.Repository.Persistence;
|
||||||
|
|
||||||
|
internal static class SugarQueries
|
||||||
|
{
|
||||||
|
public static ISugarQueryable<TbEmployee> Employees(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbEmployee>().AS("tb_Employee");
|
||||||
|
|
||||||
|
public static ISugarQueryable<TbSysConfig> SysConfigs(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbSysConfig>().AS("tb_SysConfig");
|
||||||
|
|
||||||
|
public static ISugarQueryable<TbSysRole> SysRoles(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbSysRole>().AS("tb_SysRole");
|
||||||
|
|
||||||
|
public static ISugarQueryable<TbHospital> Hospitals(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbHospital>().AS("tb_Hospital");
|
||||||
|
|
||||||
|
public static ISugarQueryable<TbDataRight> DataRights(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbDataRight>().AS("tb_DataRight");
|
||||||
|
|
||||||
|
public static ISugarQueryable<TbEmployeeDepartMapping> EmployeeDepartMappings(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbEmployeeDepartMapping>().AS("tb_EmployeeDepartMapping");
|
||||||
|
|
||||||
|
public static IUpdateable<TbEmployee> UpdateEmployees(this ISqlSugarClient db) =>
|
||||||
|
db.Updateable<TbEmployee>().AS("tb_Employee");
|
||||||
|
|
||||||
|
/// <summary>店铺装修页面表 tb_ShopRenovatePage。</summary>
|
||||||
|
public static ISugarQueryable<TbShopRenovatePage> ShopRenovatePages(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbShopRenovatePage>().AS("tb_ShopRenovatePage");
|
||||||
|
|
||||||
|
/// <summary>店铺全局风格表 tb_ShopRenovateStyle。</summary>
|
||||||
|
public static ISugarQueryable<TbShopRenovateStyle> ShopRenovateStyles(this ISqlSugarClient db) =>
|
||||||
|
db.Queryable<TbShopRenovateStyle>().AS("tb_ShopRenovateStyle");
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
using Hcrm.IRepository;
|
||||||
|
using Hcrm.Model;
|
||||||
|
using Hcrm.Repository.Persistence;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Hcrm.Repository.Repositories;
|
||||||
|
|
||||||
|
public class AccountRepository : IAccountRepository
|
||||||
|
{
|
||||||
|
private readonly ISqlSugarClient _db;
|
||||||
|
|
||||||
|
public AccountRepository(ISqlSugarClient db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int?> GetLoginPromptAsync(CancellationToken ct = default) =>
|
||||||
|
_db.SysConfigs().Select(x => x.LoginPrompt).FirstAsync(ct);
|
||||||
|
|
||||||
|
public Task<bool> EmployeeExistsAsync(string userName, CancellationToken ct = default) =>
|
||||||
|
_db.Employees().Where(e => e.EmployeeID == userName || e.EmployeeName == userName).AnyAsync();
|
||||||
|
|
||||||
|
public Task<TbEmployee?> GetEmployeeByCredentialsAsync(string userName, string hashedPassword, CancellationToken ct = default) =>
|
||||||
|
FirstOrDefaultAsync(
|
||||||
|
_db.Employees().Where(e => (e.EmployeeID == userName || e.EmployeeName == userName) && e.Password == hashedPassword),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
public Task<TbEmployee?> GetEmployeeByIdAsync(string employeeId, CancellationToken ct = default) =>
|
||||||
|
FirstOrDefaultAsync(_db.Employees().Where(e => e.tb_Employee_ID == employeeId), ct);
|
||||||
|
|
||||||
|
public Task<TbEmployee?> GetEmployeeByIdOrCodeAsync(string employeeId, CancellationToken ct = default) =>
|
||||||
|
FirstOrDefaultAsync(
|
||||||
|
_db.Employees().Where(e => e.tb_Employee_ID == employeeId || e.EmployeeID == employeeId),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
public async Task<bool> UpdateEmployeePasswordAsync(string employeeId, string hashedPassword, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var rows = await _db.UpdateEmployees()
|
||||||
|
.SetColumns(e => e.Password == hashedPassword)
|
||||||
|
.Where(e => e.tb_Employee_ID == employeeId || e.EmployeeID == employeeId)
|
||||||
|
.ExecuteCommandAsync(ct);
|
||||||
|
return rows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string[]> GetDepartmentIdsAsync(string employeeId, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var list = await _db.EmployeeDepartMappings()
|
||||||
|
.Where(m => m.tb_Employee_ID == employeeId && m.DepartmentID != null)
|
||||||
|
.Select(m => m.DepartmentID!)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
return list.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TbSysRole?> GetRoleByIdAsync(string roleId, CancellationToken ct = default) =>
|
||||||
|
FirstOrDefaultAsync(_db.SysRoles().Where(r => r.tb_SysRole_ID == roleId), ct);
|
||||||
|
|
||||||
|
public Task<string?> GetHospitalNameAsync(string hospitalId, CancellationToken ct = default) =>
|
||||||
|
_db.Hospitals().Where(h => h.tb_Hospital_ID == hospitalId).Select(h => h.HospitalName).FirstAsync(ct);
|
||||||
|
|
||||||
|
public Task<TbDataRight?> GetDataRightByEmployeeIdAsync(string employeeId, CancellationToken ct = default) =>
|
||||||
|
FirstOrDefaultAsync(_db.DataRights().Where(d => d.EmployeeID == employeeId), ct);
|
||||||
|
|
||||||
|
private static async Task<T?> FirstOrDefaultAsync<T>(ISugarQueryable<T> query, CancellationToken ct)
|
||||||
|
where T : class, new()
|
||||||
|
{
|
||||||
|
var list = await query.Take(1).ToListAsync(ct);
|
||||||
|
return list.Count > 0 ? list[0] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
using Hcrm.IRepository;
|
||||||
|
using Hcrm.Model;
|
||||||
|
using Hcrm.Repository.Persistence;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Hcrm.Repository.Repositories;
|
||||||
|
|
||||||
|
/// <summary>店铺装修仓储实现。</summary>
|
||||||
|
public class ShopRenovateRepository : IShopRenovateRepository
|
||||||
|
{
|
||||||
|
private readonly ISqlSugarClient _db;
|
||||||
|
|
||||||
|
public ShopRenovateRepository(ISqlSugarClient db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<TbShopRenovatePage>> GetPageListAsync(string? pageName, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var query = _db.ShopRenovatePages().Where(p => !p.IsDeleted);
|
||||||
|
if (!string.IsNullOrWhiteSpace(pageName))
|
||||||
|
query = query.Where(p => p.PageName.Contains(pageName));
|
||||||
|
|
||||||
|
return query.OrderBy(p => p.SortNo).OrderBy(p => p.CreateTime).ToListAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TbShopRenovatePage?> GetPageByIdAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var list = await _db.ShopRenovatePages()
|
||||||
|
.Where(p => p.tb_ShopRenovatePage_ID == id && !p.IsDeleted)
|
||||||
|
.Take(1)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
return list.Count > 0 ? list[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> InsertPageAsync(TbShopRenovatePage entity, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var rows = await _db.Insertable(entity).AS("tb_ShopRenovatePage").ExecuteCommandAsync(ct);
|
||||||
|
return rows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdatePageAsync(TbShopRenovatePage entity, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var rows = await _db.Updateable(entity).AS("tb_ShopRenovatePage")
|
||||||
|
.Where(p => p.tb_ShopRenovatePage_ID == entity.tb_ShopRenovatePage_ID && !p.IsDeleted)
|
||||||
|
.ExecuteCommandAsync(ct);
|
||||||
|
return rows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SoftDeletePageAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var rows = await _db.Updateable<TbShopRenovatePage>().AS("tb_ShopRenovatePage")
|
||||||
|
.SetColumns(p => new TbShopRenovatePage { IsDeleted = true, UpdateTime = DateTime.Now })
|
||||||
|
.Where(p => p.tb_ShopRenovatePage_ID == id && !p.IsDeleted)
|
||||||
|
.ExecuteCommandAsync(ct);
|
||||||
|
return rows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SetPageStatusAsync(string id, int status, DateTime? publishTime, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var rows = await _db.Updateable<TbShopRenovatePage>().AS("tb_ShopRenovatePage")
|
||||||
|
.SetColumns(p => new TbShopRenovatePage
|
||||||
|
{
|
||||||
|
Status = status,
|
||||||
|
PublishTime = publishTime,
|
||||||
|
UpdateTime = DateTime.Now
|
||||||
|
})
|
||||||
|
.Where(p => p.tb_ShopRenovatePage_ID == id && !p.IsDeleted)
|
||||||
|
.ExecuteCommandAsync(ct);
|
||||||
|
return rows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TbShopRenovateStyle?> GetStyleAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var list = await _db.ShopRenovateStyles()
|
||||||
|
.Where(s => s.tb_ShopRenovateStyle_ID == "default")
|
||||||
|
.Take(1)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
return list.Count > 0 ? list[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SaveStyleAsync(string styleConfigJson, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var exists = await _db.ShopRenovateStyles().AnyAsync(s => s.tb_ShopRenovateStyle_ID == "default", ct);
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
var rows = await _db.Insertable(new TbShopRenovateStyle
|
||||||
|
{
|
||||||
|
tb_ShopRenovateStyle_ID = "default",
|
||||||
|
StyleConfigJson = styleConfigJson,
|
||||||
|
UpdateTime = DateTime.Now
|
||||||
|
}).AS("tb_ShopRenovateStyle").ExecuteCommandAsync(ct);
|
||||||
|
return rows > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = await _db.Updateable<TbShopRenovateStyle>().AS("tb_ShopRenovateStyle")
|
||||||
|
.SetColumns(s => new TbShopRenovateStyle { StyleConfigJson = styleConfigJson, UpdateTime = DateTime.Now })
|
||||||
|
.Where(s => s.tb_ShopRenovateStyle_ID == "default")
|
||||||
|
.ExecuteCommandAsync(ct);
|
||||||
|
return updated > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace Hcrm.Repository.SqlSugar;
|
||||||
|
|
||||||
|
public static class SqlSugarClientFactory
|
||||||
|
{
|
||||||
|
public static ISqlSugarClient CreateClient(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var connectionString = configuration.GetConnectionString("DBContainer")
|
||||||
|
?? throw new InvalidOperationException("Connection string 'DBContainer' is not configured.");
|
||||||
|
|
||||||
|
return new SqlSugarClient(new ConnectionConfig
|
||||||
|
{
|
||||||
|
ConnectionString = connectionString,
|
||||||
|
DbType = DbType.SqlServer,
|
||||||
|
IsAutoCloseConnection = true,
|
||||||
|
InitKeyType = InitKeyType.Attribute
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using Hcrm.IService;
|
||||||
|
using Hcrm.Service.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Hcrm.Service;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddHcrmService(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IAccountService, AccountService>();
|
||||||
|
services.AddScoped<IShopRenovateService, ShopRenovateService>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,177 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
using Hcrm.IRepository;
|
||||||
|
using Hcrm.IService;
|
||||||
|
using Hcrm.Model;
|
||||||
|
|
||||||
|
namespace Hcrm.Service.Services;
|
||||||
|
|
||||||
|
public class AccountService : IAccountService
|
||||||
|
{
|
||||||
|
private readonly IAccountRepository _repo;
|
||||||
|
private readonly ILoginTokenService _loginToken;
|
||||||
|
private readonly ICurrentUserAccessor _currentUser;
|
||||||
|
|
||||||
|
public AccountService(IAccountRepository repo, ILoginTokenService loginToken, ICurrentUserAccessor currentUser)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
_loginToken = loginToken;
|
||||||
|
_currentUser = currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> LoginAsync(string userName, string password, string? code, bool requireCaptcha, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (requireCaptcha)
|
||||||
|
{
|
||||||
|
var sessionCode = _loginToken.GetCaptcha();
|
||||||
|
if (string.IsNullOrEmpty(sessionCode))
|
||||||
|
return ApiResult.Fail("验证码已过期,请更新验证码!");
|
||||||
|
if (!string.Equals(code, sessionCode, StringComparison.Ordinal))
|
||||||
|
return ApiResult.Fail("验证码错误!");
|
||||||
|
_loginToken.ClearCaptcha();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(userName))
|
||||||
|
return ApiResult.Fail("用户名不能为空!");
|
||||||
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
return ApiResult.Fail("密码不能为空!");
|
||||||
|
|
||||||
|
var loginPrompt = await _repo.GetLoginPromptAsync(ct);
|
||||||
|
|
||||||
|
if (!await _repo.EmployeeExistsAsync(userName, ct))
|
||||||
|
return ApiResult.Fail(loginPrompt == 1 ? "登录失败!" : "用户名不存在!");
|
||||||
|
|
||||||
|
var hashedPwd = userName == "999999"
|
||||||
|
? Md5Helper.Md5String(password)
|
||||||
|
: Md5Helper.Md5String(password.Trim());
|
||||||
|
|
||||||
|
var user = await _repo.GetEmployeeByCredentialsAsync(userName, hashedPwd, ct);
|
||||||
|
if (user == null)
|
||||||
|
return ApiResult.Fail(loginPrompt == 1 ? "登录失败!" : "密码错误!");
|
||||||
|
|
||||||
|
if (user.OnJob == 2)
|
||||||
|
return ApiResult.Fail("账户被系统禁用!");
|
||||||
|
|
||||||
|
var account = await BuildAccountModelAsync(user, ct);
|
||||||
|
var dto = await BuildEmployeeDtoAsync(user, ct);
|
||||||
|
_loginToken.SetCookie(account);
|
||||||
|
return ApiResult.Success(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> IsLoginAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var current = _currentUser.CurrentUser;
|
||||||
|
if (current == null)
|
||||||
|
return ApiResult.NotLoggedIn();
|
||||||
|
|
||||||
|
var user = await _repo.GetEmployeeByIdAsync(current.tb_Employee_ID, ct);
|
||||||
|
if (user == null)
|
||||||
|
return ApiResult.Fail("用户名不存在!");
|
||||||
|
|
||||||
|
var dto = await BuildEmployeeDtoAsync(user, ct);
|
||||||
|
return ApiResult.Success(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApiResult> LogoutAsync()
|
||||||
|
{
|
||||||
|
_loginToken.Logout();
|
||||||
|
return Task.FromResult(ApiResult.Success());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> ModifyPwdAsync(string employeeId, string password, string newPassword, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var current = _currentUser.CurrentUser;
|
||||||
|
if (current == null)
|
||||||
|
return ApiResult.NotLoggedIn();
|
||||||
|
|
||||||
|
var user = await _repo.GetEmployeeByIdOrCodeAsync(employeeId, ct);
|
||||||
|
if (user == null)
|
||||||
|
return ApiResult.Fail("用户ID错误");
|
||||||
|
|
||||||
|
if (user.Password != Md5Helper.Md5String(password))
|
||||||
|
return ApiResult.Fail("原密码错误");
|
||||||
|
|
||||||
|
var newHashed = Md5Helper.Md5String(newPassword);
|
||||||
|
await _repo.UpdateEmployeePasswordAsync(user.tb_Employee_ID, newHashed, ct);
|
||||||
|
|
||||||
|
user.Password = newHashed;
|
||||||
|
var account = await BuildAccountModelAsync(user, ct);
|
||||||
|
_loginToken.SetCookie(account);
|
||||||
|
return ApiResult.Success("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<AccountModel> BuildAccountModelAsync(TbEmployee user, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var departList = await _repo.GetDepartmentIdsAsync(user.tb_Employee_ID, ct);
|
||||||
|
|
||||||
|
string? roleCode = null;
|
||||||
|
if (!string.IsNullOrEmpty(user.RoleID))
|
||||||
|
roleCode = (await _repo.GetRoleByIdAsync(user.RoleID, ct))?.RCode;
|
||||||
|
|
||||||
|
string? hospitalName = null;
|
||||||
|
if (!string.IsNullOrEmpty(user.HospitalID))
|
||||||
|
hospitalName = await _repo.GetHospitalNameAsync(user.HospitalID, ct);
|
||||||
|
|
||||||
|
return new AccountModel
|
||||||
|
{
|
||||||
|
tb_Employee_ID = user.tb_Employee_ID,
|
||||||
|
EmployeeID = user.EmployeeID ?? "",
|
||||||
|
TrueName = user.EmployeeName ?? "",
|
||||||
|
Photo = user.HeadURL,
|
||||||
|
HospitalID = user.HospitalID,
|
||||||
|
HospitalName = hospitalName,
|
||||||
|
DepartmentID = user.DepartmentID,
|
||||||
|
DepartmentIDList = departList,
|
||||||
|
RoleID = user.RoleID,
|
||||||
|
RoleCode = roleCode,
|
||||||
|
ConsultGroupID = user.ConsultGroupID,
|
||||||
|
WardAreaID = user.WardAreaID,
|
||||||
|
MedicalUnit = user.MedicalUnit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<EmployeeDto> BuildEmployeeDtoAsync(TbEmployee user, CancellationToken ct)
|
||||||
|
{
|
||||||
|
SysRoleDto? roleDto = null;
|
||||||
|
var isDoctor = false;
|
||||||
|
if (!string.IsNullOrEmpty(user.RoleID))
|
||||||
|
{
|
||||||
|
var role = await _repo.GetRoleByIdAsync(user.RoleID, ct);
|
||||||
|
if (role != null)
|
||||||
|
{
|
||||||
|
roleDto = new SysRoleDto
|
||||||
|
{
|
||||||
|
tb_SysRole_ID = role.tb_SysRole_ID,
|
||||||
|
RCode = role.RCode,
|
||||||
|
RoleName = role.RoleName
|
||||||
|
};
|
||||||
|
isDoctor = role.RoleName == "医生";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string? hospitalName = null;
|
||||||
|
if (!string.IsNullOrEmpty(user.HospitalID))
|
||||||
|
hospitalName = await _repo.GetHospitalNameAsync(user.HospitalID, ct);
|
||||||
|
|
||||||
|
var dataRight = await _repo.GetDataRightByEmployeeIdAsync(user.tb_Employee_ID, ct);
|
||||||
|
|
||||||
|
return new EmployeeDto
|
||||||
|
{
|
||||||
|
tb_Employee_ID = user.tb_Employee_ID,
|
||||||
|
EmployeeID = user.EmployeeID ?? "",
|
||||||
|
EmployeeName = user.EmployeeName ?? "",
|
||||||
|
Sex = user.Sex,
|
||||||
|
Photo = user.HeadURL,
|
||||||
|
Mobile = user.Mobile,
|
||||||
|
Telephone = user.Telephone ?? "",
|
||||||
|
HospitalID = user.HospitalID,
|
||||||
|
HospitalName = hospitalName,
|
||||||
|
DepartmentID = user.DepartmentID,
|
||||||
|
ConsultGroupID = user.ConsultGroupID,
|
||||||
|
Role = roleDto ?? new SysRoleDto(),
|
||||||
|
LookMobile = dataRight?.LookMobile,
|
||||||
|
LookCredentialCode = dataRight?.LookCredentialCode,
|
||||||
|
LookName = dataRight?.LookName ?? true,
|
||||||
|
IsDoctor = isDoctor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,172 @@
|
|||||||
|
using Hcrm.DTO;
|
||||||
|
using Hcrm.IRepository;
|
||||||
|
using Hcrm.IService;
|
||||||
|
using Hcrm.Model;
|
||||||
|
|
||||||
|
namespace Hcrm.Service.Services;
|
||||||
|
|
||||||
|
/// <summary>店铺装修:页面管理与全店风格配置。</summary>
|
||||||
|
public class ShopRenovateService : IShopRenovateService
|
||||||
|
{
|
||||||
|
private readonly IShopRenovateRepository _repo;
|
||||||
|
|
||||||
|
public ShopRenovateService(IShopRenovateRepository repo)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> GetPageListAsync(string? pageName, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var list = await _repo.GetPageListAsync(pageName, ct);
|
||||||
|
var dtoList = list.Select(ToListDto).ToList();
|
||||||
|
return ApiResult.Success(dtoList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> GetPageAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
return ApiResult.Fail("页面ID不能为空!");
|
||||||
|
|
||||||
|
var page = await _repo.GetPageByIdAsync(id, ct);
|
||||||
|
if (page == null)
|
||||||
|
return ApiResult.Fail("页面不存在!");
|
||||||
|
|
||||||
|
return ApiResult.Success(ToDetailDto(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> CreatePageAsync(ShopRenovatePageSaveRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.PageName))
|
||||||
|
return ApiResult.Fail("页面名称不能为空!");
|
||||||
|
if (string.IsNullOrWhiteSpace(request.PageKey))
|
||||||
|
return ApiResult.Fail("页面标识不能为空!");
|
||||||
|
|
||||||
|
var entity = new TbShopRenovatePage
|
||||||
|
{
|
||||||
|
tb_ShopRenovatePage_ID = Guid.NewGuid().ToString("N"),
|
||||||
|
PageName = request.PageName.Trim(),
|
||||||
|
PageKey = request.PageKey.Trim(),
|
||||||
|
SortNo = request.SortNo ?? 0,
|
||||||
|
Status = ShopRenovateStatus.Unpublished,
|
||||||
|
PageConfigJson = request.PageConfigJson,
|
||||||
|
CreateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!await _repo.InsertPageAsync(entity, ct))
|
||||||
|
return ApiResult.Fail("新增失败!");
|
||||||
|
|
||||||
|
return ApiResult.Success(ToDetailDto(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> UpdatePageAsync(ShopRenovatePageSaveRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.tb_ShopRenovatePage_ID))
|
||||||
|
return ApiResult.Fail("页面ID不能为空!");
|
||||||
|
|
||||||
|
var page = await _repo.GetPageByIdAsync(request.tb_ShopRenovatePage_ID, ct);
|
||||||
|
if (page == null)
|
||||||
|
return ApiResult.Fail("页面不存在!");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.PageName))
|
||||||
|
page.PageName = request.PageName.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.PageKey))
|
||||||
|
page.PageKey = request.PageKey.Trim();
|
||||||
|
if (request.SortNo.HasValue)
|
||||||
|
page.SortNo = request.SortNo.Value;
|
||||||
|
if (request.PageConfigJson != null)
|
||||||
|
page.PageConfigJson = request.PageConfigJson;
|
||||||
|
page.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
|
if (!await _repo.UpdatePageAsync(page, ct))
|
||||||
|
return ApiResult.Fail("保存失败!");
|
||||||
|
|
||||||
|
return ApiResult.Success(ToDetailDto(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> DeletePageAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
return ApiResult.Fail("页面ID不能为空!");
|
||||||
|
|
||||||
|
if (!await _repo.SoftDeletePageAsync(id, ct))
|
||||||
|
return ApiResult.Fail("删除失败!");
|
||||||
|
|
||||||
|
return ApiResult.Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> PublishPageAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
return ApiResult.Fail("页面ID不能为空!");
|
||||||
|
|
||||||
|
var page = await _repo.GetPageByIdAsync(id, ct);
|
||||||
|
if (page == null)
|
||||||
|
return ApiResult.Fail("页面不存在!");
|
||||||
|
if (page.Status == ShopRenovateStatus.Published)
|
||||||
|
return ApiResult.Fail("页面已发布!");
|
||||||
|
|
||||||
|
if (!await _repo.SetPageStatusAsync(id, ShopRenovateStatus.Published, DateTime.Now, ct))
|
||||||
|
return ApiResult.Fail("发布失败!");
|
||||||
|
|
||||||
|
page.Status = ShopRenovateStatus.Published;
|
||||||
|
page.PublishTime = DateTime.Now;
|
||||||
|
return ApiResult.Success(ToListDto(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> UnpublishPageAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
return ApiResult.Fail("页面ID不能为空!");
|
||||||
|
|
||||||
|
var page = await _repo.GetPageByIdAsync(id, ct);
|
||||||
|
if (page == null)
|
||||||
|
return ApiResult.Fail("页面不存在!");
|
||||||
|
if (page.Status == ShopRenovateStatus.Unpublished)
|
||||||
|
return ApiResult.Fail("页面未发布!");
|
||||||
|
|
||||||
|
if (!await _repo.SetPageStatusAsync(id, ShopRenovateStatus.Unpublished, null, ct))
|
||||||
|
return ApiResult.Fail("取消发布失败!");
|
||||||
|
|
||||||
|
page.Status = ShopRenovateStatus.Unpublished;
|
||||||
|
page.PublishTime = null;
|
||||||
|
return ApiResult.Success(ToListDto(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> GetStyleConfigAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var style = await _repo.GetStyleAsync(ct);
|
||||||
|
return ApiResult.Success(new ShopRenovateStyleDto
|
||||||
|
{
|
||||||
|
StyleConfigJson = style?.StyleConfigJson
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResult> SaveStyleConfigAsync(string? styleConfigJson, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(styleConfigJson))
|
||||||
|
return ApiResult.Fail("风格配置不能为空!");
|
||||||
|
|
||||||
|
if (!await _repo.SaveStyleAsync(styleConfigJson, ct))
|
||||||
|
return ApiResult.Fail("保存失败!");
|
||||||
|
|
||||||
|
return ApiResult.Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShopRenovatePageDto ToListDto(TbShopRenovatePage p) => new()
|
||||||
|
{
|
||||||
|
tb_ShopRenovatePage_ID = p.tb_ShopRenovatePage_ID,
|
||||||
|
SortNo = p.SortNo,
|
||||||
|
PageName = p.PageName,
|
||||||
|
PageKey = p.PageKey,
|
||||||
|
PublishTime = p.PublishTime?.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
Status = p.Status,
|
||||||
|
StatusName = p.Status == ShopRenovateStatus.Published ? "已发布" : "未发布"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static ShopRenovatePageDto ToDetailDto(TbShopRenovatePage p)
|
||||||
|
{
|
||||||
|
var dto = ToListDto(p);
|
||||||
|
dto.PageConfigJson = p.PageConfigJson;
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue