# 业务对外API

# 概述

一个模块的API层(Web层),主要负责如下几个方面的工作:

接收前端层提交的数据查询请求,使用服务层提供的IQueryable<T>查询数据源,查询出需要的数据返回前端

接收前端层提交的业务处理请求,调用服务层的服务,处理业务需求,并将操作结果返回前端

使用MVC的Area-Controller-Action的层次关系,联合 [ModuleInfo]特性, 定义Api模块Module树形组织结构API模块依赖关系,构建出Module的树形数据

定义API的可访问方式,API的访问方式可分为匿名访问,登录访问和角色访问

定义自动事务提交,涉及数据库变更的业务,可在API定义自动事务提交,在业务层实现业务时即可不用考虑事务的问题

# API层代码布局

API层即是Web网站服务端的MVC控制器,控制器可按粒度需要不同,分为模块控制器和单实体控制器,这个由业务需求决定。

通常,后台管理的控制器,是实体粒度的,即每个实体都有一个控制器,并且存在于/Areas/Security/Controlers文件夹内。

业务单据编码模块的API层控制器,如下图所示:

src                                     # 源代码文件夹
└─Yuebon.WebApi                         # 项目Web工程
     └─Areas                            # 区域文件夹
         └─Security                     # 权限区域文件夹
          ├─SequenceController.cs       # 业务单据控制器
          └─ISequenceRuleController.cs  # 单据编码规则控制器

# API的基础建设

# API定义

API定义即MVC或WebApi的Area-Controller-Action定义,为方便及规范此步骤的工作,YuebonCore定义了一些API基础控制器基类,继承这些基类,很容易实现API定义。

# ApiController

ApiController 用于非Area的Api控制器,基类添加了[ApiController]跨域访问[EnableCors("yuebonCors")]特性。

 /// <summary>
/// WebApi控制器基类
/// </summary>
[ApiController]
[EnableCors("yuebonCors")]
public class ApiController : Controller
{
    /// <summary>
    /// 当前登录的用户属性
    /// </summary>
    public YuebonCurrentUser CurrentUser;
    private ILogRepository service = new LogRepository();
}

该基类定义在api接口中需要用到的公共方法输出结果对象转json的方法ToJsonContent()、获取分页参数GetPagerInfo()方法。

需要特别注意的是重写了OnActionExecuting()方法,在此方法中根据token实现用户当前操作用户赋值CurrentUser,通实现访问操作日志记录。

/// <summary>
/// 重写基类在Action执行之前的事情
/// 根据token获得当前用户,允许匿名的不需要获取用户
/// </summary>
/// <param name="filterContext">重写方法的参数</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var controllerActionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor;

    try
    {   //匿名访问不需要token认证
        var allowanyone = controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(typeof(IAllowAnonymous), true).Any()
        || controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(IAllowAnonymous), true).Any();
        if (!allowanyone)
        {
            var identities = filterContext.HttpContext.User.Identities;
            var claimsIdentity = identities.First<ClaimsIdentity>();
            if (claimsIdentity != null)
            {
                List<Claim> claimlist= claimsIdentity.Claims as List<Claim>;
                if (claimlist.Count > 0)
                {
                    string userId = claimlist[0].Value;
                    YuebonCacheHelper yuebonCacheHelper = new YuebonCacheHelper();
                    var user = yuebonCacheHelper.Get<YuebonCurrentUser>("login_user_" + userId);
                    if (user != null)
                    {
                        CurrentUser = user;
                    }
                }
            }
        }
        Log logEntity = new Log();
        if (CurrentUser != null)
        {
            logEntity.Account = CurrentUser.Account;
            logEntity.NickName = CurrentUser.NickName;
            logEntity.IPAddressName = CurrentUser.IPAddressName;
        }
        logEntity.IPAddress = filterContext.HttpContext.Connection.RemoteIpAddress.ToString();
        logEntity.Date = logEntity.CreatorTime = DateTime.Now;
        logEntity.Result = false;
        logEntity.Description = filterContext.HttpContext.Request.Path + filterContext.HttpContext.Request.QueryString;
        logEntity.Type = "Visit";
        service.Insert(logEntity);
        base.OnActionExecuting(filterContext);
    }
    catch (Exception ex)
    {
        Log4NetHelper.Error("", ex);
    }
}

# AreaApiController

与无区域控制器基类ApiController相对应,对于区域控制器,也定义了一个基类AreaApiController

该基类定义一些常用的公共方法,如新增、修改、删除、禁用/启用、软删除、分页查询等方法。

/// <summary>
/// 基本控制器,增删改查
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
/// <typeparam name="TODto">数据输出实体类型</typeparam>
/// <typeparam name="TIDto">数据输入实体类型</typeparam>
/// <typeparam name="TService">Service类型</typeparam>
/// <typeparam name="TKey">主键数据类型</typeparam>
[ApiController]
public abstract class AreaApiController<T,TODto, TIDto, TService, TKey> : ApiController
    where T : Entity
    where TService : IService<T, TODto, TKey>
    where TODto : class
    where TIDto : class
    where TKey : IEquatable<TKey>
{
    
    /// <summary>
    /// 服务接口
    /// </summary>
    public TService iService;
    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="_iService"></param>
    public AreaApiController(TService _iService)
    {
        iService = _iService;
    }
}

# API访问控制

系统默认接口都需要验证token和登录访问。目前API的访问控制,分为三种:

匿名访问AllowAnonymousAttribute:表示当前功能不需要登录即可访问,无视登录状态、角色、Token要求。

不需要登录访问NoPermissionRequiredAttribute:表示当前功能不需要登录访问,但需要验证token。

功能权限YuebonAuthorizeAttribute:表示当前功能需要登录并且用户拥有指定功能权限,才能访问,未登录或者登录但未拥有指定功能权限,拒绝访问

# 模块API实现

新增和修改的实现。

/// <summary>
/// 异步新增或修改数据
/// </summary>
/// <param name="info"></param>
/// <returns></returns>

[HttpPost("Insert")]
[YuebonAuthorize("Add")]
public override async Task<IActionResult> InsertAsync(SequenceInputDto info)
{
    CommonResult result = new CommonResult();

    if (string.IsNullOrEmpty(info.SequenceName))
    {
        result.ErrMsg = "单据名称不能为空";
        return ToJsonContent(result);
    }

    if (string.IsNullOrEmpty(info.Id))
    {
        string where = string.Format("SequenceName='{0}'", info.SequenceName);
        Sequence sequenceIsExist = iService.GetWhere(where);
        if (sequenceIsExist != null)
        {
            result.ErrMsg = "规则名称不能重复";
            return ToJsonContent(result);
        }
        Sequence sequence =info.MapTo<Sequence>();
        OnBeforeInsert(sequence);
        long ln = await iService.InsertAsync(sequence).ConfigureAwait(true);
        result.Success = ln > 0;
    }
    if (result.Success)
    {
        result.ErrCode = ErrCode.successCode;
        result.ErrMsg = ErrCode.err0;
    }
    else
    {
        result.Success = false;
        result.ErrMsg = ErrCode.err43001;
        result.ErrCode = "43001";
    }
    return ToJsonContent(result);
}


/// <summary>
/// 异步更新数据,需要在业务模块控制器重写该方法,否则更新无效
/// </summary>
/// <param name="info"></param>
/// <param name="id">主键Id</param>
/// <returns></returns>
[HttpPost("Update")]
[YuebonAuthorize("Edit")]
public override async Task<IActionResult> UpdateAsync(SequenceInputDto info, string id)
{
    CommonResult result = new CommonResult();
    if (string.IsNullOrEmpty(info.SequenceName))
    {
        result.ErrMsg = "单据名称不能为空";
        return ToJsonContent(result);
    }

    string where = string.Format("SequenceName='{0}' and id!='{1}'", info.SequenceName, info.Id);
    Sequence goodsIsExist = iService.GetWhere(where);
    if (goodsIsExist != null)
    {
        result.ErrMsg = "规则名称不能重复";
        return ToJsonContent(result);
    }
    Sequence sequence = iService.Get(info.Id);
    sequence.SequenceName = info.SequenceName;
    sequence.SequenceDelimiter = info.SequenceDelimiter;
    sequence.SequenceReset = info.SequenceReset;
    sequence.Step = info.Step;
    sequence.CurrentNo = info.CurrentNo;
    sequence.CurrentCode = info.CurrentCode;
    sequence.CurrentReset = info.CurrentReset;
    sequence.EnabledMark = info.EnabledMark;
    sequence.Description = info.Description;
    OnBeforeUpdate(sequence);
    result.Success = await iService.UpdateAsync(sequence, info.Id).ConfigureAwait(true);

    if (result.Success)
    {
        result.ErrCode = ErrCode.successCode;
        result.ErrMsg = ErrCode.err0;
    }
    else
    {
        result.Success = false;
        result.ErrMsg = ErrCode.err43001;
        result.ErrCode = "43001";
    }
    return ToJsonContent(result);
}