业务对外 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> : ApiController
where T : Entity
where TService : IService<T, TODto>
where TODto : class
where TIDto : class
{
/// <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);
}