init: F9智慧缫丝系统 - 后端(server) + 主前端(web) + 打印设计器(openprint) 首次提交

This commit is contained in:
2026-08-15 15:37:25 +08:00
commit 7235265749
294 changed files with 59182 additions and 0 deletions
@@ -0,0 +1,212 @@
using System.Linq.Expressions;
using System.Reflection;
using FreeSql;
using F9MES.Application.Dtos;
using F9MES.Common.Auth;
using F9MES.Common.Entities;
using F9MES.Common.Result;
namespace F9MES.Application.Base;
/// <summary>
/// 通用 CRUD 服务:所有业务实体的增删改查基础能力
/// 统一处理:Flag 软删除过滤、操作人/时间自动填充、分页关键字检索
/// 基于 IFreeSql 原生 APIFreeSql 3.5.311 已移除仓储接口)
/// </summary>
public class CrudService<T> where T : BaseEntity, new()
{
protected readonly IFreeSql Db;
protected readonly CurrentUserService CurrentUser;
public CrudService(IFreeSql db, CurrentUserService currentUser)
{
Db = db;
CurrentUser = currentUser;
}
/// <summary>动态条件分页查询(支持等值条件字典 + 关键字模糊搜索)</summary>
public virtual async Task<PageResult<T>> GetPageAsync(QueryDto dto)
{
if (dto.Page < 1) dto.Page = 1;
if (dto.Size < 1 || dto.Size > 500) dto.Size = 20;
var query = Db.Select<T>().Where(a => a.Flag > 0);
// 等值条件(属性名大小写不敏感,兼容前端 camelCase 传参)
if (dto.Where is { Count: > 0 })
{
foreach (var kv in dto.Where)
{
var prop = typeof(T).GetProperty(kv.Key,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (prop == null) continue;
var val = ConvertValue(kv.Value, prop.PropertyType);
if (val != null)
query = query.Where(BuildEquals(prop.Name, val));
}
}
// 关键字模糊匹配 Name/Code
if (!string.IsNullOrWhiteSpace(dto.Keyword))
{
var kw = dto.Keyword.Trim();
var propName = typeof(T).GetProperty("Name")?.PropertyType == typeof(string) ? "Name"
: typeof(T).GetProperty("Code")?.PropertyType == typeof(string) ? "Code"
: null;
if (propName != null)
query = query.Where(BuildContains(propName, kw));
}
var total = await query.CountAsync();
var items = await query
.OrderByDescending(a => a.Id)
.Skip((dto.Page - 1) * dto.Size)
.Take(dto.Size)
.ToListAsync();
return PageResult<T>.From(dto.Page, dto.Size, total, items);
}
/// <summary>按条件分页查询</summary>
public virtual async Task<PageResult<T>> GetPageAsync(Expression<Func<T, bool>> where, int page = 1, int size = 20)
{
if (page < 1) page = 1;
if (size < 1 || size > 500) size = 20;
var query = Db.Select<T>().Where(a => a.Flag > 0).Where(where);
var total = await query.CountAsync();
var items = await query
.OrderByDescending(a => a.Id)
.Skip((page - 1) * size)
.Take(size)
.ToListAsync();
return PageResult<T>.From(page, size, total, items);
}
/// <summary>按 ID 查询</summary>
public virtual async Task<T?> GetByIdAsync(long id)
{
return await Db.Select<T>().Where(a => a.Id == id && a.Flag > 0).FirstAsync();
}
/// <summary>新增(自动填充操作人/时间,FreeSql 自动回填自增ID</summary>
public virtual async Task<T> AddAsync(T entity)
{
var uid = CurrentUser.UserId;
entity.AddTime = DateTime.Now;
entity.UpdateTime = DateTime.Now;
entity.Adder = uid;
entity.Updater = uid;
if (entity.Flag == 0) entity.Flag = 1;
// ExecuteIdentityAsync:返回自增主键(3.5.311 中 ExecuteAffrows 不回填,此处手动赋值)
entity.Id = await Db.Insert(entity).ExecuteIdentityAsync();
return entity;
}
/// <summary>修改(自动更新操作人/时间)</summary>
public virtual async Task<ApiResult> UpdateAsync(T entity)
{
var old = await Db.Select<T>().Where(a => a.Id == entity.Id && a.Flag > 0).FirstAsync();
if (old == null) return ApiResult.Error("数据不存在或已删除");
entity.UpdateTime = DateTime.Now;
entity.Updater = CurrentUser.UserId;
// 保护字段不允许被修改
entity.AddTime = old.AddTime;
entity.Adder = old.Adder;
await Db.Update<T>().SetSource(entity).ExecuteAffrowsAsync();
return ApiResult.Ok();
}
/// <summary>部分修改:仅更新请求体中出现的字段(避免未提交字段被清零)</summary>
public virtual async Task<ApiResult> UpdateAsync(T entity, IEnumerable<string> fields)
{
var old = await Db.Select<T>().Where(a => a.Id == entity.Id && a.Flag > 0).FirstAsync();
if (old == null) return ApiResult.Error("数据不存在或已删除");
var set = new HashSet<string>(fields, StringComparer.OrdinalIgnoreCase);
foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanWrite || !set.Contains(prop.Name)) continue;
if (prop.Name is "Id" or "Flag" or "AddTime" or "Adder") continue;
prop.SetValue(old, prop.GetValue(entity));
}
old.UpdateTime = DateTime.Now;
old.Updater = CurrentUser.UserId;
await Db.Update<T>().SetSource(old).ExecuteAffrowsAsync();
return ApiResult.Ok();
}
/// <summary>删除(软删除:Flag=0</summary>
public virtual async Task<ApiResult> DeleteAsync(long id)
{
var old = await Db.Select<T>().Where(a => a.Id == id && a.Flag > 0).FirstAsync();
if (old == null) return ApiResult.Error("数据不存在或已删除");
old.Flag = 0;
old.UpdateTime = DateTime.Now;
old.Updater = CurrentUser.UserId;
await Db.Update<T>().SetSource(old).ExecuteAffrowsAsync();
return ApiResult.Ok();
}
/// <summary>批量删除</summary>
public virtual async Task<ApiResult> DeleteRangeAsync(IEnumerable<long> ids)
{
var list = await Db.Select<T>().Where(a => ids.Contains(a.Id) && a.Flag > 0).ToListAsync();
foreach (var item in list)
{
item.Flag = 0;
item.UpdateTime = DateTime.Now;
item.Updater = CurrentUser.UserId;
}
if (list.Count > 0) await Db.Update<T>().SetSource(list).ExecuteAffrowsAsync();
return ApiResult.Ok(message: $"已删除 {list.Count} 条");
}
/// <summary>全部有效数据(下拉选择用)</summary>
public virtual async Task<List<T>> GetAllAsync()
{
return await Db.Select<T>().Where(a => a.Flag > 0).OrderBy(a => a.Id).ToListAsync();
}
/// <summary>构建字符串字段包含关键字的表达式(FreeSql 可翻译)</summary>
protected static Expression<Func<T, bool>> BuildContains(string propName, string keyword)
{
var param = Expression.Parameter(typeof(T), "a");
var prop = Expression.Property(param, propName);
var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
var call = Expression.Call(prop, contains, Expression.Constant(keyword));
return Expression.Lambda<Func<T, bool>>(call, param);
}
/// <summary>构建等值比较表达式</summary>
protected static Expression<Func<T, bool>> BuildEquals(string propName, object value)
{
var param = Expression.Parameter(typeof(T), "a");
var prop = Expression.Property(param, propName);
var converted = Expression.Convert(Expression.Constant(value), prop.Type);
var body = Expression.Equal(prop, converted);
return Expression.Lambda<Func<T, bool>>(body, param);
}
/// <summary>将 JSON 值转换为属性目标类型</summary>
protected static object? ConvertValue(object value, Type targetType)
{
try
{
if (value == null) return null;
var type = Nullable.GetUnderlyingType(targetType) ?? targetType;
if (type == typeof(string)) return value.ToString();
if (type == typeof(long)) return Convert.ToInt64(value);
if (type == typeof(int)) return Convert.ToInt32(value);
if (type == typeof(decimal)) return Convert.ToDecimal(value);
if (type == typeof(double)) return Convert.ToDouble(value);
if (type == typeof(DateTime)) return Convert.ToDateTime(value);
if (type == typeof(bool)) return Convert.ToBoolean(value);
return value;
}
catch
{
return null;
}
}
}