Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【讨论】对外提供访问接口的模板 #5

Open
hd2y opened this issue Mar 28, 2024 · 2 comments
Open

【讨论】对外提供访问接口的模板 #5

hd2y opened this issue Mar 28, 2024 · 2 comments

Comments

@hd2y
Copy link

hd2y commented Mar 28, 2024

我这边开发时,生成的应用还需要提供出接口供前端访问,这里基于这个需求和聚合根的特点,提供一个案例供大家参考:

首先是分页查询的入参:

/// <summary>
/// 分页查询输入
/// </summary>
public class PagedInput
{
    /// <summary>
    /// 过滤条件
    /// </summary>
    public DynamicFilterInfo? Filter { get; set; }

    /// <summary>
    /// 页码
    /// </summary>
    public int Page { get; set; }

    /// <summary>
    /// 分页数
    /// </summary>
    public int Size { get; set; }

    /// <summary>
    /// 排序
    /// </summary>
    public string? OrderBy { get; set; }

    /// <summary>
    /// 是否正序
    /// </summary>
    public bool Asc { get; set; }

    /// <summary>
    /// 包含的导航属性,如果一对多关系,需要包含多级导航属性用逗号分隔
    /// </summary>
    public string[]? Include { get; set; }
}

返回内容就比较简单了:

/// <summary>
/// 分页查询结果
/// </summary>
public class PagedOutput<TEntity> where TEntity : class
{
    /// <summary>
    /// 总数
    /// </summary>
    public long Total { get; set; }

    /// <summary>
    /// 分页数据
    /// </summary>
    public List<TEntity> Data { get; set; }
}

然后控制器设置一个 EntityControllerBase 方便其他聚合根实体对外公开接口时继承:

public class EntityControllerBase<TEntity, TKey>(IAggregateRootRepository<TEntity> repository)
    where TEntity : class, IEntity<TKey>
{
    /// <summary>
    /// 查询
    /// </summary>
    [HttpGet]
    public Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken) =>
        repository.Select.Where(t => t.Id.Equals(id)).FirstAsync(cancellationToken);

    /// <summary>
    /// 分页查询
    /// </summary>
    [HttpPost]
    public Task<PagedOutput<TEntity>> GetPagedAsync([FromBody] PagedInput input, CancellationToken cancellationToken)
    {
        var select = repository.Select;

        if (input.Filter != null)
        {
            select = select.WhereDynamicFilter(input.Filter);
        }

        if (input.Include is { Length: > 0 })
        {
            foreach (var includeProperty in input.Include)
            {
                if (string.IsNullOrWhiteSpace(includeProperty)) continue;
                var includeProperties = includeProperty.Split(",");

                // includeProperties 长度不固定,循环构造表达式树获取内容
                Expression<Action<ISelect<object>>> exp = null;
                for (var i = includeProperties.Length - 1; i > 0; i--)
                {
                    // 创建一个表示输入参数的参数表达式
                    var parameter = Expression.Parameter(typeof(ISelect<object>), "then" + i);

                    if (exp == null)
                    {
                        // 获取 IncludeByPropertyName 方法的 MethodInfo
                        var methodInfo = typeof(ISelect<object>).GetMethod("IncludeByPropertyName", [typeof(string)]);
                        var propertyName = Expression.Constant(includeProperties[i]);
                        var methodCall = Expression.Call(parameter, methodInfo, propertyName);

                        // 创建一个带有输入参数和方法调用的 Lambda 表达式
                        exp = Expression.Lambda<Action<ISelect<object>>>(methodCall, parameter);
                    }
                    else
                    {
                        // 获取 IncludeByPropertyName 方法的 MethodInfo
                        var methodInfo = typeof(ISelect<object>).GetMethod("IncludeByPropertyName",
                            [typeof(string), typeof(Expression<Action<ISelect<object>>>)]);
                        var propertyName = Expression.Constant(includeProperties[i]);
                        var methodCall = Expression.Call(parameter, methodInfo, propertyName, exp);

                        // 创建一个带有输入参数和方法调用的 Lambda 表达式
                        exp = Expression.Lambda<Action<ISelect<object>>>(methodCall, parameter);
                    }
                }

                select = exp == null
                    ? select.IncludeByPropertyName(includeProperties[0])
                    : select.IncludeByPropertyName(includeProperties[0], exp);
            }
        }

        if (!string.IsNullOrEmpty(input.OrderBy))
        {
            select = select.OrderBy(input.OrderBy, input.Asc);
        }

        return select.Count(out var total)
            .Page(Math.Max(input.Page, 1), Math.Min(Math.Max(input.Size, 1), 1000))
            .ToListAsync(cancellationToken)
            .ContinueWith(t => new PagedOutput<TEntity>
            {
                Total = total,
                Data = t.Result
            }, cancellationToken);
    }

    /// <summary>
    /// 新增
    /// </summary>
    [HttpPost]
    public Task<TEntity> AddAsync([FromBody] TEntity entity, CancellationToken cancellationToken) =>
        repository.InsertAsync(entity, cancellationToken);

    /// <summary>
    /// 删除
    /// </summary>
    [HttpDelete]
    public Task<int> DeleteAsync(TKey id, CancellationToken cancellationToken) =>
        repository.DeleteAsync(t => t.Id.Equals(id), cancellationToken);

    /// <summary>
    /// 更新
    /// </summary>
    [HttpPost]
    public Task<int> UpdateAsync([FromBody] TEntity entity, CancellationToken cancellationToken)
        => repository.UpdateDiy.SetSource(entity).ExecuteAffrowsAsync(cancellationToken);

    /// <summary>
    /// 批量新增
    /// </summary>
    [HttpPost]
    public async Task<int> AddMultipleAsync([FromBody] TEntity[] entities, CancellationToken cancellationToken)
    {
        var count = 0;
        foreach (var entity in entities)
        {
            await repository.InsertAsync(entity, cancellationToken);
            count++;
        }

        return count;
    }

    /// <summary>
    /// 批量删除
    /// </summary>
    [HttpDelete]
    public Task<int> DeleteMultipleAsync(TKey[] id, CancellationToken cancellationToken) =>
        repository.DeleteAsync(t => id.Contains(t.Id), cancellationToken);

    /// <summary>
    /// 批量更新
    /// </summary>
    [HttpPost]
    public Task<int> UpdateMultipleAsync([FromBody] TEntity[] entities, CancellationToken cancellationToken) =>
        repository.UpdateDiy.SetSource(entities).ExecuteAffrowsAsync(cancellationToken);
}

例如我有个 Component 实体,对外公开接口,则创建一个 ComponentController 继承 EntityControllerBase:

[Route("api/[controller]/[action]")]
public class ComponentController(IAggregateRootRepository<Component> repository)
    : EntityControllerBase<Component, long>(repository)
{
}
@hd2y
Copy link
Author

hd2y commented Mar 28, 2024

主要是分页查询拓展了一下 Include 属性,这里是分页查询 Form 表单的一个入参示例:

{
    "filter": {
        "logic": "And",
        "filters": [
            {
                "field": "id",
                "operator": "GreaterThanOrEqual",
                "value": 1
            }
        ]
    },
    "page": 1,
    "size": 10,
    "orderBy": "id",
    "asc": true,
    "include": [
        "formGroups",
        "formGroups,warehouse",
        "formGroups,warehouse.project",
        "formItems",
        "formItems,formItemProps",
        "formItems,formItemProps,componentProp",
        "formItems,formItemProps,componentProp.component"
    ]
}

@2881099
Copy link
Owner

2881099 commented Mar 28, 2024

有想法可以直接PR进来

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants