瀏覽代碼

文章统计

懒得勤快 4 年之前
父節點
當前提交
eb9a998bf1

+ 47 - 3
src/Masuit.MyBlogs.Core/Controllers/PostController.cs

@@ -1,4 +1,5 @@
-using CacheManager.Core;
+using AngleSharp.Text;
+using CacheManager.Core;
 using Hangfire;
 using Masuit.LuceneEFCore.SearchEngine.Interfaces;
 using Masuit.MyBlogs.Core.Common;
@@ -29,6 +30,7 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Net.Http.Headers;
 using System;
+using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq.Dynamic.Core;
@@ -36,6 +38,7 @@ using System.Linq.Expressions;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
+using Z.EntityFramework.Plus;
 using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
 
 namespace Masuit.MyBlogs.Core.Controllers
@@ -651,7 +654,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         /// <returns></returns>
         [MyAuthorize]
-        public ActionResult GetPageData([FromServices] ICacheManager<uint> cacheManager, [Range(1, int.MaxValue, ErrorMessage = "页数必须大于0")] int page = 1, [Range(1, int.MaxValue, ErrorMessage = "页大小必须大于0")] int size = 10, OrderBy orderby = OrderBy.ModifyDate, string kw = "", int? cid = null)
+        public ActionResult GetPageData([FromServices] ICacheManager<HashSet<string>> cacheManager, [Range(1, int.MaxValue, ErrorMessage = "页数必须大于0")] int page = 1, [Range(1, int.MaxValue, ErrorMessage = "页大小必须大于0")] int size = 10, OrderBy orderby = OrderBy.ModifyDate, string kw = "", int? cid = null)
         {
             Expression<Func<Post, bool>> where = p => true;
             if (cid.HasValue)
@@ -669,7 +672,7 @@ namespace Masuit.MyBlogs.Core.Controllers
             {
                 item.ModifyDate = item.ModifyDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
                 item.PostDate = item.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
-                item.Online = cacheManager.Get(nameof(PostOnline) + ":" + item.Id);
+                item.Online = cacheManager.GetOrAdd(nameof(PostOnline) + ":" + item.Id, new HashSet<string>()).Count;
             }
 
             return Ok(list);
@@ -986,6 +989,47 @@ namespace Masuit.MyBlogs.Core.Controllers
             }) > 0;
             return b ? ResultData(null, true, "操作成功!") : ResultData(null, false, "操作失败!");
         }
+
+        /// <summary>
+        /// 文章统计
+        /// </summary>
+        /// <returns></returns>
+        [MyAuthorize]
+        public async Task<IActionResult> Statistic()
+        {
+            var keys = await RedisHelper.KeysAsync(nameof(PostOnline) + ":*");
+            var sets = await keys.SelectAsync(async s => (Id: s.Split(':')[1].ToInt32(), Clients: await RedisHelper.HGetAsync<HashSet<string>>(s, "value")));
+            var ids = sets.Where(t => t.Clients.Count > 0).OrderByDescending(t => t.Clients.Count).Take(10).Select(t => t.Id).ToArray();
+            var mostHots = await PostService.GetQuery<PostModelBase>(p => ids.Contains(p.Id)).FromCacheAsync().ContinueWith(t =>
+            {
+                foreach (var item in t.Result)
+                {
+                    item.ViewCount = sets.FirstOrDefault(t => t.Id == item.Id).Clients.Count;
+                }
+
+                return t.Result.OrderByDescending(p => p.ViewCount);
+            });
+            var postsQuery = PostService.GetQuery(p => p.Status == Status.Published);
+            var mostView = await postsQuery.OrderByDescending(p => p.TotalViewCount).Take(10).Select(p => new PostModelBase()
+            {
+                Id = p.Id,
+                Title = p.Title,
+                ViewCount = p.TotalViewCount
+            }).FromCacheAsync();
+            var mostAverage = await postsQuery.OrderByDescending(p => p.AverageViewCount).Take(10).Select(p => new PostModelBase()
+            {
+                Id = p.Id,
+                Title = p.Title,
+                ViewCount = (int)p.AverageViewCount
+            }).FromCacheAsync();
+            return ResultData(new
+            {
+                mostHots,
+                mostView,
+                mostAverage
+            });
+        }
+
         #endregion
     }
 }

+ 21 - 21
src/Masuit.MyBlogs.Core/Infrastructure/Repository/BaseRepository.cs

@@ -25,7 +25,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         public virtual DataContext DataContext { get; set; }
 
         public MapperConfiguration MapperConfig { get; set; }
-        public static MemoryCacheEntryOptions CacheOtions => new MemoryCacheEntryOptions()
+        public static MemoryCacheEntryOptions CacheOptions => new MemoryCacheEntryOptions()
         {
             AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1)
         };
@@ -54,7 +54,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<T> GetAllFromCache()
         {
-            return DataContext.Set<T>().FromCache(CacheOtions);
+            return DataContext.Set<T>().FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -63,7 +63,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<T>> GetAllFromCacheAsync()
         {
-            return DataContext.Set<T>().FromCacheAsync(CacheOtions);
+            return DataContext.Set<T>().FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -83,7 +83,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<TDto> GetAllFromCache<TDto>() where TDto : class
         {
-            return DataContext.Set<T>().AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCache(CacheOtions);
+            return DataContext.Set<T>().AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -93,7 +93,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<TDto>> GetAllFromCacheAsync<TDto>() where TDto : class
         {
-            return DataContext.Set<T>().AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOtions);
+            return DataContext.Set<T>().AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -129,7 +129,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<T> GetAllFromCache<TS>(Expression<Func<T, TS>> orderby, bool isAsc = true)
         {
-            return GetAll(orderby, isAsc).FromCache(CacheOtions);
+            return GetAll(orderby, isAsc).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -141,7 +141,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<T>> GetAllFromCacheAsync<TS>(Expression<Func<T, TS>> @orderby, bool isAsc = true)
         {
-            return GetAll(orderby, isAsc).FromCacheAsync(CacheOtions);
+            return GetAll(orderby, isAsc).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -167,7 +167,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<TDto> GetAllFromCache<TS, TDto>(Expression<Func<T, TS>> orderby, bool isAsc = true) where TDto : class
         {
-            return GetAllNoTracking(orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCache(CacheOtions);
+            return GetAllNoTracking(orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -180,7 +180,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<TDto>> GetAllFromCacheAsync<TS, TDto>(Expression<Func<T, TS>> @orderby, bool isAsc = true) where TDto : class
         {
-            return GetAllNoTracking(orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOtions);
+            return GetAllNoTracking(orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -213,7 +213,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<T> GetQueryFromCache(Expression<Func<T, bool>> where)
         {
-            return DataContext.Set<T>().Where(where).FromCache(CacheOtions);
+            return DataContext.Set<T>().Where(where).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -223,7 +223,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<T>> GetQueryFromCacheAsync(Expression<Func<T, bool>> @where)
         {
-            return DataContext.Set<T>().Where(where).FromCacheAsync(CacheOtions);
+            return DataContext.Set<T>().Where(where).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -236,7 +236,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<T> GetQueryFromCache<TS>(Expression<Func<T, bool>> where, Expression<Func<T, TS>> orderby, bool isAsc = true)
         {
-            return GetQuery(where, orderby, isAsc).FromCache(CacheOtions);
+            return GetQuery(where, orderby, isAsc).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -249,7 +249,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<T>> GetQueryFromCacheAsync<TS>(Expression<Func<T, bool>> @where, Expression<Func<T, TS>> @orderby, bool isAsc = true)
         {
-            return GetQuery(where, orderby, isAsc).FromCacheAsync(CacheOtions);
+            return GetQuery(where, orderby, isAsc).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -263,7 +263,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<TDto>> GetQueryFromCacheAsync<TS, TDto>(Expression<Func<T, bool>> @where, Expression<Func<T, TS>> @orderby, bool isAsc = true) where TDto : class
         {
-            return GetQueryNoTracking(where, orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOtions);
+            return GetQueryNoTracking(where, orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -320,7 +320,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>实体集合</returns>
         public virtual IEnumerable<TDto> GetQueryFromCache<TDto>(Expression<Func<T, bool>> where) where TDto : class
         {
-            return DataContext.Set<T>().Where(where).AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCache(CacheOtions);
+            return DataContext.Set<T>().Where(where).AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -330,7 +330,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public Task<IEnumerable<TDto>> GetQueryFromCacheAsync<TDto>(Expression<Func<T, bool>> @where) where TDto : class
         {
-            return DataContext.Set<T>().Where(where).AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOtions);
+            return DataContext.Set<T>().Where(where).AsNoTracking().ProjectTo<TDto>(MapperConfig).FromCacheAsync(CacheOptions);
         }
 
         /// <summary>
@@ -344,7 +344,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public virtual IEnumerable<TDto> GetQueryFromCache<TS, TDto>(Expression<Func<T, bool>> where, Expression<Func<T, TS>> orderby, bool isAsc = true) where TDto : class
         {
-            return GetQueryNoTracking(where, orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCache(CacheOtions);
+            return GetQueryNoTracking(where, orderby, isAsc).ProjectTo<TDto>(MapperConfig).FromCache(CacheOptions);
         }
 
         /// <summary>
@@ -925,7 +925,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
                 page = 1;
             }
 
-            var list = query.Skip(size * (page - 1)).Take(size).FromCache(BaseRepository<T>.CacheOtions).ToList();
+            var list = query.Skip(size * (page - 1)).Take(size).FromCache(BaseRepository<T>.CacheOptions).ToList();
             return new PagedList<T>(list, page, size, totalCount);
         }
 
@@ -950,7 +950,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
                 page = 1;
             }
 
-            var list = await query.Skip(size * (page - 1)).Take(size).FromCacheAsync(BaseRepository<T>.CacheOtions);
+            var list = await query.Skip(size * (page - 1)).Take(size).FromCacheAsync(BaseRepository<T>.CacheOptions);
             return new PagedList<T>(list.ToList(), page, size, totalCount);
         }
 
@@ -1031,7 +1031,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
                 page = 1;
             }
 
-            var list = query.Skip(size * (page - 1)).Take(size).ProjectTo<TDto>(mapper).FromCache(BaseRepository<T>.CacheOtions).ToList();
+            var list = query.Skip(size * (page - 1)).Take(size).ProjectTo<TDto>(mapper).FromCache(BaseRepository<T>.CacheOptions).ToList();
             return new PagedList<TDto>(list, page, size, totalCount);
         }
 
@@ -1058,7 +1058,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
                 page = 1;
             }
 
-            var list = await query.Skip(size * (page - 1)).Take(size).ProjectTo<TDto>(mapper).FromCacheAsync(BaseRepository<T>.CacheOtions);
+            var list = await query.Skip(size * (page - 1)).Take(size).ProjectTo<TDto>(mapper).FromCacheAsync(BaseRepository<T>.CacheOptions);
             return new PagedList<TDto>(list.ToList(), page, size, totalCount);
         }
     }

+ 1 - 1
src/Masuit.MyBlogs.Core/Infrastructure/Repository/MenuRepository.cs

@@ -42,7 +42,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
         /// <returns>还未执行的SQL语句</returns>
         public override IEnumerable<Menu> GetQueryFromCache(Expression<Func<Menu, bool>> @where)
         {
-            return DataContext.Menu.Include(m => m.Children).ThenInclude(m => m.Children).ThenInclude(m => m.Children).Where(where).FromCache(CacheOtions);
+            return DataContext.Menu.Include(m => m.Children).ThenInclude(m => m.Children).ThenInclude(m => m.Children).Where(where).FromCache(CacheOptions);
         }
     }
 }

+ 1 - 6
src/Masuit.MyBlogs.Core/Models/ViewModel/PostDataModel.cs

@@ -14,11 +14,6 @@ namespace Masuit.MyBlogs.Core.Models.ViewModel
         /// </summary>
         public string Author { get; set; }
 
-        /// <summary>
-        /// 浏览次数
-        /// </summary>
-        public int ViewCount { get; set; }
-
         /// <summary>
         /// 发表时间
         /// </summary>
@@ -74,6 +69,6 @@ namespace Masuit.MyBlogs.Core.Models.ViewModel
         /// <summary>
         /// 同时浏览人数
         /// </summary>
-        public uint Online { get; set; }
+        public int Online { get; set; }
     }
 }

+ 6 - 0
src/Masuit.MyBlogs.Core/Models/ViewModel/PostModelBase.cs

@@ -11,5 +11,11 @@
         /// 标题
         /// </summary>
         public string Title { get; set; }
+
+        /// <summary>
+        /// 浏览次数
+        /// </summary>
+        public int ViewCount { get; set; }
+
     }
 }

+ 1 - 1
src/Masuit.MyBlogs.Core/Views/Post/Details.cshtml

@@ -85,7 +85,7 @@
                                 <div class="col-md-12 line-height24">
                                     分类:<i class="icon-map-pin"></i>
                                     <a asp-controller="Home" asp-action="Category" asp-route-id="@Model.CategoryId" class="label label-info">@Model.Category.Name</a>
-                                    | 评论总数:<span class="text-danger">@Model.Comment.Count</span>条 | 热度:<span class="text-danger">@Model.TotalViewCount</span>℃ | @(await Html.RenderComponentAsync<PostOnline>(RenderMode.ServerPrerendered, new { Model.Id }))
+                                    | 评论总数:<span class="text-danger">@Model.Comment.Count</span>条 | 热度:<span class="text-danger">@Model.TotalViewCount</span>℃ | @(await Html.RenderComponentAsync<PostOnline>(RenderMode.ServerPrerendered, new{ Model.Id,IP=Context.Connection.RemoteIpAddress.ToString()}))
                                     @if (Model.Seminar.Any())
                                     {
                                         <span> | 所属专题:</span>

+ 17 - 19
src/Masuit.MyBlogs.Core/Views/Post/PostOnline.razor

@@ -1,46 +1,44 @@
 @using CacheManager.Core
 @using System.Threading
+@using Masuit.MyBlogs.Core.Common
 @implements IAsyncDisposable
-@inject ICacheManager<uint> CacheManager
+@inject ICacheManager<HashSet<string>> CacheManager
 
 <span class="text-red">@online</span>人正在浏览本文
 
 @code {
     [Parameter]
     public int Id { get; set; }
-    uint online;
+
+    [Parameter]
+    public string IP { get; set; }
+
+    int online;
     Timer _timer;
 
     protected override void OnInitialized()
     {
         var key = nameof(PostOnline) + ":" + Id;
-        online = CacheManager.AddOrUpdate(key, 1u, i => i + 1, 3);
+        online = CacheManager.AddOrUpdate(key, new HashSet<string>(), set =>
+        {
+            set.Add(IP);
+            return set;
+        }, 3).Count;
         CacheManager.Expire(key, ExpirationMode.Sliding, TimeSpan.FromMinutes(5));
         _timer = new Timer(_ =>
         {
-            online = CacheManager.Get(key);
+            online = CacheManager.Get(key).Count;
             InvokeAsync(StateHasChanged);
         }, null, 0, 1000);
     }
 
-    /// <summary>以异步方式执行与释放或重置非托管资源相关的应用程序定义的任务。</summary>
-    /// <returns>一个表示异步释放操作的任务。</returns>
     public ValueTask DisposeAsync()
     {
-        online = CacheManager.AddOrUpdate(nameof(PostOnline) + ":" + Id, 1u, i =>
+        online = CacheManager.AddOrUpdate(nameof(PostOnline) + ":" + Id, new HashSet<string>(), set =>
         {
-            try
-            {
-                checked
-                {
-                    return i - 1;
-                }
-            }
-            catch
-            {
-                return 0;
-            }
-        });
+            set.Remove(IP);
+            return set;
+        }).Count;
         return _timer.DisposeAsync();
     }
 }

+ 2 - 2
src/Masuit.MyBlogs.Core/Views/Shared/_ArticleListItem.cshtml

@@ -3,10 +3,10 @@
 @using CacheManager.Core
 @using Masuit.MyBlogs.Core.Views.Post
 @model Masuit.MyBlogs.Core.Models.DTO.PostDto
-@inject ICacheManager<uint> CacheManager
+@inject ICacheManager<HashSet<string>> CacheManager
 @{
     string[] colors = { "success", "info", "primary", "warning", "danger", "default", "primary" };
-    var online = CacheManager[nameof(PostOnline) + ":" + Model.Id];
+    var online = CacheManager.GetOrAdd(nameof(PostOnline) + ":" + Model.Id, new HashSet<string>()).Count;
 }
 
 <div class="ibox wow fadeIn">

+ 2 - 2
src/Masuit.MyBlogs.Core/Views/Shared/_ArticleListItem_Admin.cshtml

@@ -3,11 +3,11 @@
 @using Masuit.Tools.Systems
 @using CacheManager.Core
 @using Masuit.MyBlogs.Core.Views.Post
-@inject ICacheManager<uint> CacheManager
+@inject ICacheManager<HashSet<string>> CacheManager
 @model Masuit.MyBlogs.Core.Models.DTO.PostDto
 @{
     string[] colors = { "success", "info", "primary", "warning", "danger", "default", "primary" };
-    var online = CacheManager[nameof(PostOnline) + ":" + Model.Id];
+    var online = CacheManager.GetOrAdd(nameof(PostOnline) + ":" + Model.Id, new HashSet<string>()).Count;
 }
 
 <div class="ibox wow fadeIn">

+ 11 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.js

@@ -24,6 +24,17 @@
 			self.GetPageData($scope.paginationConf.currentPage, $scope.paginationConf.itemsPerPage);
 		}
 	});
+	$http.post("/post/Statistic").then(function(res) {
+		if(res.data.Success) {
+			$scope.agg = res.data.Data;
+		} else {
+			window.notie.alert({
+				type:3,
+				text:res.data.Message,
+				time:4
+			});
+		}
+	});
 
 	$http.post("/category/getcategories", null).then(function (res) {
 		var data = res.data;

+ 66 - 1
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/post/postlist.html

@@ -1,4 +1,4 @@
-<div>
+<div class="row">
     <div class="form-inline pull-right">
         <button class="btn btn-info waves-effect" ng-click="list.GetPageData(paginationConf.currentPage, paginationConf.itemsPerPage);">
             <span class="glyphicon glyphicon-refresh"></span>
@@ -115,4 +115,69 @@
         </tr>
     </table>
     <tm-pagination conf="paginationConf"></tm-pagination>
+</div>
+<div class="row">
+    <div class="col-md-4" ng-if="agg.mostHots.length>0">
+        <div class="page-header">
+            <h3 class="text-center">当前在看排行榜</h3>
+        </div>
+        <table class="table table-bordered table-condensed table-responsive">
+            <thead>
+                <tr>
+                    <th style="min-width: 40px">序号</th>
+                    <th>标题</th>
+                    <th style="min-width: 80px">在看人数</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr ng-repeat="item in agg.mostHots">
+                    <td>{{$index+1}}</td>
+                    <td><a ng-href="/{{item.Id}}" target="_blank">{{item.Title}}</a></td>
+                    <td>{{item.ViewCount}}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+    <div class="col-md-4" ng-if="agg.mostView.length>0">
+        <div class="page-header">
+            <h3 class="text-center">访问量最高排行榜</h3>
+        </div>
+        <table class="table table-bordered table-condensed table-responsive">
+            <thead>
+                <tr>
+                    <th style="min-width: 40px">序号</th>
+                    <th>标题</th>
+                    <th style="min-width: 60px">访问量</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr ng-repeat="item in agg.mostView">
+                    <td>{{$index+1}}</td>
+                    <td><a ng-href="/{{item.Id}}" target="_blank">{{item.Title}}</a></td>
+                    <td>{{item.ViewCount}}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+    <div class="col-md-4" ng-if="agg.mostAverage.length>0">
+        <div class="page-header">
+            <h3 class="text-center">日均访问量排行榜</h3>
+        </div>
+        <table class="table table-bordered table-condensed table-responsive">
+            <thead>
+                <tr>
+                    <th style="min-width: 40px">序号</th>
+                    <th>标题</th>
+                    <th style="min-width: 60px">访问量</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr ng-repeat="item in agg.mostAverage">
+                    <td>{{$index+1}}</td>
+                    <td><a ng-href="/{{item.Id}}" target="_blank">{{item.Title}}</a></td>
+                    <td>{{item.ViewCount}}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
 </div>