黄贺彬 5 年 前
コミット
c455b0afff

+ 11 - 0
HiTeachCE/Context/FreeSQLContext.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace HiTeachCE.Context
+{
+    public class FreeSQLContext
+    {
+    }
+}

+ 115 - 0
HiTeachCE/Context/FreeSQLExtension.cs

@@ -0,0 +1,115 @@
+using AspNetCoreRateLimit;
+using FreeSql;
+using FreeSql.Internal;
+using HiTeachCE.Helpers;
+using HiTeachCE.Models;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Serilog;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using ToolGood.Words;
+
+namespace HiTeachCE.Context
+{
+    public static   class FreeSQLExtension
+    {
+        
+        /// <summary>
+        /// FreeSql
+        /// </summary>
+        /// <param name="services"></param>
+        public static void AddContext(this IServiceCollection services, IConfiguration configuration)
+        {
+            IConfigurationSection configurationSection = configuration.GetSection("ConnectionStrings:MySql");
+            IFreeSql fsql = new FreeSqlBuilder()
+                   .UseConnectionString(DataType.MySql, configurationSection.Value)
+                   .UseNameConvert(NameConvertType.PascalCaseToUnderscoreWithLower)
+                   .UseAutoSyncStructure(true)
+                   .UseMonitorCommand(cmd =>
+                   {
+                       Trace.WriteLine(cmd.CommandText + ";");
+                   }
+                   )
+                   .Build()
+                   .SetDbContextOptions(opt => opt.EnableAddOrUpdateNavigateList = true);//联级保存功能开启(默认为关闭)
+
+
+
+            fsql.Aop.CurdAfter += (s, e) =>
+            {
+                Log.Debug($"ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}: FullName:{e.EntityType.FullName}" +
+                          $" ElapsedMilliseconds:{e.ElapsedMilliseconds}ms, {e.Sql}");
+
+                if (e.ElapsedMilliseconds > 200)
+                {
+                    //记录日志
+                    //发送短信给负责人
+                }
+            };
+
+            //敏感词处理
+            if (configuration["AuditValue:Enable"].ToBoolean())
+            {
+                IllegalWordsSearch illegalWords = ToolGoodUtils.GetIllegalWordsSearch();
+
+                fsql.Aop.AuditValue += (s, e) =>
+                {
+                    if (e.Column.CsType == typeof(string) && e.Value != null)
+                    {
+                        string oldVal = (string)e.Value;
+                        string newVal = illegalWords.Replace(oldVal);
+                        //第二种处理敏感词的方式
+                        //string newVal = oldVal.ReplaceStopWords();
+                        if (newVal != oldVal)
+                        {
+                            e.Value = newVal;
+                        }
+                    }
+                };
+            }
+
+            services.AddSingleton(fsql);
+            services.AddScoped<UnitOfWorkManager>();
+            fsql.GlobalFilter.Apply<IDeleteAduitEntity>("IsDeleted", a => a.IsDeleted == false);
+            //在运行时直接生成表结构
+            fsql.CodeFirst.SyncStructure(ReflexHelper.GetEntityTypes(typeof(IEntity)));
+            services.AddFreeRepository();
+        }
+        public static IServiceCollection AddFreeRepository(this IServiceCollection services)
+        {
+            services.TryAddScoped(typeof(IBaseRepository<>), typeof(GuidRepository<>));
+            services.TryAddScoped(typeof(BaseRepository<>), typeof(GuidRepository<>));
+            services.TryAddScoped(typeof(IBaseRepository<,>), typeof(DefaultRepository<,>));
+            services.TryAddScoped(typeof(BaseRepository<,>), typeof(DefaultRepository<,>));
+            return services;
+        }
+        /// <summary>
+         /// 配置限流依赖的服务
+         /// </summary>
+         /// <param name="services"></param>
+         /// <param name="configuration"></param>
+         /// <returns></returns>
+        public static IServiceCollection AddIpRateLimiting(this IServiceCollection services, IConfiguration configuration)
+        {
+            //加载配置
+            services.AddOptions();
+            //从IpRateLimiting.json获取相应配置
+            services.Configure<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));
+            services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));
+            //注入计数器和规则存储
+            services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
+            services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
+
+            //配置(计数器密钥生成器)
+            services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
+
+            return services;
+        }
+    }
+}

+ 1 - 0
HiTeachCE/Extension/Jwt/JwtAuth.cs

@@ -71,6 +71,7 @@ namespace HiTeachCE.Extension
                     },
                     //URL未授权调用
                     OnChallenge = context => {
+
                         return Task.CompletedTask;
                     },
                     //在Token验证通过后调用

+ 37 - 0
HiTeachCE/Helpers/ReflexHelper.cs

@@ -0,0 +1,37 @@
+using FreeSql.DataAnnotations;
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace HiTeachCE.Helpers
+{
+    /// <summary>
+    /// 反射帮助类
+    /// </summary>
+    public class ReflexHelper
+    {
+
+        /// <summary>
+        /// 扫描 IEntity类所在程序集,反射得到所有类上有特性标签为TableAttribute
+        /// </summary>
+        /// <returns></returns>
+        public static Type[] GetEntityTypes(Type type)
+        {
+            List<Type> tableAssembies = new List<Type>();
+            Assembly.GetAssembly(type).GetExportedTypes().ForEach(o =>
+            {
+                foreach (Attribute attribute in o.GetCustomAttributes())
+                {
+                    if (attribute is TableAttribute linCmsAuthorize)
+                    {
+                        tableAssembies.Add(o);
+                    }
+                }
+            });
+            return tableAssembies.ToArray();
+        }
+    }
+}

+ 94 - 0
HiTeachCE/Helpers/ToolGoodUtils.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ToolGood.Words;
+namespace HiTeachCE.Helpers
+{
+    /// <summary>
+    /// ToolGood.Words类库配合敏感库
+    /// </summary>
+    public class ToolGoodUtils
+    {
+        //敏感库只要这二个文件存在即可
+        //本地敏感库缓存- https://github.com/toolgood/ToolGood.Words/tree/master/csharp/ToolGood.Words.Test/_Illegal
+        //因为需要上传至github并同步gitee,安全起见,解压压缩包wwwroot目录下的_Illegal.zip
+        private const string KeywordsPath = "wwwroot/_Illegal/IllegalKeywords.txt";
+        private const string UrlsPath = "wwwroot/_Illegal/IllegalUrls.txt";
+
+        //更多敏感词汇   https://github.com/fighting41love/funNLP/tree/master/data/%E6%95%8F%E6%84%9F%E8%AF%8D%E5%BA%93
+
+        private const string InfoPath = "wwwroot/_Illegal/IllegalInfo.txt";
+        private const string BitPath = "wwwroot/_Illegal/IllegalBit.iws";
+
+        private static IllegalWordsSearch _search;
+        /// <summary>
+        /// 本地敏感库,文件修改后,重新创建缓存Bit
+        /// </summary>
+        /// <returns></returns>
+        public static IllegalWordsSearch GetIllegalWordsSearch()
+        {
+            if (!File.Exists(UrlsPath) || !File.Exists(KeywordsPath))
+            {
+                return new IllegalWordsSearch();
+            }
+
+            if (_search == null)
+            {
+                string ipath = Path.GetFullPath(InfoPath);
+                if (File.Exists(ipath) == false)
+                {
+                    _search = CreateIllegalWordsSearch();
+                }
+                else
+                {
+                    var texts = File.ReadAllText(ipath).Split('|');
+                    if (new FileInfo(Path.GetFullPath(KeywordsPath)).LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") !=
+                        texts[0] ||
+                        new FileInfo(Path.GetFullPath(UrlsPath)).LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") !=
+                        texts[1]
+                    )
+                    {
+                        _search = CreateIllegalWordsSearch();
+                    }
+                    else
+                    {
+                        var s = new IllegalWordsSearch();
+                        s.Load(Path.GetFullPath(BitPath));
+                        _search = s;
+                    }
+                }
+            }
+            return _search;
+        }
+
+        private static IllegalWordsSearch CreateIllegalWordsSearch()
+        {
+            string[] words1 = File.ReadAllLines(Path.GetFullPath(KeywordsPath), Encoding.UTF8);
+            string[] words2 = File.ReadAllLines(Path.GetFullPath(UrlsPath), Encoding.UTF8);
+            var words = new List<string>();
+            foreach (var item in words1)
+            {
+                words.Add(item.Trim());
+            }
+            foreach (var item in words2)
+            {
+                words.Add(item.Trim());
+            }
+
+            var search = new IllegalWordsSearch();
+            search.SetKeywords(words);
+
+            search.Save(Path.GetFullPath(BitPath));
+
+            var text = new FileInfo(Path.GetFullPath(KeywordsPath)).LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") + "|"
+                                                                                                                  + new FileInfo(Path.GetFullPath(UrlsPath)).LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss");
+            File.WriteAllText(Path.GetFullPath(InfoPath), text);
+
+            return search;
+        }
+
+    }
+}

+ 6 - 0
HiTeachCE/HiTeachCE.csproj

@@ -4,15 +4,21 @@
     <TargetFramework>netcoreapp3.1</TargetFramework>
   </PropertyGroup>
   <ItemGroup>
+    <PackageReference Include="AspNetCoreRateLimit" Version="3.0.5" />
     <PackageReference Include="BCrypt.Net-Core" Version="1.6.0" />
+    <PackageReference Include="FreeSql" Version="1.5.0" />
+    <PackageReference Include="FreeSql.DbContext" Version="1.5.0" />
     <PackageReference Include="Hei.Captcha" Version="0.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.2.0" />
     <PackageReference Include="MQTTnet.AspNetCore" Version="3.0.11" />
     <PackageReference Include="MQTTnet.Extensions.WebSocket4Net" Version="3.0.11" />
     <PackageReference Include="MySql.Data" Version="8.0.20" />
+    <PackageReference Include="Serilog" Version="2.9.0" />
     <PackageReference Include="sqlSugarCore" Version="5.0.0.10" />
     <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
     <PackageReference Include="TEAMModelOS.SDK" Version="3.0.520" />
+    <PackageReference Include="ToolGood.Words" Version="3.0.1.2" />
+    <PackageReference Include="Z.ExtensionMethods" Version="2.1.1" />
   </ItemGroup>
   <ItemGroup>
     <Folder Include="Dtos\" />

+ 1 - 1
HiTeachCE/Models/ActivationCode.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 namespace HiTeachCE.Models
 {
     [SugarTable("ActivationCode")]
-    public class ActivationCode :Entity
+    public class ActivationCode : Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get; set; }

+ 1 - 1
HiTeachCE/Models/Classroom.cs

@@ -10,7 +10,7 @@ namespace HiTeachCE.Models
     /// 只用于保存记录/不能用于上课使用
     /// </summary>
     [SugarTable("Classroom")]
-    public class Classroom :Entity
+    public class Classroom : Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get; set; }

+ 151 - 0
HiTeachCE/Models/FullAduitEntity.cs

@@ -0,0 +1,151 @@
+using FreeSql.DataAnnotations;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace HiTeachCE.Models
+{
+
+    public interface IEntityDto
+    {
+    }
+
+    public interface IEntityDto<TKey> : IEntityDto
+    {
+        TKey Id { get; set; }
+    }
+
+    public abstract class EntityDto<TKey> : IEntityDto<TKey>
+    {
+        /// <summary>
+        /// 主键Id
+        /// </summary>
+        public TKey Id { get; set; }
+    }
+
+    public abstract class EntityDto : EntityDto<long>
+    {
+    }
+
+    [Serializable]
+    public class FullAduitEntity : FullAduitEntity<long>
+    {
+    }
+
+    public class FullAduitEntity<T> : Entity<T>, IUpdateAuditEntity, IDeleteAduitEntity, ICreateAduitEntity
+    {
+        /// <summary>
+        /// 创建者ID
+        /// </summary>
+        public long CreateUserId { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime CreateTime { get; set; }
+
+        /// <summary>
+        /// 是否删除
+        /// </summary>
+        public bool IsDeleted { get; set; }
+
+        /// <summary>
+        /// 删除人id
+        /// </summary>
+        public long? DeleteUserId { get; set; }
+
+        /// <summary>
+        /// 删除时间
+        /// </summary>
+        public DateTime? DeleteTime { get; set; }
+
+        /// <summary>
+        /// 最后修改人Id
+        /// </summary>
+        public long? UpdateUserId { get; set; }
+
+        /// <summary>
+        /// 修改时间
+        /// </summary>
+        public DateTime UpdateTime { get; set; }
+    }
+
+
+    public interface ICreateAduitEntity
+    {
+        /// <summary>
+        /// 创建者ID
+        /// </summary>
+        [Column(Position = -7)]
+        long CreateUserId { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        [Column(Position = -6)]
+        DateTime CreateTime { get; set; }
+    }
+
+    public interface IUpdateAuditEntity
+    {
+        /// <summary>
+        /// 最后修改人Id
+        /// </summary>
+        [Column(Position = -5)]
+        long? UpdateUserId { get; set; }
+
+        /// <summary>
+        /// 修改时间
+        /// </summary>
+        [Column(Position = -4)]
+        DateTime UpdateTime { get; set; }
+    }
+
+    public interface IDeleteAduitEntity
+    {
+        /// <summary>
+        /// 是否删除
+        /// </summary>
+        [Column(Position = -3)]
+        bool IsDeleted { get; set; }
+
+        /// <summary>
+        /// 删除人id
+        /// </summary>
+        [Column(Position = -2)]
+        long? DeleteUserId { get; set; }
+
+        /// <summary>
+        /// 删除时间
+        /// </summary>
+        [Column(Position = -1)]
+        DateTime? DeleteTime { get; set; }
+    }
+
+    public abstract class Entity<T> : IEntity<T>
+    {
+        /// <summary>
+        /// 主键Id
+        /// </summary>
+        [Column(IsPrimary = true, IsIdentity = true, Position = 1)]
+        public T Id { get; set; }
+    }
+
+    [Serializable]
+    public abstract class Entity : Entity<long>
+    {
+    }
+
+    public interface IEntity<T>
+    {
+        /// <summary>
+        /// 主键Id
+        /// </summary>
+        T Id { get; set; }
+    }
+
+    public interface IEntity : IEntity<long>
+    {
+    }
+}

+ 1 - 1
HiTeachCE/Models/Learner.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 namespace HiTeachCE.Models
 {
     [SugarTable("Learner")]
-    public class Learner:Entity
+    public class Learner:Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get; set; }

+ 1 - 1
HiTeachCE/Models/Lecturer.cs

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
 namespace HiTeachCE.Models
 {
     [SugarTable("Lecturer")]
-    public class Lecturer:Entity
+    public class Lecturer: Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get; set; }

+ 1 - 1
HiTeachCE/Models/Member.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 namespace HiTeachCE.Models
 {
     [SugarTable("Member")]
-    public class Member : Entity
+    public class Member : Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get ; set ; }

+ 1 - 1
HiTeachCE/Models/Entity.cs

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
 
 namespace HiTeachCE.Models
 {
-     public interface Entity
+     public interface Model
     {
         public string id { get; set; }
     }

+ 1 - 1
HiTeachCE/Models/Organization.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 namespace HiTeachCE.Models
 {
     [SugarTable("Organization")]
-    public class Organization :Entity
+    public class Organization : Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get; set; }

+ 1 - 1
HiTeachCE/Models/Subscriber.cs

@@ -10,7 +10,7 @@ namespace HiTeachCE.Models
     /// 订阅器
     /// </summary>
     [SugarTable("Subscriber")]
-    public class Subscriber :Entity
+    public class Subscriber : Model
     {
         [SugarColumn(IsNullable = false, IsPrimaryKey = true)]
         public string id { get; set; }

+ 41 - 0
HiTeachCE/RateLimitConfig.json

@@ -0,0 +1,41 @@
+{
+  "IpRateLimiting": {
+    //false则全局将应用限制,并且仅应用具有作为端点的规则* 。 true则限制将应用于每个端点,如{HTTP_Verb}{PATH}
+    "EnableEndpointRateLimiting": true,
+    //false则拒绝的API调用不会添加到调用次数计数器上
+    "StackBlockedRequests": false,
+    "RealIpHeader": "X-Real-IP",
+    "ClientIdHeader": "X-ClientId",
+    "HttpStatusCode": 200,
+    "QuotaExceededResponse": {
+      "Content": "{{\"code\":10140,\"message\":\"访问过于频繁,请稍后重试\",\"data\":null}}",
+      "ContentType": "application/json",
+      "StatusCode": 429
+    },
+    "IpWhitelist": [],
+    "EndpointWhitelist": [],
+    "ClientWhitelist": [],
+    "GeneralRules": [
+      {
+        "Endpoint": "*",
+        // "Endpoint": "*:/cms/test",
+        "Period": "1s",
+        "Limit": 10
+      }
+    ]
+  },
+  "IpRateLimitPolicies": {
+    "IpRules": [
+      {
+        "Ip": "192.168.2.136",
+        "Rules": [
+          {
+            "Endpoint": "*",
+            "Period": "1s",
+            "Limit": 10
+          }
+        ]
+      }
+    ]
+  }
+}