我們了解ABP框架內(nèi)部自動(dòng)記錄審計(jì)日志和登錄日志的,但是這些信息只是在相關(guān)的內(nèi)部接口里面進(jìn)行記錄,并沒有一個(gè)管理界面供我們了解揉阎,但是其系統(tǒng)數(shù)據(jù)庫(kù)記錄了這些數(shù)據(jù)信息,我們可以為它們?cè)O(shè)計(jì)一個(gè)查看和導(dǎo)出這些審計(jì)日志和登錄日志的管理界面背捌。本篇隨筆繼續(xù)ABP框架的系列介紹毙籽,一步步深入了解ABP框架的應(yīng)用開發(fā),介紹審計(jì)日志和登錄日志的管理毡庆。
1坑赡、審計(jì)日志和登錄日志的基礎(chǔ)
審計(jì)日志,設(shè)置我們?cè)谠L問或者調(diào)用某個(gè)應(yīng)用服務(wù)層接口的時(shí)候么抗,橫切面流下的一系列操作記錄毅否,其中記錄我們?cè)L問的服務(wù)接口,參數(shù)蝇刀,客戶端IP地址螟加,訪問時(shí)間,以及異常等信息,這些操作都是在ABP系統(tǒng)自動(dòng)記錄的捆探,如果我們需要屏蔽某些服務(wù)類或者接口然爆,則這些就不會(huì)記錄在里面,否則默認(rèn)是記錄的黍图。
登錄日志曾雕,這個(gè)就是用戶嘗試登錄的時(shí)候,留下的記錄信息雌隅,其中包括用戶的登錄用戶名翻默,ID,IP地址恰起、登錄時(shí)間修械,以及登錄是否成功的狀態(tài)等信息。
我們查看系統(tǒng)數(shù)據(jù)庫(kù)检盼,可以看到對(duì)應(yīng)這兩個(gè)部分的日志表肯污,如下所示。
在ABP框架內(nèi)部基礎(chǔ)項(xiàng)目Abp里面吨枉,我們可以看到對(duì)應(yīng)的領(lǐng)域?qū)ο髮?shí)體和Store管理類蹦渣,不過并沒有在應(yīng)用層的對(duì)應(yīng)服務(wù)和相關(guān)的DTO,我們需要實(shí)現(xiàn)一個(gè)審計(jì)日志和登陸日志的管理功能界面貌亭,界面效果如下所示柬唯。
我們搜索ABP項(xiàng)目,查找到審計(jì)日志的相關(guān)類(包含領(lǐng)域?qū)ο髮?shí)體和Store管理類)圃庭,如下界面截圖锄奢。
同樣對(duì)于系統(tǒng)登錄日志對(duì)象,我們查找到對(duì)應(yīng)的領(lǐng)域?qū)嶓w和對(duì)應(yīng)的Manger業(yè)務(wù)邏輯類剧腻。
這些也就代表它們都有底層的實(shí)現(xiàn)拘央,但是沒有服務(wù)層應(yīng)用和DTO對(duì)象,因此我們需要擴(kuò)展這些內(nèi)容才能夠管理顯示這些記錄信息书在。
前面介紹過灰伟,默認(rèn)的一般應(yīng)用服務(wù)層和接口,都是會(huì)進(jìn)行審計(jì)記錄寫入的儒旬,如果我們需要屏蔽某些應(yīng)用服務(wù)層或者接口栏账,不進(jìn)行審計(jì)信息的記錄,那么需要使用特性標(biāo)記[DisableAuditing]來管理栈源。
如我們針對(duì)審計(jì)日志應(yīng)用層接口的訪問挡爵,我們不想讓它多余的記錄,那么就設(shè)置這個(gè)標(biāo)記即可凉翻。
或者屏蔽某些接口
另外了讨,如果我們不想公布某些特殊的接口訪問捻激,那么我們可以通過標(biāo)記 [RemoteService(false)] 進(jìn)行屏蔽,這樣在Web API層就不會(huì)公布對(duì)應(yīng)的接口了前计。
如對(duì)于審計(jì)日志的記錄胞谭,增刪改我們都不允許客戶端進(jìn)行操作,那么我們把對(duì)應(yīng)的應(yīng)用服務(wù)層接口屏蔽即可男杈。
2丈屹、系統(tǒng)審計(jì)日志和登錄日志的完善
前面介紹了,審計(jì)日志和登陸日志的處理伶棒,Abp系統(tǒng)只是做了一部分底層的內(nèi)容旺垒,我們?nèi)绻M(jìn)行這些信息的管理,我們需要完善它肤无,增加對(duì)應(yīng)的DTO類和應(yīng)用服務(wù)層接口和接口實(shí)現(xiàn)先蒋。
首先我們根據(jù)底層的領(lǐng)域?qū)嶓w對(duì)象的屬性,復(fù)制過來作為對(duì)應(yīng)DTO對(duì)象的屬性宛渐,并增加對(duì)應(yīng)的分頁條件DTO對(duì)象竞漾,由于我們不需要進(jìn)行創(chuàng)建,因此不需要增加Create***Dto對(duì)象類窥翩。
如對(duì)于審計(jì)日志的DTO對(duì)象业岁,我們定義如下所示(主要復(fù)制領(lǐng)域?qū)ο蟮膶傩裕?/p>
而分頁處理的DTO對(duì)象如下所示,我們主要增加一個(gè)用戶名和創(chuàng)建時(shí)間區(qū)間的條件寇蚊。
對(duì)于登錄日志的DTO對(duì)象笔时,我們依葫蘆畫瓢,也是如此操作即可仗岸。
登錄日志的分頁對(duì)象Dto如下所示允耿、
完善了這些DTO對(duì)象,下一步我們需要?jiǎng)?chuàng)建對(duì)應(yīng)的應(yīng)用服務(wù)層類爹梁,這樣我們才能在客戶端通過Web API獲取對(duì)應(yīng)的數(shù)據(jù)右犹。
首先我們來定義審計(jì)日志應(yīng)用服務(wù)類提澎,如下所示姚垃。
[DisableAuditing] //屏蔽這個(gè)AppService的審計(jì)功能
[AbpAuthorize]
public class AuditLogAppService : AsyncCrudAppService<AuditLog, AuditLogDto, long, AuditLogPagedDto>, IAuditLogAppService<AuditLogDto, long, AuditLogPagedDto>
{
private readonly IRepository<AuditLog, long> _repository;
private readonly IAuditingStore _stroe;
private readonly IRepository<User, long> _userRepository;
public AuditLogAppService(IRepository<AuditLog, long> repository, IAuditingStore stroe, IRepository<User, long> userRepository) : base(repository)
{
_repository = repository;
_stroe = stroe;
_userRepository = userRepository;
}
......
其中我們需要IRepository<User, long>用來轉(zhuǎn)義用戶ID為對(duì)應(yīng)的用戶名,這樣對(duì)于我們顯示有幫助盼忌。
默認(rèn)來說积糯,這個(gè)應(yīng)用服務(wù)層已經(jīng)具有常規(guī)的增刪改查、分頁等基礎(chǔ)接口了谦纱,但是我們不需要對(duì)外公布增刪改接口看成,我們需要重寫實(shí)現(xiàn)把它屏蔽。
/// <summary>
/// 屏蔽創(chuàng)建接口
/// </summary>
[RemoteService(false)]
public override Task<AuditLogDto> Create(AuditLogDto input)
{
return base.Create(input);
}
/// <summary>
/// 屏蔽更新接口
/// </summary>
[RemoteService(false)]
public override Task<AuditLogDto> Update(AuditLogDto input)
{
return base.Update(input);
}
/// <summary>
/// 屏蔽刪除接口
/// </summary>
[RemoteService(false)]
public override Task Delete(EntityDto<long> input)
{
return base.Delete(input);
}
那么我們就剩下GetAll和Get兩個(gè)方法了跨嘉,我們?nèi)绻恍枰D(zhuǎn)義特殊內(nèi)容川慌,我們就可以不重寫它,但是我們這里需要對(duì)用戶ID轉(zhuǎn)義為用戶名稱,那么需要進(jìn)行一個(gè)處理梦重,如下所示兑燥。
[DisableAuditing]
public override Task<PagedResultDto<AuditLogDto>> GetAll(AuditLogPagedDto input)
{
var result = base.GetAll(input);
foreach (var item in result.Result.Items)
{
ConvertDto(item);//對(duì)用戶名稱進(jìn)行解析
}
return result;
}
[DisableAuditing]
public override Task<AuditLogDto> Get(EntityDto<long> input)
{
var result = base.Get(input);
ConvertDto(result.Result);
return result;
}
/// <summary>
/// 對(duì)記錄進(jìn)行轉(zhuǎn)義
/// </summary>
/// <param name="item">dto數(shù)據(jù)對(duì)象</param>
/// <returns></returns>
protected virtual void ConvertDto(AuditLogDto item)
{
//用戶名稱轉(zhuǎn)義
if (item.UserId.HasValue)
{
item.UserName = _userRepository.Get(item.UserId.Value).UserName;
}
//IP地址轉(zhuǎn)義
if (!string.IsNullOrEmpty(item.ClientIpAddress))
{
item.ClientIpAddress = item.ClientIpAddress.Replace("::1", "127.0.0.1");
}
}
這里主要就用戶ID和IP地址進(jìn)行一個(gè)正常的轉(zhuǎn)義處理,這個(gè)也是我們常規(guī)接口需要處理的一種常見的情況之一琴拧。
排序我們是以執(zhí)行時(shí)間進(jìn)行排序降瞳,倒序顯示即可,因此重寫排序函數(shù)蚓胸。
/// <summary>
/// 自定義排序處理
/// </summary>
/// <param name="query"></param>
/// <param name="input"></param>
/// <returns></returns>
protected override IQueryable<AuditLog> ApplySorting(IQueryable<AuditLog> query, AuditLogPagedDto input)
{
return base.ApplySorting(query, input).OrderByDescending(s => s.ExecutionTime);//時(shí)間降序
}
一般情況下挣饥,我們就基本完成了這個(gè)模塊的處理了,這樣我們?cè)诮缑嫔显诨c(diǎn)功夫就可以調(diào)用這個(gè)API接口進(jìn)行顯示信息了沛膳,如下界面是我編寫的審計(jì)日志分頁列表顯示界面扔枫。
明細(xì)展示界面如下所示。
上面列表界面管理中锹安,如果我們還能夠以用戶進(jìn)行過濾茧吊,那就更好了,因此需要添加一個(gè)用戶名進(jìn)行過濾(注意不是用戶ID)八毯,系統(tǒng)表里面沒有用戶名稱搓侄。
如果我們需要用戶名稱過濾,如下界面所示话速。
那么我們就需要在應(yīng)用服務(wù)層的過濾函數(shù)里面處理相應(yīng)的規(guī)則了讶踪。
我們先創(chuàng)建一個(gè)審計(jì)日志和用戶信息的集合對(duì)象,如下所示泊交。
/// <summary>
/// 審計(jì)日志和用戶的領(lǐng)域?qū)ο蠹? /// </summary>
public class AuditLogAndUser
{
public AuditLog AuditLog { get;set;}
public User User { get; set; }
}
然后在 CreateFilteredQuery 函數(shù)里面進(jìn)行處理乳讥,如下代碼所示。
/// <summary>
/// 自定義條件處理
/// </summary>
/// <param name="input">分頁查詢Dto對(duì)象</param>
/// <returns></returns>
protected override IQueryable<AuditLog> CreateFilteredQuery(AuditLogPagedDto input)
{
//構(gòu)建關(guān)聯(lián)查詢Query
var query = from auditLog in Repository.GetAll()
join user in _userRepository.GetAll() on auditLog.UserId equals user.Id into userJoin
from joinedUser in userJoin.DefaultIfEmpty()
where auditLog.UserId.HasValue
select new AuditLogAndUser { AuditLog = auditLog, User = joinedUser };
//過濾分頁條件
return query
.WhereIf(!string.IsNullOrEmpty(input.UserName), t => t.User.UserName.Contains(input.UserName))
.WhereIf(input.ExecutionTimeStart.HasValue, s => s.AuditLog.ExecutionTime >= input.ExecutionTimeStart.Value)
.WhereIf(input.ExecutionTimeEnd.HasValue, s => s.AuditLog.ExecutionTime <= input.ExecutionTimeEnd.Value)
.Select(s => s.AuditLog);
}
上面其實(shí)就是先通過EF的關(guān)聯(lián)表查詢廓俭,返回一個(gè)集合記錄云石,然后在判斷用戶名是否在集合里面,最后返回所需的實(shí)體對(duì)象列表研乒。
這個(gè)EF的關(guān)聯(lián)表查詢非常關(guān)鍵汹忠,這個(gè)也是我們聯(lián)合查詢的精髓所在,通過LINQ的方式雹熬,可以很方便實(shí)現(xiàn)關(guān)聯(lián)表的查詢處理并獲得對(duì)應(yīng)的結(jié)果宽菜。
而對(duì)于用戶登錄日志,由于系統(tǒng)記錄了用戶名竿报,那么過濾用戶名铅乡,這不需要這么大費(fèi)周章關(guān)聯(lián)表進(jìn)行處理,只需要判斷數(shù)據(jù)庫(kù)字段對(duì)應(yīng)情況即可烈菌,這種方便很多阵幸。
/// <summary>
/// 自定義條件處理
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
protected override IQueryable<UserLoginAttempt> CreateFilteredQuery(UserLoginAttemptPagedDto input)
{
return base.CreateFilteredQuery(input)
.WhereIf(!string.IsNullOrEmpty(input.UserNameOrEmailAddress), t => t.UserNameOrEmailAddress.Contains(input.UserNameOrEmailAddress))
.WhereIf(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value)
.WhereIf(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value);
}
同樣系統(tǒng)用戶登錄日志界面如下所示花履。
用戶登錄明細(xì)界面效果如下所示。
以上就是對(duì)于審計(jì)日志和用戶登錄日志的擴(kuò)展實(shí)現(xiàn)挚赊,包括了對(duì)相關(guān)DTO的增加和實(shí)現(xiàn)應(yīng)用服務(wù)層接口臭挽,以及對(duì)Web API Caller層的實(shí)現(xiàn)。
/// <summary>
/// 審計(jì)日志的Web API調(diào)用處理
/// </summary>
public class AuditLogApiCaller : AsyncCrudApiCaller<AuditLogDto, long, AuditLogPagedDto>, IAuditLogAppService<AuditLogDto, long, AuditLogPagedDto>
{
/// <summary>
/// 提供單件對(duì)象使用
/// </summary>
public static AuditLogApiCaller Instance
{
get
{
return Singleton<AuditLogApiCaller>.Instance;
}
}
/// <summary>
/// 默認(rèn)構(gòu)造函數(shù)
/// </summary>
public AuditLogApiCaller()
{
this.DomainName = "AuditLog";//指定域?qū)ο竺Q咬腕,用于組裝接口地址
}
}
由于只是部分實(shí)現(xiàn)功能欢峰,我們還是可以基于前面介紹開發(fā)模式(利用代碼生成工具Database2Sharp快速生成)來實(shí)現(xiàn)ABP優(yōu)化框架類文件的生成,以及界面代碼的生成涨共,然后進(jìn)行一定的調(diào)整就是本項(xiàng)目的代碼了纽帖。
代碼生成工具的ABP項(xiàng)目代碼模板,和基于ABPWinform界面代碼的模板举反,是我基于實(shí)際項(xiàng)目的反復(fù)優(yōu)化和驗(yàn)證懊直,并盡量減少冗余代碼而完成的一種快速開發(fā)方式,基于這樣開發(fā)方式可以大大減少項(xiàng)目開發(fā)的難度火鼻,提高開發(fā)效率室囊,并完全匹配整個(gè)框架的需要,是一種非常愜意的快速開發(fā)方式魁索。