Explorar o código

文章分析上线

懒得勤快 %!s(int64=4) %!d(string=hai) anos
pai
achega
5dd8f3c2b4

+ 1 - 1
src/Masuit.MyBlogs.Core/Common/CommonHelper.cs

@@ -221,7 +221,7 @@ namespace Masuit.MyBlogs.Core.Common
                         var network = parts[^1] == "0" ? asn.AutonomousSystemOrganization : parts[^1] + "/" + asn.AutonomousSystemOrganization;
                         parts[0] = parts[0] != "0" ? parts[0] : countryName;
                         parts[3] = parts[3] != "0" ? parts[3] : cityName;
-                        return new IPLocation(parts[0], parts[2], parts[3], network?.TrimEnd('/'), asn.AutonomousSystemNumber)
+                        return new IPLocation(parts[0], parts[2], parts[3], network?.Trim('/'), asn.AutonomousSystemNumber)
                         {
                             Address2 = countryName + cityName,
                             Coodinate = city.Location

+ 1 - 0
src/Masuit.MyBlogs.Core/Common/HttpContextExtension.cs

@@ -36,6 +36,7 @@ namespace Masuit.MyBlogs.Core.Common
                     "baidu.com",
                     "google.com",
                     "googlebot.com",
+                    "googleusercontent.com",
                     "bing.com",
                     "sogou.com",
                     "soso.com",

+ 2 - 2
src/Masuit.MyBlogs.Core/Configs/MappingProfile.cs

@@ -66,8 +66,6 @@ namespace Masuit.MyBlogs.Core.Configs
             CreateMap<Seminar, SeminarDto>().ReverseMap();
             CreateMap<SeminarCommand, SeminarDto>().ReverseMap();
 
-            //CreateMap<SeminarPost, SeminarPostHistoryVersion>().ForMember(s => s.PostHistoryVersionId, e => e.MapFrom(s => s.PostId)).ReverseMap();
-
             CreateMap<PostMergeRequestCommandBase, PostMergeRequest>().ForMember(p => p.Id, e => e.Ignore()).ForMember(p => p.MergeState, e => e.Ignore()).ReverseMap();
             CreateMap<PostMergeRequestCommand, PostMergeRequest>().ForMember(p => p.Id, e => e.Ignore()).ForMember(p => p.MergeState, e => e.Ignore()).ReverseMap();
             CreateMap<PostMergeRequestCommand, Post>().ForMember(p => p.Id, e => e.Ignore()).ForMember(p => p.Status, e => e.Ignore()).ReverseMap();
@@ -80,6 +78,8 @@ namespace Masuit.MyBlogs.Core.Configs
             CreateMap<AdvertisementDto, Advertisement>().ForMember(a => a.Status, e => e.Ignore()).ForMember(a => a.UpdateTime, e => e.MapFrom(a => DateTime.Now));
 
             CreateMap<Donate, DonateDto>();
+
+            CreateMap<PostVisitRecord, PostVisitRecordViewModel>().ForMember(m => m.Time, e => e.MapFrom(m => m.Time.ToString("yyyy-MM-dd HH:mm:ss")));
         }
     }
 }

+ 48 - 0
src/Masuit.MyBlogs.Core/Controllers/PostController.cs

@@ -24,6 +24,7 @@ using Masuit.Tools.Core.Validator;
 using Masuit.Tools.Html;
 using Masuit.Tools.Linq;
 using Masuit.Tools.Logging;
+using Masuit.Tools.Models;
 using Masuit.Tools.Security;
 using Masuit.Tools.Strings;
 using Masuit.Tools.Systems;
@@ -39,6 +40,7 @@ using System.IO;
 using System.Linq;
 using System.Linq.Dynamic.Core;
 using System.Linq.Expressions;
+using System.Net;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading;
@@ -59,6 +61,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         public IWebHostEnvironment HostEnvironment { get; set; }
         public ISearchEngine<DataContext> SearchEngine { get; set; }
         public ImagebedClient ImagebedClient { get; set; }
+        public IPostVisitRecordService PostVisitRecordService { get; set; }
 
         /// <summary>
         /// 文章详情页
@@ -1020,6 +1023,51 @@ namespace Masuit.MyBlogs.Core.Controllers
             });
         }
 
+        /// <summary>
+        /// 文章访问记录
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="page"></param>
+        /// <param name="size"></param>
+        /// <returns></returns>
+        [HttpGet("/{id}/records"), MyAuthorize]
+        [ProducesResponseType(typeof(PagedList<PostVisitRecordViewModel>), (int)HttpStatusCode.OK)]
+        public async Task<IActionResult> PostVisitRecords(int id, int page = 1, int size = 15)
+        {
+            var pages = await PostVisitRecordService.GetPagesAsync<DateTime, PostVisitRecordViewModel>(page, size, e => e.PostId == id, e => e.Time, false);
+            return Ok(pages);
+        }
+
+        /// <summary>
+        /// 文章访问记录图表
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        [HttpGet("/{id}/records-chart"), MyAuthorize]
+        [ProducesResponseType((int)HttpStatusCode.OK)]
+        public async Task<IActionResult> PostVisitRecordChart(int id, CancellationToken cancellationToken)
+        {
+            var list = await PostVisitRecordService.GetQuery(e => e.PostId == id, e => e.Time).Select(e => e.Time).GroupBy(t => t.Date).Select(g => new
+            {
+                Date = g.Key,
+                Count = g.Count()
+            }).ToListAsync(cancellationToken);
+            return Ok(list);
+        }
+
+        /// <summary>
+        /// 文章访问记录分析
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("/{id}/insight"), MyAuthorize]
+        [ProducesResponseType(typeof(PagedList<PostVisitRecordViewModel>), (int)HttpStatusCode.OK)]
+        public IActionResult PostVisitRecordInsight(int id)
+        {
+            return View(PostService[id]);
+        }
+
         #endregion
     }
 }

+ 3 - 3
src/Masuit.MyBlogs.Core/Extensions/Hangfire/HangfireBackJob.cs

@@ -119,6 +119,8 @@ namespace Masuit.MyBlogs.Core.Extensions.Hangfire
         /// <param name="refer"></param>
         public void RecordPostVisit(int pid, string ip, string refer)
         {
+            var time = DateTime.Now.AddMonths(-3);
+            _recordService.GetQuery(b => b.Time < time).DeleteFromQuery();
             var post = _postService.GetById(pid);
             if (post == null)
             {
@@ -126,7 +128,7 @@ namespace Masuit.MyBlogs.Core.Extensions.Hangfire
             }
 
             post.TotalViewCount += 1;
-            post.AverageViewCount = post.TotalViewCount / Math.Ceiling((DateTime.Now - post.PostDate).TotalDays);
+            post.AverageViewCount = _recordService.Count(e => e.PostId == pid) / Math.Ceiling((DateTime.Now - _recordService.GetQuery(r => r.PostId == pid).Select(r => r.Time).DefaultIfEmpty().Min()).TotalDays);
             _recordService.AddEntity(new PostVisitRecord()
             {
                 IP = ip,
@@ -136,8 +138,6 @@ namespace Masuit.MyBlogs.Core.Extensions.Hangfire
                 PostId = pid
             });
             _postService.SaveChanges();
-            var time = DateTime.Now.AddMonths(-3);
-            _recordService.GetQuery(b => b.Time < time).DeleteFromQuery();
         }
 
         /// <summary>

+ 3 - 3
src/Masuit.MyBlogs.Core/Extensions/MiddlewareExtension.cs

@@ -119,7 +119,7 @@ namespace Masuit.MyBlogs.Core.Extensions
             app.UseBundling(bundles =>
             {
                 bundles.AddCss("/main.css")
-                    .Include("/fonts/icomoon.min.css")
+                    .Include("/fonts/icomoon.css")
                     .Include("/Content/jquery.paging.css")
                     .Include("/Content/common/reset.css")
                     .Include("/Content/common/loading.css")
@@ -130,13 +130,13 @@ namespace Masuit.MyBlogs.Core.Extensions
                     .Include("/Assets/nav/css/style.css");
                 bundles.AddCss("/filemanager.css")
                     .Include("/Content/bootswatch.min.css")
-                    .Include("/fonts/icomoon.min.css")
+                    .Include("/fonts/icomoon.css")
                     .Include("/ng-views/filemanager/css/animations.css")
                     .Include("/ng-views/filemanager/css/dialogs.css")
                     .Include("/ng-views/filemanager/css/main.css")
                     .Include("/Content/common/loading.min.css");
                 bundles.AddCss("/dashboard.css")
-                    .Include("/fonts/icomoon.min.css")
+                    .Include("/fonts/icomoon.css")
                     .Include("/Assets/jedate/jedate.css")
                     .Include("/Assets/fileupload/filestyle.css")
                     .Include("/Content/common/loading.min.css")

+ 10 - 0
src/Masuit.MyBlogs.Core/Models/ViewModel/PostVisitRecordViewModel.cs

@@ -0,0 +1,10 @@
+namespace Masuit.MyBlogs.Core.Models.ViewModel
+{
+    public class PostVisitRecordViewModel
+    {
+        public string IP { get; set; }
+        public string Location { get; set; }
+        public string Referer { get; set; }
+        public string Time { get; set; }
+    }
+}

+ 1 - 1
src/Masuit.MyBlogs.Core/Views/Dashboard/FileManager.cshtml

@@ -9,7 +9,7 @@
     <title>资源管理器</title>
     <environment names="Development">
         <link href="~/Content/bootswatch.min.css" rel="stylesheet" />
-        <link href="~/fonts/icomoon.min.css" rel="stylesheet" />
+        <link href="~/fonts/icomoon.css" rel="stylesheet" />
         <link href="~/ng-views/filemanager/css/animations.css" rel="stylesheet">
         <link href="~/ng-views/filemanager/css/dialogs.css" rel="stylesheet">
         <link href="~/ng-views/filemanager/css/main.css" rel="stylesheet">

+ 1 - 1
src/Masuit.MyBlogs.Core/Views/Dashboard/Index.cshtml

@@ -26,7 +26,7 @@
     <link href="https://cdn.staticfile.org/ng-table/1.0.0/ng-table.css" rel="stylesheet" async defer>
     <link href="~/Assets/layui/css/layui.min.css" rel="stylesheet" />
     <environment names="Development">
-        <link href="~/fonts/icomoon.min.css" rel="stylesheet" />
+        <link href="~/fonts/icomoon.css" rel="stylesheet" />
         <link href="~/Assets/jedate/jedate.css" rel="stylesheet" />
         <link href="~/Assets/fileupload/filestyle.css" rel="stylesheet" />
         <link href="~/Content/common/loading.min.css" rel="stylesheet" />

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

@@ -111,6 +111,7 @@
                                             <a asp-controller="Dashboard" asp-action="Index" asp-fragment="/post/[email protected]" class="btn btn-primary" target="_blank">修改</a>
                                             <a asp-controller="Dashboard" asp-action="Index" asp-fragment="/[email protected]" class="btn btn-info" target="_blank">复制</a>
                                             <button class="btn btn-danger" id="del">删除</button>
+                                            <a asp-controller="Post" asp-action="PostVisitRecordInsight" asp-route-id="@Model.Id" class="btn btn-primary" target="_blank">洞察</a>
                                         </div>
                                     </div>
                                 </div>

+ 124 - 0
src/Masuit.MyBlogs.Core/Views/Post/PostVisitRecordInsight.cshtml

@@ -0,0 +1,124 @@
+@model Masuit.MyBlogs.Core.Models.Entity.Post
+
+@{
+    Layout = null;
+}
+
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>文章《@Model.Title》洞察分析</title>
+    <meta content="webkit" name="renderer">
+    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
+    <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+    <link href="/Assets/layui/css/layui.min.css" media="all" rel="stylesheet">
+</head>
+<body style="overflow-x: hidden">
+    <h3 align="center">文章《@Model.Title》洞察分析</h3>
+    <table class="layui-hide" id="table" lay-filter="tableEvent"></table>
+    <div id="chart" style="height: 500px"></div>
+</body>
+</html>
+<script src="/Assets/layui/layui.js"></script>
+<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js" type="text/javascript"></script>
+<script>
+    layui.use('table', function() {
+        var table = layui.table;
+        table.render({
+            elem: '#table',
+            url: '/@Model.Id/records',
+            cellMinWidth: 80, //全局定义常规单元格的最小宽度,layui 2.2.1 新增
+            cols: [
+                [
+                    { field: 'IP', title: 'IP', align: 'center', event: 'tool-ip', fixed: 'left',width:320 },
+                    { field: 'Location', title: '位置和网络', align: 'center'},
+                    { field: 'Referer', title: '页面来源', align: 'center', event: 'visit' },
+                    { field: 'Time', title: '访问时间', align: 'center',width:180 }
+                ]
+            ],
+            page: true,
+            request: {
+                limitName: 'size' //每页数据量的参数名,默认:limit
+            },
+            parseData: function(res) { //res 即为原始返回的数据
+                return {
+                    "code": res.TotalCount > 0 ? 0 : 1, //解析接口状态
+                    "msg": "暂无数据", //解析提示文本
+                    "count": res.TotalCount, //解析数据长度
+                    "data": res.Data //解析数据列表
+                };
+            }
+        });
+        table.on('tool(tableEvent)', function(obj){
+            var data = obj.data;
+            if(obj.event === 'tool-ip'){
+                window.open("/tools/ip/"+data.IP);
+            }
+            if(obj.event === 'visit'){
+                window.open(data.Referer);
+            }
+        });
+    });
+    window.fetch("/@Model.Id/records-chart", {
+        credentials: 'include',
+        method: 'GET',
+        mode: 'cors'
+    }).then(function(response) {
+        return response.json();
+    }).then(function(res) {
+        var data = [];
+        for (let item of res) {
+            data.push([Date.parse(item.Date), item.Count]);
+        }
+        var chartDom = document.getElementById('chart');
+        var myChart = echarts.init(chartDom);
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                position: function(pt) {
+                    return [pt[0], '10%'];
+                }
+            },
+            title: {
+                left: 'center',
+                text: '最近90天访问趋势'
+            },
+            xAxis: {
+                type: 'time',
+                axisLabel: {
+                    formatter:function (value){
+                        var dt=new Date(value);
+                        return dt.toLocaleDateString();
+                    }
+                }
+            },
+            yAxis: {
+                type: 'value'
+            },
+            dataZoom: [
+                {
+                    type: 'inside',
+                    start: 70,
+                    end: 100,
+                    minValueSpan: 100
+                }, {
+                    start: 70,
+                    end: 100,
+                    minValueSpan: 100
+                }
+            ],
+            series: [
+                {
+                    name: '访问量',
+                    type: 'line',
+                    smooth: true,
+                    symbol: 'none',
+                    areaStyle: {},
+                    data: data
+                }
+            ]
+        };
+        myChart.setOption(option);
+    });
+</script>

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

@@ -32,7 +32,7 @@
     <link href="https://cdn.staticfile.org/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
     <link href="~/Assets/layui/css/layui.min.css" rel="stylesheet" async defer>
     <environment names="Development">
-        <link href="~/fonts/icomoon.min.css" rel="stylesheet" />
+        <link href="~/fonts/icomoon.css" rel="stylesheet" />
         <link href="~/Content/jquery.paging.css" rel="stylesheet" />
         <link href="~/Content/common/reset.css" rel="stylesheet" />
         <link href="~/Content/common/loading.css" rel="stylesheet" />
@@ -51,7 +51,7 @@
     <script src="https://cdn.staticfile.org/limonte-sweetalert2/6.6.9/sweetalert2.min.js" async defer></script>
     <script src="https://cdn.staticfile.org/notie/4.3.1/notie.min.js" async defer></script>
     <script src="https://cdn.staticfile.org/jquery.form/4.2.2/jquery.form.min.js" async defer></script>
-    <script src="https://cdn.staticfile.org/jquery-mobile/1.4.5/jquery.mobile.min.js"></script>
+    <script src="https://cdn.staticfile.org/jqueryui/1.9.2/jquery.ui.widget.min.js"></script>
     <script src="https://cdn.staticfile.org/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
     <script src="~/Scripts/layer/layer.js"></script>
     <script src="~/Assets/layui/layui.js"></script>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/Masuit.MyBlogs.Core/wwwroot/fonts/icomoon.min.css


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

@@ -254,6 +254,15 @@
 			});
 		});
     }
+	self.insight= function(row) {
+        layer.full(layer.open({
+          type: 2,
+          title: '文章《'+row.Title+'》洞察分析',
+          maxmin: true, //开启最大化最小化按钮
+          area: ['893px', '100vh'],
+          content: '/'+row.Id+'/insight'
+        }));
+    }
 }]);
 myApp.controller("writeblog", ["$scope", "$http", "$timeout","$location", function ($scope, $http, $timeout,$location) {
 	clearInterval(window.interval);

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

@@ -112,7 +112,7 @@
                     <span class="el-switch-style" ng-click="lockedSwitch(row.Id)"></span>
                 </label>
             </td>
-            <td data-title="'操作'" style="width: 160px;">
+            <td data-title="'操作'" style="width: 195px;">
                 <div class="btn-group">
                     <button class="btn btn-default btn-sm waves-effect" ng-click="list.pass(row)" ng-if="row.Status=='审核中'">
                         <i class="icon-checkmark"></i>
@@ -135,6 +135,9 @@
                     <a class="btn  btn-sm waves-effect" ng-class="{true:'btn-success',false:'btn-primary'}[row.IsFixedTop]" ng-click="list.fixtop(row.Id)" ng-if="row.Status!='已删除'">
                         <i class="icon-pushpin" ng-class="{true:'text-danger'}[row.IsFixedTop]"></i>
                     </a>
+                    <a class="btn btn-info btn-sm waves-effect" ng-click="list.insight(row)">
+                        <i class="icon-chart"></i>
+                    </a>
                 </div>
             </td>
         </tr>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio