├── Doc ├── diInst.png ├── netLog.png ├── packT4.png ├── sunny.png ├── packApi.png ├── commentXml.png ├── myDbContext.png ├── offlineLog.png ├── pageRequest.png ├── swaggerXml.png ├── T4Repository.png ├── pageResponse.png └── packRepository.png ├── UseDemo ├── ApiDemo │ ├── appsettings.Development.json │ ├── DTO │ │ ├── Request │ │ │ └── Demo │ │ │ │ ├── MemPua.cs │ │ │ │ ├── Enummm.cs │ │ │ │ ├── CustomerValidator.cs │ │ │ │ ├── Buyer.cs │ │ │ │ └── Customer.cs │ │ └── Response │ │ │ └── ResponseMapperConfig.cs │ ├── Job │ │ ├── JobA.cs │ │ └── JobB.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Program.cs │ ├── ApiDemo.csproj │ ├── Controllers │ │ └── UnAuthController.cs │ ├── appsettings.json │ ├── ApiDemo.xml │ └── Startup.cs ├── ServiceDemo │ ├── ServiceDemo.csproj │ ├── IStudentService.cs │ └── StudentService.cs ├── RepositoryDemo │ ├── RepositoryDemo.csproj │ ├── DbModel │ │ ├── Model │ │ │ ├── IdTest.cs │ │ │ ├── Category.cs │ │ │ ├── PassageCategory.cs │ │ │ ├── Passage.cs │ │ │ ├── StudentAddress.cs │ │ │ └── Student.cs │ │ ├── RelationMap │ │ │ ├── StudentAddressMap.cs │ │ │ └── PassageCategoryMap.cs │ │ └── MoelConfig │ │ │ ├── StudentConfig.cs │ │ │ ├── CategoryConfig.cs │ │ │ ├── PassageConfig.cs │ │ │ └── StudentAddressConfig.cs │ ├── DbSet.cs │ ├── MyDbContext.cs │ ├── DesignTimeDbContextFactory.cs │ └── Migrations │ │ ├── 20181102012925_v1.cs │ │ ├── MyDbContextModelSnapshot.cs │ │ └── 20181102012925_v1.Designer.cs └── TemplateT4Demo │ ├── TemplateT4Demo.csproj │ └── Program.cs ├── Framework ├── Sunny.Repository │ ├── DbModel │ │ ├── IDbModel.cs │ │ ├── IRelationMap.cs │ │ └── BaseModel.cs │ ├── Sunny.Repository.xml │ ├── FluentApiTools.cs │ └── Sunny.Repository.csproj ├── Sunny.Common │ ├── ConfigOption │ │ ├── NetLoggerOption.cs │ │ ├── RedisOption.cs │ │ ├── SnowflakeOption.cs │ │ ├── IpInfoQueryOption.cs │ │ ├── SmsOption.cs │ │ ├── TokenValidateOption.cs │ │ ├── MailOption.cs │ │ └── JobOption.cs │ ├── Log │ │ ├── LogData.cs │ │ ├── NetLoggerProvider.cs │ │ ├── NetLoggerProcessor.cs │ │ └── NetLogger.cs │ ├── Extend │ │ ├── IDispose │ │ │ └── IDisposeExtend.cs │ │ ├── DateTime │ │ │ └── DateTimeExtend.cs │ │ ├── CollectionQuery │ │ │ ├── PageQuery.cs │ │ │ ├── PageInfo.cs │ │ │ ├── PageData.cs │ │ │ └── CollectionQuery.cs │ │ ├── Exception │ │ │ └── BizException.cs │ │ ├── GenericType │ │ │ ├── ListExtend.cs │ │ │ └── ClassTypeExtend.cs │ │ ├── Object │ │ │ └── ObjectExtend.cs │ │ ├── Enum │ │ │ └── EnumExtend.cs │ │ ├── Log │ │ │ └── LoggerFactoryExtend.cs │ │ └── Cache │ │ │ └── IDistributedCacheExtend.cs │ ├── Properties │ │ └── PublishProfiles │ │ │ └── FolderProfile.pubxml │ ├── DependencyInjection │ │ └── DenendencyInjectionInterface.cs │ ├── Enum │ │ └── Enums.cs │ ├── Helper │ │ ├── Id │ │ │ └── IdHelper.cs │ │ ├── String │ │ │ ├── JsonHelper.cs │ │ │ └── StringHelper.cs │ │ ├── Serialize │ │ │ └── SerializeHelper.cs │ │ ├── Calc │ │ │ └── CalcHelper.cs │ │ ├── File │ │ │ └── FileHelper.cs │ │ ├── Security │ │ │ └── SecurityHelper.cs │ │ ├── Xml │ │ │ └── XMLHelper.cs │ │ └── Di │ │ │ └── DIHelper.cs │ ├── JsonTypeConverter │ │ ├── LongConverter.cs │ │ └── DecimalConverter.cs │ ├── Sunny.Common.csproj │ └── Id │ │ └── SnowFlake.cs ├── Sunny.Api │ ├── FluentValidation │ │ └── Validator.cs │ ├── DTO │ │ ├── Request │ │ │ └── PageInfoValidator.cs │ │ └── Response │ │ │ └── Result.cs │ ├── Quartz │ │ ├── IJobEntity.cs │ │ └── JobWrapper.cs │ ├── Controllers │ │ └── SunnyController.cs │ ├── Properties │ │ └── PublishProfiles │ │ │ └── FolderProfile.pubxml │ ├── Extend │ │ ├── SunnyControllerExtend.cs │ │ └── IApplicationBuilderExtend.cs │ ├── Midware │ │ ├── TokenValidateMiddleware.cs │ │ ├── BizExceptionHandlerMiddleware.cs │ │ └── ExceptionHandlingMiddleware.cs │ ├── Sunny.Api.csproj │ └── Sunny.Api.xml └── TemplateT4 │ ├── FluentApiConfig │ ├── DbModelConfig.tt │ ├── RelationMapConfig.tt │ ├── FluentApiConfigProcesser.cs │ ├── RelationMapConfigPartial.cs │ └── DbModelConfigPartial.cs │ └── Sunny.TemplateT4.csproj ├── .gitignore └── Sunny.sln /Doc/diInst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/diInst.png -------------------------------------------------------------------------------- /Doc/netLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/netLog.png -------------------------------------------------------------------------------- /Doc/packT4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/packT4.png -------------------------------------------------------------------------------- /Doc/sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/sunny.png -------------------------------------------------------------------------------- /Doc/packApi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/packApi.png -------------------------------------------------------------------------------- /Doc/commentXml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/commentXml.png -------------------------------------------------------------------------------- /Doc/myDbContext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/myDbContext.png -------------------------------------------------------------------------------- /Doc/offlineLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/offlineLog.png -------------------------------------------------------------------------------- /Doc/pageRequest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/pageRequest.png -------------------------------------------------------------------------------- /Doc/swaggerXml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/swaggerXml.png -------------------------------------------------------------------------------- /Doc/T4Repository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/T4Repository.png -------------------------------------------------------------------------------- /Doc/pageResponse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/pageResponse.png -------------------------------------------------------------------------------- /Doc/packRepository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackYang/Sunny/HEAD/Doc/packRepository.png -------------------------------------------------------------------------------- /UseDemo/ApiDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UseDemo/ServiceDemo/ServiceDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Framework/Sunny.Repository/DbModel/IDbModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Repository.DbModel 6 | { 7 | /// 8 | /// 用于标识某类是DbModel,继承此接口后可通过T4模板生成ModelConfig类 9 | /// 10 | public interface IDbModel 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/NetLoggerOption.cs: -------------------------------------------------------------------------------- 1 | namespace Sunny.Common.ConfigOption 2 | { 3 | public class NetLoggerOption 4 | { 5 | public string Url { get; set; } 6 | public string SystemId { get; set; } 7 | public string OfflineLogPath { get; set; } = @"C:\SunnyFramework\OfflineLog"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/RepositoryDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Framework/Sunny.Repository/DbModel/IRelationMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Repository.DbModel 6 | { 7 | /// 8 | /// 用于标识用于映射多对多的类,继承此接口后可通过T4模板生成RelationMap类 9 | /// 10 | public interface IRelationMap:IDbModel 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/Model/IdTest.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Common.Enum; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace RepositoryDemo.DbModel 7 | { 8 | public class IdTest 9 | { 10 | public long Id { get; set; } 11 | 12 | public Enums.RequestType requestType { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Log/LogData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.Log 6 | { 7 | public class LogData 8 | { 9 | public string Message { get; set; } 10 | 11 | public string LevelString { get; set; } 12 | 13 | public Exception Exception { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/DTO/Request/Demo/MemPua.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ApiDemo.FluentValidation2 8 | { 9 | public class MemPua 10 | { 11 | [Required] 12 | public string Name { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/RedisOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.ConfigOption 6 | { 7 | public class RedisOption 8 | { 9 | public string ConnectionString { get; set; } 10 | public string InstanceName { get; set; } 11 | 12 | public int DefaultSlidingExpiration { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/Model/Category.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace RepositoryDemo.DbModel 7 | { 8 | public class Category:BaseModel 9 | { 10 | 11 | public string CategoryName { get; set; } 12 | 13 | public IList PassageCategories { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/DTO/Request/Demo/Enummm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ApiDemo.DTO.Request 8 | { 9 | public class Enummm 10 | { 11 | 12 | public enum IntEnum { 13 | 14 | Wifi, 15 | [Description("这是一个很NB的GPS哦")] 16 | GPS 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/SnowflakeOption.cs: -------------------------------------------------------------------------------- 1 | namespace Sunny.Common.ConfigOption 2 | { 3 | public class SnowflakeOption 4 | { 5 | 6 | /// 7 | /// 数据中心ID,1到31之间 8 | /// 9 | public long DatacenterId { get; set; } 10 | 11 | /// 12 | /// 机器ID,1到31之间 13 | /// 14 | public long MachineId { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/IDispose/IDisposeExtend.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace System 6 | { 7 | public static class IDisposeExtend 8 | { 9 | public static void DisposeIfNotNull(this IDisposable disObj) 10 | { 11 | if (disObj != null) 12 | { 13 | disObj.Dispose(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UseDemo/TemplateT4Demo/TemplateT4Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/IpInfoQueryOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.ConfigOption 6 | { 7 | /// 8 | /// 查询IP信息的配置 9 | /// 10 | public class IpInfoQueryOption 11 | { 12 | /// 13 | /// IP查询的API 14 | /// 15 | public string ApiUrl { get; set; } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/FluentValidation/Validator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Sunny.Common.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Sunny.Api.FluentValidation 9 | { 10 | /// 11 | /// 需要验证的实体请继承自这个类 12 | /// 13 | /// 14 | public abstract class Validator: AbstractValidator, ISingleton 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/Model/PassageCategory.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace RepositoryDemo.DbModel 7 | { 8 | public class PassageCategory : IRelationMap 9 | { 10 | public long CategoryId { get; set; } 11 | 12 | public Category Category { get; set; } 13 | 14 | public long PassageId { get; set; } 15 | 16 | public Passage Passage { get; set; } 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/SmsOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.ConfigOption 6 | { 7 | public class SmsOption 8 | { 9 | /// 10 | /// 短信网关的API地址 11 | /// 12 | public string ApiUrl { get; set; } 13 | /// 14 | /// IP白名单列表,在列表中的IP发短信前不执行检查事件 15 | /// 16 | public string[] IPWhiteList { get; set; } 17 | 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UseDemo/TemplateT4Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using Sunny.TemplateT4.FluentApiConfig; 2 | using System; 3 | 4 | namespace TemplateT4Demo 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | string assemblyName = "RepositoryDemo"; 11 | string configNameSpace = "RepositoryDemo.DbModel.ModelConfig"; 12 | 13 | FluentApiConfigProcesser.GenerateFiles(assemblyName,configNameSpace); 14 | 15 | Console.Read(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/TokenValidateOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.ConfigOption 6 | { 7 | public class TokenValidateOption 8 | { 9 | /// 10 | /// 根据TokenKey的值从HttpHeader中获取对应的字符串,默认是"token" 11 | /// 12 | public string TokenKey { get; set; } = "token"; 13 | 14 | /// 15 | /// 以此开头的API都需要验证Token 16 | /// 17 | public string AuthApiStartWith { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/DateTime/DateTimeExtend.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace System 6 | { 7 | public static class DateTimeExtend 8 | { 9 | /// 10 | /// 返回yyyy-MM-dd HH:mm:ss 格式的时间 11 | /// 12 | /// 13 | /// 14 | public static string ToNormalString(this DateTime dateTime) 15 | { 16 | return dateTime.ToString("yyyy-MM-dd HH:mm:ss"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | Any CPU 10 | netcoreapp2.1 11 | bin\Debug\netcoreapp2.1\publish\ 12 | 13 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbSet.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using RepositoryDemo.DbModel; 3 | 4 | namespace RepositoryDemo 5 | { 6 | 7 | public partial class MyDbContext 8 | { 9 | public DbSet Category { get; set; } 10 | public DbSet Passage { get; set; } 11 | public DbSet PassageCategory { get; set; } 12 | 13 | 14 | public DbSet Student { get; set; } 15 | 16 | public DbSet StudentAddress { get; set; } 17 | 18 | public DbSet IdTest { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/Model/Passage.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace RepositoryDemo.DbModel 7 | { 8 | public class Passage : BaseModel 9 | { 10 | //文章编号 11 | 12 | 13 | 14 | //标题 15 | public string Title { get; set; } 16 | 17 | 18 | //最后编辑时间 19 | public DateTime LastEditTime { get; set; } 20 | 21 | public bool IsDelete { get; set; } 22 | //文章分类(使用技术等) 23 | public IList PassageCategories { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/CollectionQuery/PageQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.Extend.CollectionQuery 6 | { 7 | /// 8 | /// 带分页的查询条件 9 | /// 10 | /// 11 | public class PageQuery 12 | { 13 | /// 14 | /// 查询条件 15 | /// 16 | public T Condition { get; set; } 17 | 18 | /// 19 | /// 分页信息 20 | /// 21 | public PageInfo PageInfo { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Framework/Sunny.Repository/DbModel/BaseModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text; 6 | 7 | namespace Sunny.Repository.DbModel 8 | { 9 | public class BaseModel: IDbModel 10 | { 11 | 12 | public long Id { get; set; } 13 | 14 | public DateTime CreateTime { get; set; } 15 | 16 | public long CreaterId { get; set; } 17 | 18 | public DateTime UpdateTime { get; set; } 19 | 20 | public long UpdaterId { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /UseDemo/ServiceDemo/IStudentService.cs: -------------------------------------------------------------------------------- 1 | using RepositoryDemo.DbModel; 2 | using Sunny.Common.DependencyInjection; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace ServiceDemo 7 | { 8 | public interface IStudentServic:IScoped 9 | { 10 | 11 | Task GetStudent(); 12 | 13 | Task GetStudent2(); 14 | 15 | Task BizExceptionTest(); 16 | } 17 | 18 | 19 | public class SomeoneClass : IScoped 20 | { 21 | 22 | public string SomeoneMethod() 23 | { 24 | return "hello this is di class"; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/RelationMap/StudentAddressMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo.DbModel.RelationMap 8 | { 9 | public class StudentAddressMap : IEntityTypeConfiguration 10 | { 11 | public void Configure(EntityTypeBuilder builder) 12 | { 13 | builder.HasOne(x => x.Student).WithOne(s => s.Address).HasForeignKey(x => x.StudentId); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/DTO/Request/PageInfoValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Sunny.Api.FluentValidation; 3 | using Sunny.Common.Extend.CollectionQuery; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace Sunny.Api.DTO.Request 9 | { 10 | public class PageInfoValidator : Validator 11 | { 12 | public PageInfoValidator() 13 | { 14 | RuleFor(x => x.PageIndex).GreaterThanOrEqualTo(1).WithMessage("PageIndex必须大于0"); 15 | RuleFor(x => x.PageSize).GreaterThanOrEqualTo(1).WithMessage("PageSize必须大于0"); 16 | } 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/Job/JobA.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using Sunny.Api.Quartz; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace ApiDemo.Job 8 | { 9 | public class JobA : IJobEntity 10 | { 11 | 12 | public string JobName => "名字随便取,你能看懂就行,最终会在日志中出现"; 13 | 14 | public string Describe => "这个Job是干嘛用的..最终会在日志中出现"; 15 | 16 | public async Task ExecuteAsync(IJobExecutionContext jobContext) 17 | { 18 | Console.WriteLine("hello job 这有中文 execing..."); 19 | await Task.Run(() => Thread.Sleep(3000)); 20 | Console.WriteLine("job end"); 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | *.log 10 | *.ide 11 | *.cache 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the 31 | 32 | /UseDemo/RepositoryDemo/Migrations 33 | /UseDemo/ApiDemo/Properties/PublishProfiles/FolderProfile.pubxml 34 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/Exception/BizException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Text; 5 | 6 | namespace System 7 | { 8 | /// 9 | /// 用于表示业务异常的类 10 | /// 11 | public class BizException : Exception 12 | { 13 | public BizException(string message) : base(message) 14 | { 15 | } 16 | 17 | public BizException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | 21 | protected BizException(SerializationInfo info, StreamingContext context) : base(info, context) 22 | { 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/DependencyInjection/DenendencyInjectionInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.DependencyInjection 6 | { 7 | /// 8 | /// 用于标记需要批量注入的接口 9 | /// 10 | public interface IDependency 11 | { 12 | } 13 | /// 14 | /// 每次使用时都实例化一次 15 | /// 16 | public interface ITransient : IDependency { } 17 | 18 | /// 19 | /// 每个请求中只实例化一次 20 | /// 21 | public interface IScoped : IDependency { } 22 | 23 | /// 24 | /// 整个系统中只实例化一次 25 | /// 26 | public interface ISingleton : IDependency { } 27 | } 28 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/Model/StudentAddress.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo.DbModel 8 | { 9 | public class StudentAddress : BaseModel 10 | { 11 | 12 | // public int StudentAddressId { get; set; } 13 | 14 | public string Address1 { get; set; } 15 | 16 | public int Zipcode { get; set; } 17 | public string State { get; set; } 18 | public string Country { get; set; } 19 | 20 | public Student Student { get; set; } 21 | 22 | public long StudentId { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/MyDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Sunny.Repository; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo 8 | { 9 | public partial class MyDbContext : DbContext 10 | { 11 | public MyDbContext(DbContextOptions options) 12 | : base(options) 13 | { 14 | 15 | } 16 | 17 | protected override void OnModelCreating(ModelBuilder modelBuilder) 18 | { 19 | base.OnModelCreating(modelBuilder); 20 | FluentApiTools.ApplyDbModelFluentApiConfig(modelBuilder, "RepositoryDemo"); 21 | } 22 | } 23 | 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/Model/Student.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace RepositoryDemo.DbModel 7 | { 8 | public class Student : BaseModel 9 | { 10 | public Student() { } 11 | 12 | 13 | public string StudentName { get; set; } 14 | 15 | public string Test { get; set; } 16 | 17 | private string AA2 { get; set; } 18 | 19 | private new DateTime UpdateTime { get; set; } 20 | 21 | private new long UpdaterId { get; set; } 22 | 23 | public StudentAddress Address { get; set; } 24 | 25 | public decimal Score { get; set; } 26 | 27 | 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Quartz/IJobEntity.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using Sunny.Common.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Sunny.Api.Quartz 9 | { 10 | /// 11 | /// 任务信息 12 | /// 13 | public interface IJobEntity:ITransient 14 | { 15 | /// 16 | /// 任务的名称 17 | /// 18 | string JobName { get; } 19 | /// 20 | /// 任务的描述 21 | /// 22 | string Describe { get; } 23 | 24 | /// 25 | /// 任务内容 26 | /// 27 | Task ExecuteAsync(IJobExecutionContext jobContext); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/MailOption.cs: -------------------------------------------------------------------------------- 1 | namespace Sunny.Common.ConfigOption 2 | { 3 | /// 4 | /// 邮件配置信息 5 | /// 6 | public class MailOption 7 | { 8 | /// 9 | /// 邮件服务器地址 10 | /// 11 | public string EmailHost { get; set; } 12 | /// 13 | /// 用户名 14 | /// 15 | public string EmailUserName { get; set; } 16 | /// 17 | /// 密码 18 | /// 19 | public string EmailPassword { get; set; } 20 | /// 21 | /// IP白名单列表,在列表中的IP发邮件前不执行检查事件 22 | /// 23 | public string[] IPWhiteList { get; set; } 24 | 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/GenericType/ListExtend.cs: -------------------------------------------------------------------------------- 1 | namespace System.Collections.Generic 2 | { 3 | static public class ListExtend 4 | { 5 | /// 6 | /// 对集合中的项进行扩展,返回一个dynamic集合 7 | /// 8 | /// 9 | /// 10 | /// 如ToDynamic(x=>{x.Extend(new{EnumCn=x.EnumX.GetDescribe()})}) 11 | /// 12 | static public List ToDynamic(this List source, Func func) 13 | { 14 | List list = new List(); 15 | source.ForEach(x => list.Add(func.Invoke(x))); 16 | return list; 17 | 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/Object/ObjectExtend.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | public static class ObjectExtend 4 | { 5 | /// 6 | /// 将objValue的值转换成defValue的类型,如果转换失败,返回defValue 7 | /// 8 | /// 目标类型 9 | /// 要转换的原数据 10 | /// 转换失败时返回的默认值 11 | /// 12 | static public T ConvertTo(this object objValue, T defValue) 13 | { 14 | try 15 | { 16 | return (T)Convert.ChangeType(objValue, typeof(T)); 17 | } 18 | catch 19 | { 20 | return defValue; 21 | } 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Controllers/SunnyController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Options; 3 | using Sunny.Api.DTO.Response; 4 | using Sunny.Common.ConfigOption; 5 | 6 | namespace Sunny.Api.Controllers 7 | { 8 | 9 | [ApiController] 10 | public class SunnyController : Controller 11 | { 12 | 13 | /// 14 | /// 返回一个动态类型的值,通常用于想返回扩展类型的场景,比如在源对象上增加一个枚举值描述的动态属性 15 | /// 16 | /// 17 | /// 18 | protected Result SuccessDynamic(dynamic responseData) 19 | { 20 | Result r = new Result(); 21 | r.Data = responseData; 22 | 23 | return r; 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Enum/Enums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Text; 5 | 6 | namespace Sunny.Common.Enum 7 | { 8 | public class Enums 9 | { 10 | #region 操作状态,表示本次操作成功失败或异常 11 | 12 | public enum OperationStatus 13 | { 14 | [Description("操作异常")] 15 | Exception = -1, 16 | 17 | 18 | [Description("操作成功")] 19 | Success = 0, 20 | 21 | 22 | [Description("操作失败")] 23 | Fail = 1 24 | 25 | } 26 | #endregion 27 | 28 | public enum RequestType 29 | { 30 | [Description("Get请求")] 31 | Get, 32 | [Description("Post请求")] 33 | Post 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/ConfigOption/JobOption.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sunny.Common.ConfigOption 4 | { 5 | /// 6 | /// 任务的配置项 7 | /// 8 | public class JobOption 9 | { 10 | /// 11 | /// 任务类的名称 12 | /// 13 | public string JobClassName { get; set; } 14 | 15 | /// 16 | ///任务所属的组名称,同一组类不能有2个相同的任务 17 | /// 18 | public string JobGroup { get; set; } 19 | 20 | /// 21 | /// 在什么时候运行,用Cron表达式 22 | /// 23 | public string RunAtCron { get; set; } 24 | 25 | /// 26 | ///任务的参数 27 | /// 28 | public IDictionary Args { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/Enum/EnumExtend.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | 5 | namespace System 6 | { 7 | static public class EnumExtend 8 | { 9 | 10 | public static string GetDescribe(this T t) where T : Enum 11 | { 12 | string describe = t.ToString(); 13 | 14 | MemberInfo[] memInfo = typeof(T).GetMember(t.ToString()); 15 | 16 | 17 | if (memInfo != null && memInfo.Length > 0) 18 | { 19 | object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); 20 | if (attrs != null && attrs.Length > 0) 21 | describe = ((DescriptionAttribute)attrs[0]).Description; 22 | } 23 | 24 | return describe; 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/TemplateT4/FluentApiConfig/DbModelConfig.tt: -------------------------------------------------------------------------------- 1 | <#@ template language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Text; 11 | 12 | namespace <#=ConfigNamespace#> 13 | { 14 | public class <#=DbModelType.Name#>Config : IEntityTypeConfiguration<<#=DbModelType.Name#>> 15 | { 16 | 17 | public void Configure(EntityTypeBuilder<<#=DbModelType.Name#>> builder) 18 | { 19 | <#=GetTableConfig() #> 20 | <#=GetFieldsConfig() #> 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DesignTimeDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Design; 3 | using Microsoft.Extensions.Logging; 4 | using RepositoryDemo; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace Sunny.Repository 10 | { 11 | public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory 12 | 13 | { 14 | 15 | public MyDbContext CreateDbContext(string[] args) 16 | 17 | { 18 | 19 | var builder = new DbContextOptionsBuilder(); 20 | 21 | 22 | builder.UseMySql("server=localhost;database=test;user=root;password=youPassword;"); 23 | 24 | return new MyDbContext(builder.Options); 25 | 26 | 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Framework/TemplateT4/FluentApiConfig/RelationMapConfig.tt: -------------------------------------------------------------------------------- 1 | <#@ template language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Text; 11 | 12 | namespace <#=ConfigNamespace#> 13 | { 14 | public class <#=DbModelType.Name#>Map : IEntityTypeConfiguration<<#=DbModelType.Name#>> 15 | { 16 | 17 | public void Configure(EntityTypeBuilder<<#=DbModelType.Name#>> builder) 18 | { 19 | builder.ToTable("<#=DbModelType.Name.UpperCharToUnderLine()#>"); 20 | <#=GetRelationConfig()#> 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/DTO/Response/ResponseMapperConfig.cs: -------------------------------------------------------------------------------- 1 | using ApiDemo.DTO.Request.Demo; 2 | using ApiDemo.FluentValidation2; 3 | using AutoMapper; 4 | using RepositoryDemo.DbModel; 5 | 6 | namespace ApiDemo.DTO.Response 7 | { 8 | public class ResponseMapperConfig : Profile 9 | { 10 | public ResponseMapperConfig() 11 | { 12 | 13 | CreateMap() 14 | .ForMember(cus => cus.LocalType, opt => opt.MapFrom(id => id.requestType)).ReverseMap(); 15 | //手动指定字段映射关系 16 | //.ForMember(cus => cus.LocalType, opt => opt.MapFrom(id => id.requestType)) 17 | //.ReverseMap()映射反向转换 18 | 19 | CreateMap().ReverseMap(); 20 | CreateMap().ReverseMap(); 21 | CreateMap().ReverseMap(); 22 | } 23 | 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /UseDemo/ServiceDemo/StudentService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using RepositoryDemo; 5 | using RepositoryDemo.DbModel; 6 | 7 | namespace ServiceDemo 8 | { 9 | public class StudentService : IStudentServic 10 | { 11 | MyDbContext db; 12 | public StudentService(MyDbContext db) 13 | { 14 | this.db = db; 15 | } 16 | 17 | public async Task GetStudent() 18 | { 19 | return await Task.Run(()=>db.Student.FirstOrDefault()); 20 | } 21 | 22 | public async Task GetStudent2() 23 | { 24 | return await Task.Run(() => db.Student.FirstOrDefault()); 25 | } 26 | 27 | public async Task BizExceptionTest() 28 | { 29 | throw new BizException("订单不存在,这是一个测试抛出的业务异常"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/Job/JobB.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using ServiceDemo; 3 | using Sunny.Api.Quartz; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace ApiDemo.Job 10 | { 11 | public class JobB : IJobEntity 12 | { 13 | 14 | public string JobName => "this job B Name"; 15 | 16 | public string Describe => "this job B Describe"; 17 | 18 | IStudentServic studentServic; 19 | 20 | public JobB(IStudentServic studentServic) 21 | { 22 | this.studentServic = studentServic; 23 | 24 | } 25 | 26 | 27 | public async Task ExecuteAsync(IJobExecutionContext jobContext) 28 | { 29 | Console.WriteLine(jobContext.JobDetail.JobDataMap["pxxx"]);//使用了配置中传来的参数,参数的名称要和配置里的一样 30 | Console.WriteLine( (await studentServic.GetStudent()).StudentName); 31 | 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:60960", 8 | "sslPort": 44381 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "unauthapi/unauth", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "ApiDemo": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "unauthapi/unauth", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/Id/IdHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Sunny.Common.ConfigOption; 3 | using Sunny.Common.Id; 4 | 5 | namespace Sunny.Common.Helper 6 | { 7 | public class IdHelper 8 | { 9 | private static Snowflake snowflake = null; 10 | private IdHelper() 11 | { 12 | 13 | } 14 | 15 | static IdHelper() 16 | { 17 | 18 | } 19 | 20 | /// 21 | /// 程序启动时调用一次就OK 22 | /// 23 | public static void InitSnowflake(SnowflakeOption options) 24 | { 25 | snowflake = new Snowflake(options.MachineId, options.DatacenterId); 26 | } 27 | 28 | /// 29 | /// 获取ID 30 | /// 31 | /// 32 | public static long GenId() 33 | { 34 | return snowflake.NextId(); 35 | 36 | } 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FileSystem 9 | FileSystem 10 | Release 11 | Any CPU 12 | 13 | True 14 | False 15 | 913ae651-2048-4be1-9199-63c8ab3b8299 16 | bin\Release\netcoreapp2.1\publish\ 17 | False 18 | 19 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/JsonTypeConverter/LongConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Sunny.Common.JsonTypeConverter 5 | { 6 | public class LongConverter : JsonConverter 7 | { 8 | public override bool CanConvert(Type objectType) 9 | { 10 | return objectType == typeof(long) || objectType == typeof(long?); 11 | } 12 | 13 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 14 | { 15 | long result = 0; 16 | if (reader.Value != null) 17 | { 18 | long.TryParse(reader.Value.ToString(), out result); 19 | } 20 | return result; 21 | } 22 | 23 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 24 | { 25 | writer.WriteValue(value.ToString()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/JsonTypeConverter/DecimalConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Sunny.Common.JsonTypeConverter 5 | { 6 | public class DecimalConverter : JsonConverter 7 | { 8 | public override bool CanConvert(Type objectType) 9 | { 10 | return objectType == typeof(decimal) || objectType == typeof(decimal?); 11 | } 12 | 13 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 14 | { 15 | decimal result = 0; 16 | if (reader.Value != null) 17 | { 18 | decimal.TryParse(reader.Value.ToString(), out result); 19 | } 20 | return result; 21 | } 22 | 23 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 24 | { 25 | writer.WriteValue(value.ToString()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace ApiDemo 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .ConfigureLogging(logging => 23 | { 24 | //logging.SetMinimumLevel(LogLevel.Warning); 25 | //微软默认添加了console和debue的级别大于Warning的日志输出,不习惯可以清除了自己添加 26 | logging.ClearProviders(); 27 | }) 28 | .UseStartup() 29 | .Build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Framework/Sunny.Repository/Sunny.Repository.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sunny.Repository 5 | 6 | 7 | 8 | 9 | 用于标识某类是DbModel,继承此接口后可通过T4模板生成ModelConfig类 10 | 11 | 12 | 13 | 14 | 用于标识用于映射多对多的类,继承此接口后可通过T4模板生成RelationMap类 15 | 16 | 17 | 18 | 19 | 对DbModel应用FlentApi字段配置 20 | 21 | 22 | DbModel所在的程序集名称 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/MoelConfig/StudentConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using System; 4 | 5 | namespace RepositoryDemo.DbModel.ModelConfig 6 | { 7 | public class StudentConfig : IEntityTypeConfiguration 8 | { 9 | 10 | public void Configure(EntityTypeBuilder builder) 11 | { 12 | builder.ToTable("student"); 13 | builder.Property(x => x.Id).HasColumnName("id"); 14 | builder.Property(x => x.StudentName).HasColumnName("student_name").HasMaxLength(30); 15 | builder.Property(x => x.Test).HasColumnName("test").HasMaxLength(30); 16 | builder.Property(x => x.Score).HasColumnName("score").HasColumnType("decimal(18, 2)"); 17 | builder.Property(x => x.CreateTime).HasColumnName("create_time"); 18 | builder.Property(x => x.CreaterId).HasColumnName("creater_id"); 19 | 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/CollectionQuery/PageInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Sunny.Common.Extend.CollectionQuery 5 | { 6 | /// 7 | /// 分页信息 8 | /// 9 | public class PageInfo 10 | { 11 | /// 12 | /// 当前页索引 13 | /// 14 | public int PageIndex { get; set; } 15 | 16 | /// 17 | /// 页大小 18 | /// 19 | public int PageSize { get; set; } 20 | 21 | /// 22 | /// 总记录数 23 | /// 24 | public int RecordTotal { get; set; } 25 | 26 | /// 27 | /// 总页数 28 | /// 29 | public int PageTotal 30 | { 31 | get 32 | { 33 | // (recordTotal-1)/pageSize+1当recordTotal=0时,pageTotal=1 34 | return (int)Math.Ceiling(RecordTotal / (double)PageSize); 35 | } 36 | } 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/MoelConfig/CategoryConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo.DbModel.ModelConfig 8 | { 9 | public class CategoryConfig : IEntityTypeConfiguration 10 | { 11 | 12 | public void Configure(EntityTypeBuilder builder) 13 | { 14 | builder.ToTable("category"); 15 | builder.Property(x => x.Id).HasColumnName("id"); 16 | builder.Property(x => x.CategoryName).HasColumnName("category_name").HasMaxLength(30); 17 | builder.Property(x => x.CreateTime).HasColumnName("create_time").HasDefaultValue(DateTime.Now); 18 | builder.Property(x => x.CreaterId).HasColumnName("creater_id"); 19 | builder.Property(x => x.UpdateTime).HasColumnName("update_time").IsRowVersion(); 20 | builder.Property(x => x.UpdaterId).HasColumnName("updater_id"); 21 | 22 | 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/DTO/Request/Demo/CustomerValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Sunny.Api.FluentValidation; 3 | using Sunny.Common.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace ApiDemo.FluentValidation2 10 | { 11 | public class CustomerValidator : Validator 12 | { 13 | public CustomerValidator() 14 | { 15 | RuleFor(x => x.Surname).NotEmpty(); 16 | RuleFor(x => x.Forename).NotEmpty().WithMessage("PleasFFe specify a first name"); 17 | RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount); 18 | RuleFor(x => x.Address).Length(20, 250); 19 | RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode"); 20 | } 21 | 22 | private bool BeAValidPostcode(string postcode) 23 | { 24 | return postcode == "123"; 25 | // custom postcode validating logic goes here 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Extend/SunnyControllerExtend.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Api.DTO.Response; 2 | using Sunny.Common.Enum; 3 | 4 | namespace Sunny.Api.Controllers 5 | { 6 | static public class SunnyControllerExtend 7 | { 8 | 9 | #region 封装操作结果 10 | public static Result GetResult(this SunnyController controller, T responseData, int code, string msg) 11 | { 12 | return new Result { Code = code, Msg = msg , Data = responseData}; 13 | } 14 | 15 | public static Result Success(this SunnyController controller, T responseData) 16 | { 17 | Result r = new Result(); 18 | r.Data = responseData; 19 | return r; 20 | } 21 | 22 | 23 | 24 | public static Result Success(this SunnyController controller) 25 | { 26 | return new Result(); 27 | } 28 | 29 | public static Result Fail(this SunnyController controller, string msg) 30 | { 31 | return new Result { Code = Enums.OperationStatus.Fail.GetHashCode(), Msg = msg }; 32 | } 33 | 34 | #endregion 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/DTO/Response/Result.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Common.Enum; 2 | using System; 3 | 4 | namespace Sunny.Api.DTO.Response 5 | { 6 | /// 7 | /// 带返回数据的结果 8 | /// 9 | /// 返回的类型 10 | public class Result 11 | { 12 | public int Code { get; set; } = Enums.OperationStatus.Success.GetHashCode(); 13 | 14 | public string Msg { get; set; } = Enums.OperationStatus.Success.GetDescribe(); 15 | public new T Data { get; set; } = default(T); 16 | 17 | public static implicit operator Result(Result result) 18 | { 19 | return new Result 20 | { 21 | Code = result.Code, 22 | Msg = result.Msg 23 | }; 24 | } 25 | } 26 | 27 | /// 28 | /// 不带返回数据的Result 29 | /// 如果要给前端返回数据时,请用泛型版本,这样看API时就知道返回的数据长什么样子 30 | /// 31 | public class Result 32 | { 33 | public int Code { get; set; } = Enums.OperationStatus.Success.GetHashCode(); 34 | 35 | public string Msg { get; set; } = Enums.OperationStatus.Success.GetDescribe(); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/ApiDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 1701;1702;1591 9 | D:\Work\Code\Sunny\UseDemo\ApiDemo\ApiDemo.xml 10 | 11 | 12 | 13 | D:\Work\Code\Sunny\UseDemo\ApiDemo\ApiDemo.xml 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/MoelConfig/PassageConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo.DbModel.ModelConfig 8 | { 9 | public class PassageConfig : IEntityTypeConfiguration 10 | { 11 | 12 | public void Configure(EntityTypeBuilder builder) 13 | { 14 | builder.ToTable("passage").HasQueryFilter(x => !x.IsDelete); 15 | builder.Property(x => x.Id).HasColumnName("id"); 16 | builder.Property(x => x.Title).HasColumnName("title").HasMaxLength(30); 17 | builder.Property(x => x.LastEditTime).HasColumnName("last_edit_time"); 18 | builder.Property(x => x.IsDelete).HasColumnName("is_delete"); 19 | builder.Property(x => x.CreateTime).HasColumnName("create_time").HasDefaultValueSql("now()"); 20 | builder.Property(x => x.CreaterId).HasColumnName("creater_id"); 21 | builder.Property(x => x.UpdateTime).HasColumnName("update_time").IsRowVersion(); 22 | builder.Property(x => x.UpdaterId).HasColumnName("updater_id"); 23 | 24 | 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/CollectionQuery/PageData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Sunny.Common.Extend.CollectionQuery 5 | { 6 | /// 7 | /// 包含分页信息的数据 8 | /// 9 | /// 10 | public class PageData 11 | { 12 | /// 13 | /// 数据 14 | /// 15 | public List List { get; set; } 16 | 17 | /// 18 | /// 分页信息 19 | /// 20 | public PageInfo PageInfo { get; set; } 21 | 22 | /// 23 | /// 将数据中的每一项转为dynamic,并返回一个新实例, 24 | /// 通常用于要对数据项进行扩展时,比如为数据项添加枚举的中文意思并返回给前端 25 | /// 如ToDynamic(x=>{x.Extend(new{EnumCn=x.EnumX.GetDescribe()})}) 26 | /// 27 | /// 28 | /// 29 | public PageData ToDynamic(Func func) 30 | { 31 | PageData result = new PageData(); 32 | result.PageInfo = PageInfo; 33 | if (List != null && List.Count > 0) 34 | { 35 | result.List = List.ToDynamic(func); 36 | } 37 | return result; 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/MoelConfig/StudentAddressConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo.DbModel.ModelConfig 8 | { 9 | public class StudentAddressConfig : IEntityTypeConfiguration 10 | { 11 | 12 | public void Configure(EntityTypeBuilder builder) 13 | { 14 | builder.ToTable("student_address"); 15 | builder.Property(x => x.Id).HasColumnName("id"); 16 | builder.Property(x => x.Address1).HasColumnName("address1").HasMaxLength(30); 17 | builder.Property(x => x.Zipcode).HasColumnName("zipcode"); 18 | builder.Property(x => x.State).HasColumnName("state").HasMaxLength(30); 19 | builder.Property(x => x.Country).HasColumnName("country").HasMaxLength(30); 20 | builder.Property(x => x.StudentId).HasColumnName("student_id"); 21 | builder.Property(x => x.CreateTime).HasColumnName("create_time"); 22 | builder.Property(x => x.CreaterId).HasColumnName("creater_id"); 23 | builder.Property(x => x.UpdateTime).HasColumnName("update_time").IsRowVersion(); 24 | builder.Property(x => x.UpdaterId).HasColumnName("updater_id"); 25 | 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/String/JsonHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | namespace Sunny.Common.Helper 6 | { 7 | public class JsonHelper 8 | { 9 | /// 10 | /// 将对象转换成json字符串 11 | /// 12 | /// 要转换的对象 13 | /// json字符串 14 | static public string ToJsonString(object obj) 15 | { 16 | 17 | return JsonConvert.SerializeObject(obj); 18 | 19 | } 20 | 21 | /// 22 | /// 将json字符串转换成指定类型的对象 23 | /// 24 | /// 指定类型 25 | /// json字符串 26 | /// 指定类型的对象 27 | static public T FromJsonString(string jsonString) 28 | { 29 | return JsonConvert.DeserializeObject(jsonString); 30 | } 31 | 32 | /// 33 | /// 将json字符串转换成dynamic类型的对象 34 | /// 35 | /// json字符串 36 | /// 动态解析类型的对象 37 | static public dynamic FromJsonString(string jsonString) 38 | { 39 | return JsonConvert.DeserializeObject(jsonString); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/DTO/Request/Demo/Buyer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ApiDemo.DTO.Request.Demo 4 | { 5 | public class Buyer 6 | { 7 | public string Name { get; set; } 8 | 9 | public string Phone { get; set; } 10 | 11 | public OrderInfo Order { get; set; } 12 | 13 | public BuyerComment Comment { get; set; } 14 | } 15 | 16 | public class BuyerSub : Buyer 17 | { 18 | 19 | public string Address { get; set; } 20 | } 21 | 22 | public class OrderInfo 23 | { 24 | 25 | public Decimal Price { get; set; } 26 | 27 | public int ProductCount { get; set; } 28 | 29 | } 30 | 31 | public class Seller 32 | { 33 | 34 | public string NaMe { get; set; } 35 | 36 | public DateTime SellTime { get; set; } 37 | 38 | public string Phone { get; set; } 39 | 40 | public OrderInfo Order { get; set; } 41 | 42 | public SellerComment Comment { get; set; } 43 | 44 | } 45 | 46 | public class SellerSub : Seller 47 | { 48 | public string Address { get; set; } 49 | } 50 | 51 | public class BuyerComment 52 | { 53 | 54 | public string Content { get; set; } 55 | 56 | public string BuyerName { get; set; } 57 | 58 | } 59 | 60 | public class SellerComment 61 | { 62 | 63 | public int Content { get; set; } 64 | public string SellerName { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/DbModel/RelationMap/PassageCategoryMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace RepositoryDemo.DbModel.RelationMap 8 | { 9 | public class PassageCategoryMap : IEntityTypeConfiguration 10 | { 11 | /// 12 | /// PassageCategories FluentAPI配置 13 | /// 14 | /// 添加复合主键、配置多对多关系 15 | /// 16 | /// 17 | public void Configure(EntityTypeBuilder builder) 18 | { 19 | //添加复合主键 20 | builder.HasKey(t => new { t.PassageId, t.CategoryId }); 21 | 22 | /// 23 | /// 24 | /// 配置Passage与PassageCategories的一对多关系 25 | /// 26 | /// EFCore中,新增默认级联模式为ClientSetNull 27 | /// 28 | /// 依赖实体的外键会被设置为空,同时删除操作不会作用到依赖的实体上,依赖实体保持不变,同下 29 | /// 30 | /// 31 | 32 | //配置Passage与PassageCategories的一对多关系 33 | builder.HasOne(t => t.Passage).WithMany(p => p.PassageCategories).HasForeignKey(t => t.PassageId); 34 | 35 | //配置Category与PassageCategories的一对多关系 36 | builder.HasOne(t => t.Category).WithMany(p => p.PassageCategories).HasForeignKey(t => t.CategoryId); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Quartz/JobWrapper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Quartz; 4 | using Sunny.Common.Helper; 5 | using System; 6 | using System.Diagnostics; 7 | using System.Threading.Tasks; 8 | 9 | namespace Sunny.Api.Quartz 10 | { 11 | [PersistJobDataAfterExecution] 12 | public class JobWrapper : IJob where T : IJobEntity 13 | { 14 | ILogger logger; 15 | T job; 16 | 17 | 18 | public JobWrapper() 19 | { 20 | logger = DiHelper.GetService().CreateLogger(typeof(T)); 21 | job = DiHelper.CreateInstance(); 22 | 23 | } 24 | 25 | public async Task Execute(IJobExecutionContext context) 26 | { 27 | Stopwatch sw = new Stopwatch(); 28 | 29 | try 30 | { 31 | sw.Start(); 32 | await job.ExecuteAsync(context); 33 | sw.Stop(); 34 | 35 | logger.LogInformation($"Job( Name: {job.JobName} , Describe:{job.Describe})已执行,耗时:{sw.ElapsedMilliseconds} 毫秒."); 36 | 37 | } 38 | catch (Exception ex) 39 | { 40 | sw.Stop(); 41 | logger.LogError($"Job( Name: {job.JobName} , Describe:{job.Describe})执行时发生异常.", ex); 42 | } 43 | finally 44 | { 45 | IDisposable disposable = job as IDisposable; 46 | disposable?.Dispose(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/CollectionQuery/CollectionQuery.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Common.Extend.CollectionQuery; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | 8 | namespace System.Linq 9 | { 10 | static public class CollectionQuery 11 | { 12 | /// 13 | /// 分页 14 | /// 15 | /// 数据源 16 | /// 第几页 17 | /// 每页记录数 18 | /// 记录总数 19 | /// 20 | public static List Pagination(this IQueryable list, int pageIndex, int pageSize, out int recordTotal) 21 | { 22 | recordTotal = list.Count(); 23 | return list.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(); 24 | } 25 | 26 | /// 27 | /// 分页并返回包含分页信息的数据 28 | /// 29 | /// 30 | /// 31 | /// 分页要求 32 | /// 33 | public static PageData Pagination(this IQueryable list,PageInfo pageInfo) 34 | { 35 | pageInfo.RecordTotal = list.Count(); 36 | var data=list.Skip((pageInfo.PageIndex - 1) * pageInfo.PageSize).Take(pageInfo.PageSize); 37 | return new PageData() { List = data.ToList(), PageInfo = pageInfo }; 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Framework/Sunny.Repository/FluentApiTools.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace Sunny.Repository 7 | { 8 | 9 | public class FluentApiTools { 10 | 11 | 12 | /// 13 | /// 对DbModel应用FlentApi字段配置 14 | /// 15 | /// 16 | /// DbModel所在的程序集名称 17 | public static void ApplyDbModelFluentApiConfig(ModelBuilder modelBuilder,string dbModelAssemblyName) 18 | { 19 | 20 | /*下边这段代码,如果项目中使用了2个以上的DbContext,也就是使用2个数据库,那么在CodeFirst场景中, 21 | 通过控制台命令进行数据库迁移时,会将2个数据库的表都生成到目标库里, 22 | 有一个解决的办法是在DbModel中继续一个自定义的接口,用以筛选要迁移的表, 23 | 然后在下边的语句中加筛选条件,如.Where(q => q.GetInterface(typeof(你定义的接口).FullName) != null&&q.GetInterface(typeof(IEntityTypeConfiguration<>).FullName) != null); 24 | 25 | 还有一个更好的办法就是为新的库新建立一个项目(程序集),不要将2个DbContext放于一个项目(程序集)中 26 | * 27 | */ 28 | 29 | //查找所有FluentAPI配置 30 | var typesToRegister = Assembly.Load(dbModelAssemblyName).GetTypes().Where(q => q.GetInterface(typeof(IEntityTypeConfiguration<>).FullName) != null); 31 | 32 | //应用FluentAPI 33 | foreach (var type in typesToRegister) 34 | { 35 | //dynamic使C#具有弱语言的特性,在编译时不对类型进行检查 36 | 37 | dynamic configurationInstance = Activator.CreateInstance(type); 38 | modelBuilder.ApplyConfiguration(configurationInstance); 39 | 40 | } 41 | 42 | } 43 | 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/DTO/Request/Demo/Customer.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Common.Helper; 2 | using System; 3 | using static ApiDemo.DTO.Request.Enummm; 4 | 5 | namespace ApiDemo.FluentValidation2 6 | { 7 | public class Customer 8 | { 9 | 10 | public long Id{ get; set; } 11 | public String Surname { get; set; } 12 | public String Forename { get; set; } 13 | public int Discount { get; set; } 14 | /// 15 | /// 地址 16 | /// 17 | public String Address { get; set; } 18 | public String Postcode { get; set; } 19 | public bool HasDiscount { get; set; } 20 | 21 | public IntEnum LocalType { get; set; } = IntEnum.GPS; 22 | 23 | public decimal Price { get; set; } = decimal.MaxValue; 24 | public decimal? PriceNull { get; set; } 25 | public decimal PriceDefault { get; set; } 26 | 27 | public string PriceCN { get; set; } = decimal.MaxValue.ToString(); 28 | 29 | public double DoubleNum { get; set; }= double.MaxValue; 30 | public string DoubleCN { get; set; } = double.MaxValue.ToString(); 31 | 32 | public float FloatNum { get; set; }= float.MaxValue; 33 | 34 | public string FloatCN { get; set; } = float.MaxValue.ToString(); 35 | 36 | public long LongNum { get; set; } = long.MaxValue; 37 | public string LongCn { get; set; } = long.MaxValue.ToString(); 38 | 39 | public DateTime Now { get; set; } = DateTime.Now; 40 | public Int64 IntNum { get; set; } = Int64.MaxValue; 41 | 42 | public string IntCn { get; set; } = Int64.MaxValue.ToString(); 43 | 44 | 45 | public long PuaID { get; set; } = IdHelper.GenId(); 46 | 47 | public string STRINGTEST { get; set; } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Midware/TokenValidateMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Caching.Distributed; 3 | using Microsoft.Extensions.Options; 4 | using Microsoft.Extensions.Primitives; 5 | using Sunny.Common.ConfigOption; 6 | using System.Threading.Tasks; 7 | 8 | namespace Sunny.Api.Midware 9 | { 10 | public class TokenValidateMiddleware 11 | { 12 | private readonly RequestDelegate next; 13 | private readonly IDistributedCache cache; 14 | private readonly TokenValidateOption option; 15 | 16 | 17 | public TokenValidateMiddleware(RequestDelegate next, IDistributedCache cache, IOptions options) 18 | { 19 | this.next = next; 20 | this.cache = cache; 21 | this.option = options.Value; 22 | } 23 | 24 | public async Task Invoke(HttpContext context) 25 | { 26 | bool validOk = false; 27 | 28 | if (!string.IsNullOrWhiteSpace(option.AuthApiStartWith) && context.Request.Path.ToString().StartsWith(option.AuthApiStartWith)) 29 | { 30 | StringValues token = StringValues.Empty; 31 | 32 | if (context.Request.Headers.TryGetValue(option.TokenKey, out token)) 33 | { 34 | if (await cache.ExistsAsync(token)) 35 | { 36 | validOk = true; 37 | } 38 | } 39 | } 40 | else 41 | { 42 | validOk = true; 43 | } 44 | 45 | 46 | if (!validOk) 47 | { 48 | context.Response.StatusCode = StatusCodes.Status401Unauthorized; 49 | } 50 | else 51 | { 52 | await next(context); 53 | } 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Framework/Sunny.Repository/Sunny.Repository.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | https://github.com/MackYang/Sunny.git 6 | https://github.com/MackYang/Sunny/raw/master/Doc/sunny.png 7 | https://github.com/MackYang/Sunny 8 | Sunny框架的Repository部分,用于项目中的数据仓储 9 | MackYang 10 | 1.0.4 11 | 增加了IDbModel和IRelationMap,以优化生成的FluentApi配置 12 | 1.0.4.0 13 | 1.0.4.0 14 | 15 | 16 | 17 | 1701;1702;1591 18 | D:\Work\Code\Sunny\Framework\Sunny.Repository\Sunny.Repository.xml 19 | 20 | 21 | 22 | 1701;1702;1591 23 | D:\Work\Code\Sunny\Framework\Sunny.Repository\Sunny.Repository.xml 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Sunny.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 7.3 6 | Sunny框架需要依赖的通用部分 7 | https://github.com/MackYang/Sunny 8 | https://github.com/MackYang/Sunny/raw/master/Doc/sunny.png 9 | https://github.com/MackYang/Sunny.git 10 | MackYang 11 | 1.0.7 12 | 添加了分页查询的Model 13 | 14 | 15 | 16 | 1701;1702;1591 17 | D:\Work\Code\Sunny\Framework\Sunny.Common\Sunny.Common.xml 18 | Off 19 | 20 | 21 | 22 | 1701;1702;1591 23 | D:\Work\Code\Sunny\Framework\Sunny.Common\Sunny.Common.xml 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/GenericType/ClassTypeExtend.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Dynamic; 5 | using System.Text; 6 | 7 | namespace System 8 | { 9 | public static class ClassTypeExtend 10 | { 11 | 12 | /// 13 | /// 使用另一个对象对源对象进行扩展,这类似于 jQuery 中的 extend 方法。 14 | /// 15 | /// 源对象。 16 | /// 用于扩展的另一个对象。 17 | /// 是否要覆盖源对象中同名属性的值,默认为否 18 | /// 19 | public static dynamic Extend(this T source, object other, bool isOverride = false) where T : class 20 | { 21 | if (source == null) 22 | { 23 | return other; 24 | } 25 | 26 | if (other == null) 27 | { 28 | return source; 29 | } 30 | 31 | var sourceProperties = TypeDescriptor.GetProperties(source); 32 | var otherProperties = TypeDescriptor.GetProperties(other); 33 | var expando = new ExpandoObject(); 34 | var dictionary = (IDictionary)expando; 35 | 36 | 37 | foreach (PropertyDescriptor p in sourceProperties) 38 | { 39 | dictionary.Add(p.Name, p.GetValue(source)); 40 | } 41 | 42 | foreach (PropertyDescriptor p in otherProperties) 43 | { 44 | if (!dictionary.ContainsKey(p.Name) && !isOverride) 45 | { 46 | dictionary.Add(p.Name, p.GetValue(other)); 47 | } 48 | else 49 | { 50 | throw new Exception($"源对象已经包含了名为{p.Name}的属性,如果要覆盖请给isOverride参数传true值"); 51 | } 52 | } 53 | 54 | return expando; 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/Controllers/UnAuthController.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Caching.Distributed; 4 | using Microsoft.Extensions.Logging; 5 | using Quartz; 6 | using RepositoryDemo; 7 | using RepositoryDemo.DbModel; 8 | using ServiceDemo; 9 | using Sunny.Api.Controllers; 10 | using Sunny.Api.DTO.Response; 11 | using Sunny.Common.DependencyInjection; 12 | using System; 13 | using System.Threading.Tasks; 14 | 15 | namespace ApiDemo.Api.Controllers 16 | { 17 | /// 18 | /// 不需要验证登录即可访问的API 19 | /// 20 | [Route("unAuthApi/[controller]")] 21 | public class UnAuthController : SunnyController 22 | { 23 | 24 | IDistributedCache cache; 25 | MyDbContext db; 26 | ILogger logger; 27 | IMapper mapper; 28 | 29 | 30 | //TDbContext tDbContex; 31 | public UnAuthController(MyDbContext efDbContext, ILogger logger, IMapper mapper, IDistributedCache cache) 32 | { 33 | this.db = efDbContext; 34 | this.logger = logger; 35 | this.mapper = mapper; 36 | this.cache = cache; 37 | 38 | //this.tDbContex = tDbContext; 39 | } 40 | 41 | 42 | /// 43 | /// 设置Token,使以/api开头的方法可以通过验证 44 | /// 45 | /// 46 | [HttpGet] 47 | public async Task> Get() 48 | { 49 | return await Task.Run(() => this.Success(true)); 50 | } 51 | 52 | /// 53 | /// 设置Token,使以/api开头的方法可以通过验证 54 | /// 55 | /// 56 | [HttpGet("SetToken")] 57 | public Result SetToken() 58 | { 59 | 60 | cache.Set("123", "aweiskOK23"); 61 | return this.Success(true); 62 | } 63 | 64 | /// 65 | /// 测试异常日志记录 66 | /// 67 | /// 68 | [HttpGet("GetException")] 69 | public Result GetException() 70 | { 71 | 72 | throw new Exception("Test Excexxxx"); 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/Serialize/SerializeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.Serialization; 4 | using System.Runtime.Serialization.Formatters.Binary; 5 | 6 | namespace Sunny.Common.Helper 7 | { 8 | public class SerializeHelper 9 | { 10 | /// 11 | /// 将对象序列化为Base64字符串 12 | /// 13 | /// 需要序列化的对象 14 | /// Base64字符串 15 | public static string SerializeObject(object obj) 16 | { 17 | try 18 | { 19 | IFormatter formatter = new BinaryFormatter(); 20 | MemoryStream ms = new MemoryStream(); 21 | formatter.Serialize(ms, obj); 22 | ms.Position = 0; 23 | byte[] buffer = new byte[ms.Length]; 24 | ms.Read(buffer, 0, buffer.Length); 25 | ms.Flush(); 26 | ms.Close(); 27 | return Convert.ToBase64String(buffer); 28 | } 29 | catch (Exception ex) 30 | { 31 | throw new Exception("将对象序列化成Base64字符串时发生异常:" + ex); 32 | } 33 | 34 | } 35 | 36 | /// 37 | /// 将Base64字符串反序列化为指定类型的对象 38 | /// 39 | /// 对象类型 40 | /// Base64字符串 41 | /// 出现异常时是否抛出 42 | /// T类型的对象 43 | public static T Desrialize(string base64Str) 44 | { 45 | 46 | try 47 | { 48 | T obj = default(T); 49 | IFormatter formatter = new BinaryFormatter(); 50 | byte[] buffer = Convert.FromBase64String(base64Str); 51 | MemoryStream memoryStream = new MemoryStream(buffer); 52 | obj = (T)formatter.Deserialize(memoryStream); 53 | memoryStream.Flush(); 54 | memoryStream.Close(); 55 | return obj; 56 | } 57 | catch (Exception ex) 58 | { 59 | throw new Exception("将Base64字符串反序列化为对象时出现异常:" + ex); 60 | } 61 | 62 | 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/Calc/CalcHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sunny.Common.Helper 4 | { 5 | public class CalcHelper 6 | { 7 | 8 | /// 9 | /// 计算增减率返回%比形式字符串,保留2位小数 10 | /// 11 | /// 本期 12 | /// 同期 13 | /// 14 | public static string CalcDifferenceRate(decimal a, decimal b) 15 | { 16 | if (b == 0) 17 | { 18 | if (a == b) 19 | { 20 | return "0.00%"; 21 | } 22 | if (a > b) 23 | { 24 | return "100.00%"; 25 | } 26 | else 27 | { 28 | return "-100.00%"; 29 | } 30 | 31 | } 32 | 33 | return ((a - b) / b).ToString("0.##%"); 34 | 35 | 36 | } 37 | 38 | 39 | /// 40 | /// 计算增减率返回转成百分比后的decimal 41 | /// 42 | /// 本期 43 | /// 同期 44 | /// 45 | public static decimal CalcDifferenceRateReturnDecimal(decimal a, decimal b) 46 | { 47 | if (b == 0) 48 | { 49 | if (a == b) 50 | { 51 | return 0; 52 | } 53 | if (a > b) 54 | { 55 | return 100; 56 | } 57 | else 58 | { 59 | return -100; 60 | } 61 | 62 | } 63 | 64 | return Math.Round(((a - b) / b) * 100, 2); 65 | 66 | 67 | } 68 | 69 | 70 | /// 71 | /// 除法计算保留几位小数 72 | /// 73 | /// 被除数 74 | /// 除数 75 | /// 76 | public static decimal CalcDivision(decimal a, decimal b, int decimals = 2) 77 | { 78 | if (b == 0) 79 | { 80 | return 0.00M; 81 | } 82 | return Math.Round(a / b, decimals); 83 | 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Sunny.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | Library 7 | 8 | Sunny框架的WebApi部分,提供一些Api的常用功能 9 | https://github.com/MackYang/Sunny.git 10 | https://github.com/MackYang/Sunny 11 | https://github.com/MackYang/Sunny/raw/master/Doc/sunny.png 12 | 1.1.0 13 | MackYang 14 | 解决Swagger文档生成的问题 15 | 16 | 17 | 18 | D:\Work\Code\Sunny\Framework\Sunny.Api\Sunny.Api.xml 19 | 1701;1702;1591 20 | 21 | 22 | 23 | 1701;1702;1591 24 | D:\Work\Code\Sunny\Framework\Sunny.Api\Sunny.Api.xml 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Midware/BizExceptionHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Sunny.Common.Enum; 3 | using Sunny.Common.Helper; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Sunny.Api.Midware 10 | { 11 | 12 | /// 13 | /// 业务异常中间件 14 | /// 15 | public class BizExceptionHandlerMiddleware 16 | { 17 | private readonly RequestDelegate next; 18 | 19 | public BizExceptionHandlerMiddleware(RequestDelegate next) 20 | { 21 | this.next = next; 22 | } 23 | 24 | public async Task Invoke(HttpContext context) 25 | { 26 | try 27 | { 28 | await next(context); 29 | } 30 | catch (BizException ex) 31 | { 32 | await HandleBizExceptionAsync(context, ex); 33 | } 34 | 35 | } 36 | 37 | /// 38 | ///处理业务异常 39 | /// 40 | /// 41 | /// 42 | /// 43 | public async Task HandleBizExceptionAsync(HttpContext context, BizException ex) 44 | { 45 | var data = new SpeciResult { code = Enums.OperationStatus.Fail.GetHashCode(), msg = ex.Message }; 46 | await ResponseInfo(context, data); 47 | } 48 | 49 | /// 50 | /// 向前端输出信息 51 | /// 52 | /// 53 | /// 54 | /// 55 | private static async Task ResponseInfo(HttpContext context, SpeciResult data) 56 | { 57 | var result = JsonHelper.ToJsonString(data); 58 | context.Response.ContentType = "application/json;charset=utf-8"; 59 | await context.Response.WriteAsync(result); 60 | 61 | } 62 | } 63 | 64 | /// 65 | /// 特殊的返回结果类,和IResult所定义的不同之处在于字段全是小写的,因为发生异常时,后边的中间件不会被调用,所以没法将大驼峰转小驼峰再返回给前端 66 | /// 67 | public class SpeciResult 68 | { 69 | public int code { get; set; } 70 | public string msg { get; set; } 71 | public object data { get; set; } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Framework/TemplateT4/FluentApiConfig/FluentApiConfigProcesser.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Common.Helper; 2 | using Sunny.Repository.DbModel; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace Sunny.TemplateT4.FluentApiConfig 10 | { 11 | public class FluentApiConfigProcesser 12 | { 13 | /// 14 | /// 生成DbModel的FluentApi配置文件 15 | /// 16 | /// DbModel所在的程序集名称,如Sunny.Repository 17 | /// 所生成的配置文件的命名空间,如Sunny.Repository.DbModel.MoelConfig 18 | /// 生成文件的输出目录,默认是D:\SunnyFramework\Output\DbConfig\ 19 | static public void GenerateFiles(string assemblyName,string outputFileNamespace,string outputDir = @"D:\SunnyFramework\Output\DbConfig\") 20 | { 21 | Assembly assembly = Assembly.Load(assemblyName); 22 | List ts = assembly.GetTypes().ToList(); 23 | 24 | var typeList = ts.Where(s => !s.IsInterface && (typeof(IDbModel).IsAssignableFrom(s) || typeof(IRelationMap).IsAssignableFrom(s)) && !s.GetTypeInfo().IsAbstract && s.FullName != "Sunny.Repository.DbModel.BaseModel"); 25 | foreach (var item in typeList) 26 | { 27 | var fullNameArr = item.FullName.Split("."); 28 | var filePath = $@"{fullNameArr[fullNameArr.Length - 2]}\{fullNameArr[fullNameArr.Length - 1]}"; 29 | 30 | if (typeof(IRelationMap).IsAssignableFrom(item)) 31 | { 32 | RelationMapConfig config = new RelationMapConfig(item, outputFileNamespace); 33 | string outputFile = outputDir + @"\RelationConfig\" + filePath + "Map.cs"; 34 | FileHelper.WriteFile(outputFile, config.TransformText(), false); 35 | } 36 | else 37 | { 38 | DbModelConfig config = new DbModelConfig(item, outputFileNamespace); 39 | string outputFile = outputDir+@"\FieldConfig\" + filePath+"Config.cs"; 40 | FileHelper.WriteFile(outputFile, config.TransformText(), false); 41 | } 42 | } 43 | 44 | Console.WriteLine("DbModalFluentApiConfig Generated To:"); 45 | Console.WriteLine(outputDir); 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/Log/LoggerFactoryExtend.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging.Console; 2 | using Sunny.Common.ConfigOption; 3 | using Sunny.Common.Log; 4 | using System; 5 | 6 | 7 | namespace Microsoft.Extensions.Logging 8 | { 9 | public static class LoggerFactoryExtend 10 | { 11 | 12 | public static ILoggerFactory AddNetLogger(this ILoggerFactory factory, NetLoggerOption option) 13 | { 14 | factory.AddProvider(new NetLoggerProvider(option)); 15 | return factory; 16 | } 17 | 18 | public static ILoggerFactory AddNetLogger(this ILoggerFactory factory, NetLoggerOption option, Func filter) 19 | { 20 | factory.AddProvider(new NetLoggerProvider(option, filter, false)); 21 | return factory; 22 | } 23 | 24 | /// 25 | /// 使用默认的过虑规则启用网络日志记录,默认规则是只记录微软Warning及以上的日志,或非微软Info及以上的日志 26 | /// 27 | /// 28 | /// 网络日志配置 29 | /// 30 | public static ILoggerFactory AddNetLoggerUseDefaultFilter(this ILoggerFactory factory, NetLoggerOption option) 31 | { 32 | Func filter = (category, level) => 33 | { 34 | 35 | return (level >= LogLevel.Information && !category.StartsWith("Microsoft")) || (level >= LogLevel.Warning); 36 | 37 | }; 38 | 39 | factory.AddProvider(new NetLoggerProvider(option, filter, false)); 40 | return factory; 41 | } 42 | 43 | /// 44 | /// 使用默认的过虑规则启用控制台日志记录,默认规则是只记录微软Warning及以上的日志,或非微软Info及以上的日志,或SQL语句 45 | /// 46 | /// 47 | /// 48 | /// 49 | public static ILoggerFactory AddConsoleLoggerUseDefaultFilter(this ILoggerFactory factory) 50 | { 51 | Func filter = (category, level) => 52 | { 53 | 54 | return (level >= LogLevel.Information && !category.StartsWith("Microsoft")) || (level >= LogLevel.Warning || category.StartsWith("Microsoft.EntityFrameworkCore.Database.Command")); 55 | 56 | }; 57 | 58 | factory.AddProvider(new ConsoleLoggerProvider(filter, false)); 59 | return factory; 60 | } 61 | 62 | 63 | public static ILoggerFactory AddNetLogger(this ILoggerFactory factory, NetLoggerOption option, Func filter, bool includeScopes) 64 | { 65 | factory.AddProvider(new NetLoggerProvider(option, filter, includeScopes)); 66 | return factory; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Extend/IApplicationBuilderExtend.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Quartz; 3 | using Sunny.Api.Quartz; 4 | using Sunny.Common.ConfigOption; 5 | using Sunny.Common.Helper; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace Microsoft.AspNetCore.Builder 10 | { 11 | static public class IApplicationBuilderExtend 12 | { 13 | /// 14 | /// 初始化DiHelper中的ServiceProvider 15 | /// 16 | /// 17 | static public void InitServiceProvider(this IApplicationBuilder builder) 18 | { 19 | DiHelper.ServiceProvider = builder.ApplicationServices; 20 | } 21 | 22 | /// 23 | /// 启用Job 24 | /// 25 | /// 26 | /// 27 | /// 28 | static public async void EnableJob(this IApplicationBuilder builder, IConfiguration configuration, ISchedulerFactory schedulerFactory) 29 | { 30 | var option = configuration.GetSection("SunnyOptions:JobOption").Get(); 31 | if (option != null && option.Length > 0) 32 | { 33 | //1、通过调度工厂获得调度器 34 | var scheduler = await schedulerFactory.GetScheduler(); 35 | //2、开启调度器 36 | await scheduler.Start(); 37 | 38 | var types = DiHelper.GetCustomizeTypes(t => typeof(IJobEntity).IsAssignableFrom(t) && !t.GetTypeInfo().IsAbstract); 39 | 40 | 41 | foreach (var item in option) 42 | { 43 | var tArr = types.Where(x => x.Name == item.JobClassName); 44 | foreach (var t in tArr) 45 | { 46 | //3、创建一个触发器 47 | var trigger = TriggerBuilder.Create() 48 | .WithCronSchedule(item.RunAtCron) 49 | .Build(); 50 | 51 | var wrapperType = typeof(JobWrapper<>).MakeGenericType(t.GetTypeInfo()); 52 | 53 | //4、创建任务 54 | var jobDetail = JobBuilder.Create(wrapperType) 55 | .WithIdentity(item.JobClassName, item.JobGroup) 56 | .SetJobData(item.Args == null ? new JobDataMap() : new JobDataMap(item.Args)) 57 | .Build(); 58 | 59 | 60 | //5、将触发器和任务器绑定到调度器中 61 | await scheduler.ScheduleJob(jobDetail, trigger); 62 | 63 | } 64 | 65 | 66 | } 67 | } 68 | 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/File/FileHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | 4 | 5 | namespace Sunny.Common.Helper 6 | { 7 | 8 | public class FileHelper 9 | { 10 | /// 11 | /// 写入文件 12 | /// 13 | /// 文件名 14 | /// 文件内容 15 | /// 默认是追加,如果要替换原内容传flase 16 | public static void WriteFile(string filePath, string content, bool isAppend = true) 17 | { 18 | var dirInfo = Directory.GetParent(filePath).ToString(); 19 | CreatePathIfNotExists(dirInfo); 20 | if (isAppend) 21 | { 22 | System.IO.File.AppendAllText(filePath, content); 23 | } 24 | else 25 | { 26 | System.IO.File.WriteAllText(filePath, content); 27 | } 28 | 29 | 30 | } 31 | 32 | /// 33 | /// 读取文件 34 | /// 35 | /// 文件路径 36 | /// 37 | public static string ReadFile(string filePath) 38 | { 39 | return System.IO.File.ReadAllText(filePath); 40 | 41 | } 42 | 43 | 44 | 45 | ///// 46 | ///// 自动清除24小时以前创建的缓存文件,每一小时运行一次 47 | ///// 48 | //public static void AutoClearCacheFile() 49 | //{ 50 | // string cachePath = HttpContext.Current.Server.MapPath(Vars.CacheFilePath); 51 | // TimeSpan timeSpan = new TimeSpan(1, 0, 0);//1小时 52 | // while (true) 53 | // { 54 | // try 55 | // { 56 | // List fileList = GetFileList(cachePath); 57 | // if (fileList != null && fileList.Count > 0) 58 | // { 59 | // foreach (string item in fileList) 60 | // { 61 | // DateTime cacheTime = File.GetCreationTime(item); 62 | // if (DateTime.Now.Subtract(new TimeSpan(24, 0, 0)) > cacheTime) 63 | // { 64 | // File.Delete(item); 65 | // } 66 | // } 67 | // } 68 | // } 69 | // catch (Exception ex) 70 | // { 71 | // Utility.Logger.Error("删除缓存文件时发生异常:" + ex); 72 | // } 73 | 74 | // Thread.Sleep(timeSpan); 75 | // } 76 | //} 77 | 78 | /// 79 | /// 创建目录(如果目录不存在) 80 | /// 81 | /// 要创建的目录 82 | public static void CreatePathIfNotExists(string path) 83 | { 84 | 85 | if (!Directory.Exists(path)) 86 | { 87 | Directory.CreateDirectory(path); 88 | } 89 | 90 | 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Information" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Information" 12 | } 13 | } 14 | }, 15 | 16 | "ConnectionStrings": { 17 | "MySql": "server=127.0.0.1;Port=3306;database=test;uid=root;pwd=yourPassword;Charset=utf8;Pooling=true;Max Pool Size=1000;", 18 | "MySq2": "server=127.0.0.1;Port=3306;database=test2;uid=root;pwd=yourPassword.;Charset=utf8;" 19 | }, 20 | 21 | "SunnyOptions": { 22 | "NetLoggerOption": { 23 | "Url": "http://test.log.loc-mall.com/Api/AddLog", 24 | //每个业务系统配置自己的ID,请到http://test.log.loc-mall.com/ui/pages/apply.aspx申请,并妥善保存, 25 | //写入的日志在这里查看http://test.log.loc-mall.com/ui/pages/log.aspx?systemid=xxxxxxxx 26 | //生产环境去掉test.即可,或者到https://github.com/MackYang/LogService.git下来部署载自己的日志系统 27 | "SystemId": "b06b7e4d-bbce-11e8-98ca-00163e063309", 28 | //存放离线日志的目录,当网络日志记录失败时,会将日志以yyyy-MM-dd.txt写到该目录下 29 | "OfflineLogPath": "D:\\SunnyFramework\\OfflineLog" 30 | }, 31 | "SnowflakeOption": { 32 | "DatacenterId": 1, 33 | "MachineId": 1 34 | }, 35 | "RedisOptions": { 36 | //连接字符串 37 | "ConnectionString": "127.0.0.1:6379", 38 | //调用方实例名称,redis中的key会自动以设置的字符串开头,用以标识是哪台机器存入的key 39 | "InstanceName": "api_", 40 | //默认的滑动过期时间多少秒,为了防止缓存击穿,实际存入时会在该时间上加10秒类的随机数 41 | "DefaultSlidingExpiration": 600 42 | }, 43 | "TokenValidateOption": { 44 | //将根据该值来从HttpHeader中存取对应的token值,默认就是"token" 45 | "TokenKey": "token", 46 | // 以此开头的API都需要验证Token,如果不需要可不配置 47 | "AuthApiStartWith": "/api" 48 | 49 | }, 50 | //邮件配置 51 | "MailOption": { 52 | // 邮件服务器地址 53 | "EmailHost": "smtp.ym.163.com", 54 | // 用户名 55 | "EmailUserName": "you user name", 56 | // 密码 57 | "EmailPassword": "you password", 58 | // IP白名单列表,在列表中的IP发邮件前不执行检查事件 59 | "IPWhiteList": [ "127.0.0.1" ] 60 | }, 61 | //短信配置 62 | "SmsOption": { 63 | // 这个短信平台不能用了,以后接入其他平台 64 | "ApiUrl": "http://sms.pica.com/zqhdServer/sendSMS.jsp?regcode=yourCode&pwd=yourPassword&phone=$TOPHONE&content=$CONTENT&extnum=&level=1&schtime=&reportflag=1&url=url&smstype=0", 65 | // IP白名单列表,在列表中的IP发短信前不执行检查事件 66 | "IPWhiteList": [ "127.0.0.1" ] 67 | }, 68 | //查询IP信息的配置 69 | "IpInfoQueryOption": { 70 | //IP查询的API 71 | "ApiUrl": "http://www.ip.cn/index.php?ip=" 72 | }, 73 | "JobOption": [ 74 | { 75 | //Job所的的类名称 76 | "JobClassName": "JobB", 77 | //Job所属的组,同一组中不能有2个相同的任务 78 | "JobGroup": "group1", 79 | //Job在什么时候运行,用Cron表达式 80 | "RunAtCron": "*/55 * * * * ?", 81 | //Job的参数,没有可以不写 82 | "Args": { 83 | //参数名字和你在任务中写的要相同 84 | "pxxx": "kkk", 85 | "nnn": 123 86 | } 87 | }, 88 | 89 | { 90 | //Job所的的类名称 91 | "JobClassName": "JobA", 92 | //Job在什么时候运行,用Cron表达式 93 | "RunAtCron": "*/59 * * * * ?" 94 | 95 | } 96 | ] 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Framework/TemplateT4/Sunny.TemplateT4.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netcoreapp2.1 6 | 7 | 8 | https://github.com/MackYang/Sunny.git 9 | https://github.com/MackYang/Sunny/raw/master/Doc/sunny.png 10 | https://github.com/MackYang/Sunny 11 | Sunny框架的T4模板部分,主要用于生成DbModel的FluentApi配置 12 | MackYang 13 | 1.0.9 14 | 增加varchar字段默认不能为空的配置 15 | 1.0.9.0 16 | 1.0.9.0 17 | 18 | 19 | 20 | D:\Work\Code\Sunny\Framework\TemplateT4\Sunny.TemplateT4.xml 21 | 1701;1702;1591 22 | 23 | 24 | 25 | 1701;1702;1591 26 | D:\Work\Code\Sunny\Framework\TemplateT4\Sunny.TemplateT4.xml 27 | 28 | 29 | 30 | 31 | True 32 | True 33 | RelationMapConfig.tt 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | mscorlib 48 | 49 | 50 | System 51 | 52 | 53 | System.Core 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | True 64 | True 65 | DbModelConfig.tt 66 | 67 | 68 | True 69 | True 70 | RelationMapConfig.tt 71 | 72 | 73 | 74 | 75 | 76 | TextTemplatingFilePreprocessor 77 | DbModelConfig.cs 78 | 79 | 80 | TextTemplatingFilePreprocessor 81 | RelationMapConfig.cs 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Log/NetLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Sunny.Common.ConfigOption; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace Sunny.Common.Log 9 | { 10 | class NetLoggerProvider : ILoggerProvider, ISupportExternalScope 11 | { 12 | private readonly ConcurrentDictionary _loggers = new ConcurrentDictionary(); 13 | 14 | private readonly Func _filter; 15 | 16 | private readonly NetLoggerProcessor _messageQueue; 17 | 18 | private static readonly Func trueFilter = (cat, level) => true; 19 | private static readonly Func falseFilter = (cat, level) => false; 20 | private IDisposable _optionsReloadToken; 21 | private bool _includeScopes; 22 | private bool _disableColors; 23 | private IExternalScopeProvider _scopeProvider; 24 | 25 | public NetLoggerProvider(NetLoggerOption option) : this(option,null, false) { } 26 | 27 | public NetLoggerProvider(NetLoggerOption option,Func filter, bool includeScopes) 28 | { 29 | _filter = filter; 30 | _includeScopes = includeScopes; 31 | _messageQueue = new NetLoggerProcessor(option); 32 | 33 | } 34 | 35 | 36 | 37 | 38 | public ILogger CreateLogger(string name) 39 | { 40 | return _loggers.GetOrAdd(name, CreateLoggerImplementation); 41 | } 42 | 43 | private NetLogger CreateLoggerImplementation(string name) 44 | { 45 | return new NetLogger(name, GetFilter( ), null, _messageQueue) 46 | ; 47 | } 48 | 49 | private Func GetFilter( ) 50 | { 51 | if (_filter != null) 52 | { 53 | return _filter; 54 | } 55 | 56 | 57 | return trueFilter; 58 | } 59 | 60 | private IEnumerable GetKeyPrefixes(string name) 61 | { 62 | while (!string.IsNullOrEmpty(name)) 63 | { 64 | yield return name; 65 | var lastIndexOfDot = name.LastIndexOf('.'); 66 | if (lastIndexOfDot == -1) 67 | { 68 | yield return "Default"; 69 | break; 70 | } 71 | name = name.Substring(0, lastIndexOfDot); 72 | } 73 | } 74 | 75 | private IExternalScopeProvider GetScopeProvider() 76 | { 77 | if (_includeScopes && _scopeProvider == null) 78 | { 79 | _scopeProvider = new LoggerExternalScopeProvider(); 80 | } 81 | return _includeScopes ? _scopeProvider : null; 82 | } 83 | 84 | public void Dispose() 85 | { 86 | _optionsReloadToken?.Dispose(); 87 | _messageQueue.Dispose(); 88 | } 89 | 90 | public void SetScopeProvider(IExternalScopeProvider scopeProvider) 91 | { 92 | _scopeProvider = scopeProvider; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Framework/TemplateT4/FluentApiConfig/RelationMapConfigPartial.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace Sunny.TemplateT4.FluentApiConfig 9 | { 10 | public partial class RelationMapConfig 11 | { 12 | public Type DbModelType { get; set; } 13 | 14 | public string ConfigNamespace { get; set; } 15 | 16 | 17 | public string GetRelationConfig() 18 | { 19 | //当前类型的所有公共属性 20 | var allFields = DbModelType.GetProperties(); 21 | 22 | //获取简单类型字段 23 | var simpleTypeFields = allFields.Where(x => !x.PropertyType.IsGenericType && !typeof(IDbModel).IsAssignableFrom(x.PropertyType)); 24 | 25 | //导航属性字段,表示关系 26 | var navigationFields = allFields.Where(x => x.PropertyType.IsGenericType || typeof(IDbModel).IsAssignableFrom(x.PropertyType)).ToList(); 27 | 28 | if (simpleTypeFields.Count() != 2|| navigationFields.Count()!=2) 29 | { 30 | throw new Exception($"实体{DbModelType}的导航属性配置不正确,在多对多的关系映射中,必须包含2个Model的Id以及对应的导航属性,如果不是多对多的映射,不需要继承自IRelationMap接口,直接遵守微软约定大约配置的法则EFCore即可自动配置."); 31 | } 32 | 33 | StringBuilder sb = new StringBuilder(); 34 | 35 | //复合主键设置 36 | var mutliKey = $"builder.HasKey(t => new { "{" + string.Join(',', simpleTypeFields.Select(x => "t." + x.Name)) + "}" });"; 37 | sb.AppendLine(mutliKey); 38 | 39 | var leftModelName = navigationFields[0].Name; 40 | 41 | var leftModelNavigations = navigationFields[0].PropertyType.GetProperties().Where(x => x.PropertyType.GenericTypeArguments.Any(n => n == DbModelType)); 42 | if (leftModelNavigations.Count() != 1) 43 | { 44 | throw new Exception($"类型{navigationFields[0].PropertyType}中有且只能有一个泛型类型为{DbModelType}的导航属性"); 45 | } 46 | 47 | var leftModelNavigationFieldName = leftModelNavigations.First().Name; 48 | 49 | var rightModelName = navigationFields[1].Name; 50 | 51 | var rightModelNavigations = navigationFields[1].PropertyType.GetProperties().Where(x => x.PropertyType.GenericTypeArguments.Any(n => n == DbModelType)); 52 | if (rightModelNavigations.Count() != 1) 53 | { 54 | throw new Exception($"类型{navigationFields[0].PropertyType}中有且只能有一个泛型类型为{DbModelType}的导航属性"); 55 | } 56 | 57 | var rightModelNavigationFieldName = rightModelNavigations.First().Name; 58 | 59 | //配置左边的一对多关系 60 | var leftConfig = $"builder.HasOne(t => t.{leftModelName}).WithMany(x => x.{leftModelNavigationFieldName}).HasForeignKey(t => t.{leftModelName+"Id"});"; 61 | 62 | //配置右边的一对多关系 63 | var rightConfig = $"builder.HasOne(t => t.{rightModelName}).WithMany(x => x.{rightModelNavigationFieldName}).HasForeignKey(t => t.{rightModelName + "Id"});"; 64 | 65 | sb.AppendLine(leftConfig); 66 | sb.AppendLine(rightConfig); 67 | 68 | return sb.ToString(); 69 | } 70 | 71 | 72 | 73 | public RelationMapConfig(Type dbModelType, string configNamespace) 74 | { 75 | this.DbModelType = dbModelType; 76 | this.ConfigNamespace = configNamespace; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Midware/ExceptionHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Logging; 3 | using Sunny.Common.Enum; 4 | using Sunny.Common.Helper; 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | 9 | namespace Sunny.Api.Midware 10 | { 11 | public class ErrorHandlingMiddleware 12 | { 13 | private readonly RequestDelegate next; 14 | private readonly ILoggerFactory loggerFactory; 15 | public ErrorHandlingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) 16 | { 17 | 18 | this.next = next; 19 | this.loggerFactory = loggerFactory; 20 | 21 | } 22 | 23 | public async Task Invoke(HttpContext context) 24 | { 25 | var statusCode = context.Response.StatusCode; 26 | 27 | try 28 | { 29 | await next(context); 30 | } 31 | catch (BizException ex) 32 | { 33 | await HandleBizExceptionAsync(context, ex); 34 | } 35 | catch (Exception ex) 36 | { 37 | await HandleExceptionAsync(context, ex); 38 | } 39 | finally 40 | { 41 | if (statusCode != StatusCodes.Status200OK) 42 | { 43 | await HandleErrorAsync(context, statusCode); 44 | } 45 | } 46 | } 47 | 48 | private async Task HandleExceptionAsync(HttpContext context, Exception ex) 49 | { 50 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 51 | var logger = loggerFactory.CreateLogger(ex.TargetSite.DeclaringType); 52 | logger.LogError(ex, ex.Message); 53 | 54 | var data = new SpeciResult { code = Enums.OperationStatus.Exception.GetHashCode(), msg = "我们已经收到此次异常信息,将尽快解决!" }; 55 | 56 | await ResponseInfo(context, data); 57 | } 58 | 59 | private async Task HandleErrorAsync(HttpContext context, int statusCode) 60 | { 61 | 62 | var msg = "未知错误"; 63 | 64 | switch (statusCode) 65 | { 66 | case StatusCodes.Status401Unauthorized: 67 | msg = "未授权"; 68 | break; 69 | case StatusCodes.Status404NotFound: 70 | msg = "未找到服务"; 71 | break; 72 | case StatusCodes.Status502BadGateway: 73 | msg = "请求错误"; 74 | break; 75 | } 76 | var data = new SpeciResult { code = Enums.OperationStatus.Fail.GetHashCode(), msg = msg }; 77 | await ResponseInfo(context, data); 78 | } 79 | 80 | /// 81 | ///处理业务异常 82 | /// 83 | /// 84 | /// 85 | /// 86 | private async Task HandleBizExceptionAsync(HttpContext context, BizException ex) 87 | { 88 | var data = new SpeciResult { code = Enums.OperationStatus.Fail.GetHashCode(), msg = ex.Message }; 89 | await ResponseInfo(context, data); 90 | } 91 | 92 | /// 93 | /// 向前端输出信息 94 | /// 95 | /// 96 | /// 97 | /// 98 | private async Task ResponseInfo(HttpContext context, SpeciResult data) 99 | { 100 | var result = JsonHelper.ToJsonString(data); 101 | context.Response.ContentType = "application/json;charset=utf-8"; 102 | await context.Response.WriteAsync(result); 103 | 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Extend/Cache/IDistributedCacheExtend.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace Microsoft.Extensions.Caching.Distributed 6 | { 7 | static public class IDistributedCacheExtend 8 | { 9 | static public int DefaultSlidingExpiration { get; set; } = 600; 10 | 11 | static Random random = new Random(); 12 | 13 | /// 14 | /// 将对象转成json字符串后存到缓存中 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 过期选项,如果不指定,则在配置的默认时间后滑动过期 20 | public static void Set(this IDistributedCache cache, string key, object value, DistributedCacheEntryOptions options = null) 21 | { 22 | if (options == null) 23 | { 24 | options = new DistributedCacheEntryOptions(); 25 | } 26 | 27 | options.SlidingExpiration = TimeSpan.FromSeconds(DefaultSlidingExpiration + random.Next(10)); 28 | 29 | cache.SetString(key, JsonConvert.SerializeObject(value), options); 30 | } 31 | 32 | /// 33 | /// 将对象转成json字符串后存到缓存中 34 | /// 35 | /// 36 | /// 37 | /// 38 | /// 过期选项,如果不指定,则在配置的默认时间后滑动过期 39 | public static async Task SetAsync(this IDistributedCache cache, string key, object value, DistributedCacheEntryOptions options = null) 40 | { 41 | if (options == null) 42 | { 43 | options = new DistributedCacheEntryOptions(); 44 | } 45 | 46 | options.SlidingExpiration = TimeSpan.FromSeconds(DefaultSlidingExpiration + random.Next(10)); 47 | 48 | var jsonStr = await Task.Factory.StartNew(() => JsonConvert.SerializeObject(value)); 49 | 50 | await cache.SetStringAsync(key, jsonStr, options); 51 | } 52 | 53 | /// 54 | /// 从缓存中获取json字符串并转成指定类型对象返回 55 | /// 56 | /// 57 | /// 58 | /// 59 | /// 如果没有取到返回的默认值 60 | /// 61 | public static T Get(this IDistributedCache cache, string key,T defaultValue=default) 62 | { 63 | var jsonStr = cache.GetString(key); 64 | return string.IsNullOrWhiteSpace(jsonStr) ? defaultValue : JsonConvert.DeserializeObject(jsonStr); 65 | } 66 | 67 | /// 68 | /// 从缓存中获取json字符串并转成指定类型对象返回 69 | /// 70 | /// 71 | /// 72 | /// 73 | /// 如果没有取到返回的默认值 74 | /// 75 | public static async Task GetAsync(this IDistributedCache cache, string key, T defaultValue = default) 76 | { 77 | var jsonStr =await cache.GetStringAsync(key); 78 | 79 | if (string.IsNullOrWhiteSpace(jsonStr)) 80 | { 81 | return defaultValue; 82 | } 83 | 84 | return await Task.Factory.StartNew(() => JsonConvert.DeserializeObject(jsonStr)); 85 | } 86 | 87 | /// 88 | /// 指定的key是否存在 89 | /// 90 | /// 91 | /// 92 | /// 93 | public static async Task ExistsAsync(this IDistributedCache cache, string key) 94 | { 95 | var bytes = await cache.GetAsync(key); 96 | 97 | return bytes != null && bytes.Length > 0; 98 | } 99 | 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /Framework/TemplateT4/FluentApiConfig/DbModelConfigPartial.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Repository.DbModel; 2 | using System; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Security.Cryptography.X509Certificates; 6 | using System.Text; 7 | 8 | namespace Sunny.TemplateT4.FluentApiConfig 9 | { 10 | public partial class DbModelConfig 11 | { 12 | 13 | public Type DbModelType { get; set; } 14 | 15 | public string ConfigNamespace { get; set; } 16 | 17 | private string InheritBaseModel() 18 | { 19 | var baseFields = typeof(BaseModel).GetProperties(); 20 | 21 | //当前类型的所有公共属性 22 | var allFields = DbModelType.GetProperties(); 23 | 24 | //排除BaseModel的属性 25 | var selfFields = allFields.Where(x => !baseFields.Any(dx => { return dx.Name == x.Name; })); 26 | 27 | //排除导航属性字段,这些字段是以关系的形式处理 28 | var simpleTypeFields = selfFields.Where(x => !x.PropertyType.IsGenericType && !typeof(IDbModel).IsAssignableFrom(x.PropertyType)); 29 | 30 | 31 | StringBuilder sb = new StringBuilder(); 32 | sb.Clear(); 33 | sb.AppendLine(GetFieldConfig(baseFields.Where(x => x.Name == "Id").First())); 34 | simpleTypeFields.ToList().ForEach(x => sb.AppendLine(GetFieldConfig(x))); 35 | //排除在子类中被private的字段 36 | baseFields.Where(x => x.Name != "Id" && allFields.Any(n => { return n.Name == x.Name; })).ToList().ForEach(x => sb.AppendLine(GetFieldConfig(x))); 37 | 38 | return sb.ToString(); 39 | } 40 | 41 | private string OtherModel() 42 | { 43 | //当前类型的所有公共属性 44 | var allFields = DbModelType.GetProperties(); 45 | 46 | //排除导航属性字段,这些字段是以关系的形式处理 47 | var simpleTypeFields = allFields.Where(x => !x.PropertyType.IsGenericType && !typeof(IDbModel).IsAssignableFrom(x.PropertyType)); 48 | 49 | StringBuilder sb = new StringBuilder(); 50 | simpleTypeFields.ToList().ForEach(x => sb.AppendLine(GetFieldConfig(x))); 51 | 52 | return sb.ToString(); 53 | } 54 | 55 | public string GetTableConfig() 56 | { 57 | var queryFilterConfig = ""; 58 | 59 | //是否有软删除字段 60 | var hasIsDelete = DbModelType.GetProperties().Count(x => x.Name == "IsDelete") > 0; 61 | 62 | if (hasIsDelete) 63 | { 64 | queryFilterConfig = ".HasQueryFilter(x=> !x.IsDelete)"; 65 | } 66 | var tabConfig = $"builder.ToTable(\"{DbModelType.Name.UpperCharToUnderLine()}\"){queryFilterConfig};"; 67 | return tabConfig; 68 | } 69 | 70 | public string GetFieldsConfig() 71 | { 72 | if (typeof(BaseModel).IsAssignableFrom(DbModelType)) 73 | { 74 | return InheritBaseModel(); 75 | } 76 | else 77 | { 78 | return OtherModel(); 79 | } 80 | } 81 | 82 | 83 | private string GetFieldConfig(PropertyInfo pi) 84 | { 85 | 86 | string originName = pi.Name; 87 | 88 | string destName = originName.UpperCharToUnderLine(); 89 | 90 | string fieldConfig = $"builder.Property(x => x.{originName}).HasColumnName(\"{ destName}\")"; 91 | 92 | if (originName == "CreateTime") 93 | { 94 | fieldConfig += ".HasDefaultValueSql(\"now()\")"; 95 | } 96 | 97 | if (originName == "UpdateTime") 98 | { 99 | fieldConfig += ".IsRowVersion()"; 100 | } 101 | 102 | if (pi.PropertyType == typeof(String)) 103 | { 104 | fieldConfig += ".HasMaxLength(30).IsRequired()"; 105 | } 106 | if (pi.PropertyType == typeof(Decimal)) 107 | { 108 | fieldConfig += ".HasColumnType(\"decimal(18, 2)\")"; 109 | } 110 | 111 | return fieldConfig + ";"; 112 | 113 | } 114 | 115 | 116 | public DbModelConfig(Type dbModelType, string configNamespace) 117 | { 118 | this.DbModelType = dbModelType; 119 | this.ConfigNamespace = configNamespace; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Framework/Sunny.Api/Sunny.Api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sunny.Api 5 | 6 | 7 | 8 | 9 | 返回一个动态类型的值,通常用于想返回扩展类型的场景,比如在源对象上增加一个枚举值描述的动态属性 10 | 11 | 12 | 13 | 14 | 15 | 16 | 带返回数据的结果 17 | 18 | 返回的类型 19 | 20 | 21 | 22 | 不带返回数据的Result 23 | 如果要给前端返回数据时,请用泛型版本,这样看API时就知道返回的数据长什么样子 24 | 25 | 26 | 27 | 28 | 需要验证的实体请继承自这个类 29 | 30 | 31 | 32 | 33 | 34 | 业务异常中间件 35 | 36 | 37 | 38 | 39 | 处理业务异常 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 向前端输出信息 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 特殊的返回结果类,和IResult所定义的不同之处在于字段全是小写的,因为发生异常时,后边的中间件不会被调用,所以没法将大驼峰转小驼峰再返回给前端 56 | 57 | 58 | 59 | 60 | 处理业务异常 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 向前端输出信息 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 任务信息 77 | 78 | 79 | 80 | 81 | 任务的名称 82 | 83 | 84 | 85 | 86 | 任务的描述 87 | 88 | 89 | 90 | 91 | 任务内容 92 | 93 | 94 | 95 | 96 | 初始化DiHelper中的ServiceProvider 97 | 98 | 99 | 100 | 101 | 102 | 启用Job 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Id/SnowFlake.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Sunny.Common.Id 6 | { 7 | public class Snowflake 8 | { 9 | 10 | //基准时间 11 | private static long StartStmp = 1539837182162L; 12 | 13 | /*每一部分占用的位数*/ 14 | //机器标识位数 15 | const int MachineIdBits = 5; 16 | //数据标志位数 17 | const int DatacenterIdBits = 5; 18 | //序列号识位数 19 | const int SequenceBits = 12; 20 | 21 | /* 每一部分的最大值*/ 22 | //机器ID最大值 23 | const long MaxMachineNum = -1L ^ (-1L << MachineIdBits); 24 | //数据标志ID最大值 25 | const long MaxDatacenterNum = -1L ^ (-1L << DatacenterIdBits); 26 | //序列号ID最大值 27 | private const long MaxSequenceNum = -1L ^ (-1L << SequenceBits); 28 | 29 | /*每一部分向左的位移*/ 30 | //机器ID偏左移12位 31 | private const int MachineShift = SequenceBits; 32 | //数据ID偏左移17位 33 | private const int DatacenterIdShift = SequenceBits + MachineIdBits; 34 | //时间毫秒左移22位 35 | public const int TimestampLeftShift = SequenceBits + MachineIdBits + DatacenterIdBits; 36 | 37 | 38 | private long _sequence = 0L;//序列号 39 | private long _lastTimestamp = -1L;//上一次时间戳 40 | public long MachineId { get; protected set; }//机器标识 41 | public long DatacenterId { get; protected set; }//数据中心 42 | //public long Sequence = 0L;//序列号 43 | //{ 44 | // get { return _sequence; } 45 | // internal set { _sequence = value; } 46 | //} 47 | 48 | private readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 49 | private readonly object _lock = new Object(); 50 | public Snowflake(long machineId, long datacenterId) 51 | { 52 | // 如果超出范围就抛出异常 53 | if (machineId > MaxMachineNum || machineId < 0) 54 | { 55 | throw new ArgumentException(string.Format("machineId 必须大于0,MaxMachineNum: {0}", MaxMachineNum)); 56 | } 57 | 58 | if (datacenterId > MaxDatacenterNum || datacenterId < 0) 59 | { 60 | throw new ArgumentException(string.Format("datacenterId必须大于0,且不能大于MaxDatacenterNum: {0}", MaxDatacenterNum)); 61 | } 62 | 63 | //先检验再赋值 64 | MachineId = machineId; 65 | DatacenterId = datacenterId; 66 | //_sequence = sequence; 67 | } 68 | 69 | //public static Init(long machineId, long datacenterId) 70 | //{ 71 | 72 | //} 73 | public long NextId() 74 | { 75 | lock (_lock) 76 | { 77 | var timestamp = TimeGen(); 78 | if (timestamp < _lastTimestamp) 79 | { 80 | throw new Exception(string.Format("时间戳必须大于上一次生成ID的时间戳. 拒绝为{0}毫秒生成id", _lastTimestamp - timestamp)); 81 | } 82 | 83 | //如果上次生成时间和当前时间相同,在同一毫秒内 84 | if (_lastTimestamp == timestamp) 85 | { 86 | //sequence自增,和sequenceMask相与一下,去掉高位 87 | _sequence = (_sequence + 1) & MaxSequenceNum; 88 | //判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0 89 | if (_sequence == 0L) 90 | { 91 | //等待到下一毫秒 92 | timestamp = TilNextMillis(_lastTimestamp); 93 | } 94 | } 95 | else 96 | { 97 | //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加, 98 | //为了保证尾数随机性更大一些,最后一位可以设置一个随机数 99 | _sequence = 0L;//new Random().Next(10); 100 | } 101 | 102 | _lastTimestamp = timestamp; 103 | return ((timestamp - StartStmp) << TimestampLeftShift) | (DatacenterId << DatacenterIdShift) | (MachineId << MachineShift) | _sequence; 104 | } 105 | } 106 | 107 | // 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势. 108 | protected virtual long TilNextMillis(long lastTimestamp) 109 | { 110 | var timestamp = TimeGen(); 111 | while (timestamp <= lastTimestamp) 112 | { 113 | timestamp = TimeGen(); 114 | } 115 | return timestamp; 116 | } 117 | 118 | // 获取当前的时间戳 119 | protected virtual long TimeGen() 120 | { 121 | //return TimeExtensions.CurrentTimeMillis(); 122 | return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/ApiDemo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ApiDemo 5 | 6 | 7 | 8 | 9 | 不需要验证登录即可访问的API 10 | 11 | 12 | 13 | 14 | 设置Token,使以/api开头的方法可以通过验证 15 | 16 | 17 | 18 | 19 | 20 | 设置Token,使以/api开头的方法可以通过验证 21 | 22 | 23 | 24 | 25 | 26 | 测试异常日志记录 27 | 28 | 29 | 30 | 31 | 32 | Session测试 33 | 34 | 35 | 36 | 37 | 38 | Redis测试 39 | 40 | 41 | 42 | 43 | 44 | 不带返回值的成功场景测试 45 | 46 | 47 | 48 | 49 | 50 | 带返回值的成功场景测试,测试模型验证 51 | 52 | 53 | 54 | 55 | 56 | 带返回值,且值为动态扩展对象的场景测试,分页测试 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 带查询条件分页测试 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 失败返回值测试 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 路由测试 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 模型绑定测试 89 | 90 | 91 | 92 | 93 | 94 | 95 | AutoMapper测试 96 | 97 | 98 | 99 | 100 | 101 | 随机返回成功和失败测试 102 | 103 | 104 | 105 | 106 | 107 | 模型验证测试 108 | 109 | 110 | 111 | 112 | 113 | 114 | 数据存取和日志记录测试 115 | 116 | 117 | 118 | 119 | 120 | 地址 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/Security/SecurityHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | 6 | namespace Sunny.Common.Helper 7 | { 8 | ///安全策略 9 | public static class SecurityHelper 10 | { 11 | private static DESCryptoServiceProvider descryptoServiceProvider = null; 12 | static SecurityHelper() 13 | { 14 | descryptoServiceProvider = new DESCryptoServiceProvider(); 15 | } 16 | 17 | /// 18 | /// DES加密字符串 19 | /// 20 | /// 源字符串 21 | /// 用于加解密的key 22 | /// 加密后的字符串 23 | public static string DesEncrypt(string input, string key) 24 | { 25 | try 26 | { 27 | byte[] inputByteArray; 28 | inputByteArray = Encoding.Default.GetBytes(input); 29 | descryptoServiceProvider.Key = ASCIIEncoding.ASCII.GetBytes(key.Substring(0, 8)); 30 | descryptoServiceProvider.IV = ASCIIEncoding.ASCII.GetBytes(key.Substring(0, 8)); 31 | MemoryStream memoryStream = new MemoryStream(); 32 | CryptoStream cryptoStream = new CryptoStream(memoryStream, descryptoServiceProvider.CreateEncryptor(), CryptoStreamMode.Write); 33 | cryptoStream.Write(inputByteArray, 0, inputByteArray.Length); 34 | cryptoStream.FlushFinalBlock(); 35 | StringBuilder ret = new StringBuilder(); 36 | foreach (byte b in memoryStream.ToArray()) 37 | { 38 | ret.AppendFormat("{0:X2}", b); 39 | } 40 | return ret.ToString(); 41 | } 42 | catch (Exception ex) 43 | { 44 | throw new Exception("给字符串" + input + "加密时出现异常:" + ex); 45 | } 46 | 47 | } 48 | 49 | /// 50 | /// 解密DES字符串 51 | /// 52 | /// DES字符串 53 | /// 用于加解密的key 54 | /// 解密后的字符串 55 | public static string DesDecrypt(string DESString, string key) 56 | { 57 | 58 | 59 | try 60 | { 61 | int len = DESString.Length / 2; 62 | byte[] inputByteArray = new byte[len]; 63 | int x, i; 64 | for (x = 0; x < len; x++) 65 | { 66 | i = Convert.ToInt32(DESString.Substring(x * 2, 2), 16); 67 | inputByteArray[x] = (byte)i; 68 | } 69 | descryptoServiceProvider.Key = ASCIIEncoding.ASCII.GetBytes(key.Substring(0, 8)); 70 | descryptoServiceProvider.IV = ASCIIEncoding.ASCII.GetBytes(key.Substring(0, 8)); 71 | MemoryStream ms = new MemoryStream(); 72 | CryptoStream cs = new CryptoStream(ms, descryptoServiceProvider.CreateDecryptor(), CryptoStreamMode.Write); 73 | cs.Write(inputByteArray, 0, inputByteArray.Length); 74 | cs.FlushFinalBlock(); 75 | return Encoding.Default.GetString(ms.ToArray()); 76 | } 77 | catch (Exception ex) 78 | { 79 | throw new Exception("解密DES字符串" + DESString + "时出现异常:" + ex); 80 | } 81 | 82 | } 83 | 84 | /// 85 | /// 以MD5的方式加密字符串 86 | /// 87 | /// 源字符串 88 | /// 加密后的MD5格式字符串 89 | public static string MD5Encrypt(string input) 90 | { 91 | try 92 | { 93 | using (var md5 = MD5.Create()) 94 | { 95 | var result = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); 96 | var strResult = BitConverter.ToString(result); 97 | return strResult.Replace("-", ""); 98 | } 99 | } 100 | catch (Exception ex) 101 | { 102 | throw new Exception("将字符串加密成MD5格式时,出现异常:" + ex); 103 | } 104 | 105 | } 106 | 107 | 108 | /// 109 | /// 对字符串进行SHA1加密 110 | /// 111 | /// 需要加密的字符串 112 | /// 密文 113 | public static string SHA1Encrypt(string input) 114 | { 115 | 116 | try 117 | { 118 | byte[] StrRes = Encoding.Default.GetBytes(input); 119 | HashAlgorithm iSHA = new SHA1CryptoServiceProvider(); 120 | StrRes = iSHA.ComputeHash(StrRes); 121 | StringBuilder EnText = new StringBuilder(); 122 | foreach (byte iByte in StrRes) 123 | { 124 | EnText.AppendFormat("{0:x2}", iByte); 125 | } 126 | return EnText.ToString(); 127 | } 128 | catch (Exception ex) 129 | { 130 | throw new Exception("将字符串加密成SHA1格式时,出现异常:" + ex); 131 | } 132 | 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Log/NetLoggerProcessor.cs: -------------------------------------------------------------------------------- 1 | using Sunny.Common.ConfigOption; 2 | using Sunny.Common.Helper; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Sunny.Common.Log 9 | { 10 | class NetLoggerProcessor : IDisposable 11 | { 12 | private const int _maxQueuedMessages = 1024; 13 | 14 | private readonly BlockingCollection _messageQueue = new BlockingCollection(_maxQueuedMessages); 15 | private readonly Thread _outputThread; 16 | private readonly NetLoggerOption option; 17 | 18 | 19 | public NetLoggerProcessor(NetLoggerOption option) 20 | { 21 | this.option = option; 22 | // Start Console message queue processor 23 | _outputThread = new Thread(ProcessLogQueue) 24 | { 25 | IsBackground = true, 26 | Name = "Net logger queue processing thread" 27 | }; 28 | _outputThread.Start(); 29 | } 30 | 31 | public virtual void EnqueueMessage(LogData message) 32 | { 33 | if (!_messageQueue.IsAddingCompleted) 34 | { 35 | try 36 | { 37 | _messageQueue.Add(message); 38 | return; 39 | } 40 | catch (InvalidOperationException ex) { WriteOffLineLog("AddLogQueue Err", ex); } 41 | } 42 | 43 | // Adding is completed so just log the message 44 | WriteMessage(message); 45 | } 46 | 47 | // for testing 48 | internal virtual async void WriteMessage(LogData log) 49 | { 50 | //Console.WriteLine($"NetXXX:Message{message.Message},Level:{message.LevelString}") 51 | 52 | await AddLog(log.LevelString, log.Message, null, log.Exception?.StackTrace); 53 | } 54 | 55 | 56 | 57 | 58 | private async Task AddLog(string logLevel, string logMessage, string attData, string stackInfo) 59 | { 60 | bool flag = false; 61 | 62 | var jsonObj = new 63 | { 64 | systemId = option.SystemId, 65 | logLevel = logLevel, 66 | logMessage = logMessage, 67 | attData = attData, 68 | // stackInfo = stackInfo 如果是异常,消息里已经包含了堆栈了,不用再添加 69 | }; 70 | string json = JsonHelper.ToJsonString(jsonObj); 71 | try 72 | { 73 | var res = await NetHelper.PostWithJson(option.Url, json); 74 | OPResult opRes = JsonHelper.FromJsonString(res); 75 | if (opRes.State == 1) 76 | { 77 | flag = true; 78 | } 79 | else 80 | { 81 | WriteOffLineLog(json); 82 | } 83 | } 84 | catch (Exception ex) 85 | { 86 | WriteOffLineLog(json, ex); 87 | } 88 | 89 | return flag; 90 | 91 | } 92 | 93 | 94 | 95 | private void WriteOffLineLog(string originalLogInfo, Exception offlineExInfo = null) 96 | { 97 | 98 | try 99 | { 100 | string offlineFilePath = $@"{option.OfflineLogPath}\{DateTime.Now.ToString("yyyy-MM-dd")}.txt"; 101 | 102 | string data = $"Time:{DateTime.Now.ToNormalString()}\r\n\r\nOriginalLogInfo:{originalLogInfo}"; 103 | if (offlineExInfo != null) 104 | { 105 | data += $"\r\n\r\nOfflineExceptionInfo:{$"Message:{offlineExInfo.Message},Stack:{offlineExInfo.StackTrace}"}"; 106 | } 107 | data += "\r\n\r\n\r\n\r\n\r\n"; 108 | 109 | FileHelper.WriteFile(offlineFilePath, data); 110 | 111 | } 112 | catch (Exception ex) 113 | { 114 | Console.WriteLine(JsonHelper.ToJsonString(ex)); 115 | 116 | } 117 | 118 | } 119 | 120 | private class OPResult 121 | { 122 | public int State { get; set; } 123 | 124 | public object Data { get; set; } 125 | 126 | } 127 | 128 | 129 | 130 | 131 | private void ProcessLogQueue() 132 | { 133 | try 134 | { 135 | foreach (var message in _messageQueue.GetConsumingEnumerable()) 136 | { 137 | WriteMessage(message); 138 | } 139 | } 140 | catch 141 | { 142 | try 143 | { 144 | _messageQueue.CompleteAdding(); 145 | } 146 | catch (Exception ex) { WriteOffLineLog("AddLogQueue Err", ex); } 147 | } 148 | } 149 | 150 | public void Dispose() 151 | { 152 | _messageQueue.CompleteAdding(); 153 | 154 | try 155 | { 156 | _outputThread.Join(1500); // with timeout in-case Console is locked by user input 157 | } 158 | catch (ThreadStateException) { } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Sunny.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sunny.Common", "Framework\Sunny.Common\Sunny.Common.csproj", "{430C45D0-09D8-42FA-88B4-526385BFAE8F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sunny.Repository", "Framework\Sunny.Repository\Sunny.Repository.csproj", "{1F8A0634-DAFC-49FC-9E4E-BF93A4BE1683}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Framework", "Framework", "{25C48BCE-DBD6-4261-A047-AA8AB47F5EA6}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sunny.Api", "Framework\Sunny.Api\Sunny.Api.csproj", "{913AE651-2048-4BE1-9199-63C8AB3B8299}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sunny.TemplateT4", "Framework\TemplateT4\Sunny.TemplateT4.csproj", "{1B813AAE-92C9-4AE5-869C-87AC1B3519CD}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UseDemo", "UseDemo", "{4267E4AE-CBE4-4D18-A4B5-078FD2ED6719}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiDemo", "UseDemo\ApiDemo\ApiDemo.csproj", "{BF2B2FBD-12B0-41BC-ABDD-A39400062361}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryDemo", "UseDemo\RepositoryDemo\RepositoryDemo.csproj", "{A3E24D59-31A3-4E6C-B0DC-EE0642E815A7}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceDemo", "UseDemo\ServiceDemo\ServiceDemo.csproj", "{910654BD-EAD7-4887-9F0C-8764FD9EF097}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemplateT4Demo", "UseDemo\TemplateT4Demo\TemplateT4Demo.csproj", "{927A980D-14DD-4EB1-BB7B-1BBE529C7ECD}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {430C45D0-09D8-42FA-88B4-526385BFAE8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {430C45D0-09D8-42FA-88B4-526385BFAE8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {430C45D0-09D8-42FA-88B4-526385BFAE8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {430C45D0-09D8-42FA-88B4-526385BFAE8F}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {1F8A0634-DAFC-49FC-9E4E-BF93A4BE1683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {1F8A0634-DAFC-49FC-9E4E-BF93A4BE1683}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {1F8A0634-DAFC-49FC-9E4E-BF93A4BE1683}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {1F8A0634-DAFC-49FC-9E4E-BF93A4BE1683}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {913AE651-2048-4BE1-9199-63C8AB3B8299}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {913AE651-2048-4BE1-9199-63C8AB3B8299}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {913AE651-2048-4BE1-9199-63C8AB3B8299}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {913AE651-2048-4BE1-9199-63C8AB3B8299}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {1B813AAE-92C9-4AE5-869C-87AC1B3519CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {1B813AAE-92C9-4AE5-869C-87AC1B3519CD}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {1B813AAE-92C9-4AE5-869C-87AC1B3519CD}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {1B813AAE-92C9-4AE5-869C-87AC1B3519CD}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {BF2B2FBD-12B0-41BC-ABDD-A39400062361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {BF2B2FBD-12B0-41BC-ABDD-A39400062361}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {BF2B2FBD-12B0-41BC-ABDD-A39400062361}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {BF2B2FBD-12B0-41BC-ABDD-A39400062361}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {A3E24D59-31A3-4E6C-B0DC-EE0642E815A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {A3E24D59-31A3-4E6C-B0DC-EE0642E815A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {A3E24D59-31A3-4E6C-B0DC-EE0642E815A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {A3E24D59-31A3-4E6C-B0DC-EE0642E815A7}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {910654BD-EAD7-4887-9F0C-8764FD9EF097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {910654BD-EAD7-4887-9F0C-8764FD9EF097}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {910654BD-EAD7-4887-9F0C-8764FD9EF097}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {910654BD-EAD7-4887-9F0C-8764FD9EF097}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {927A980D-14DD-4EB1-BB7B-1BBE529C7ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {927A980D-14DD-4EB1-BB7B-1BBE529C7ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {927A980D-14DD-4EB1-BB7B-1BBE529C7ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {927A980D-14DD-4EB1-BB7B-1BBE529C7ECD}.Release|Any CPU.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(NestedProjects) = preSolution 69 | {430C45D0-09D8-42FA-88B4-526385BFAE8F} = {25C48BCE-DBD6-4261-A047-AA8AB47F5EA6} 70 | {1F8A0634-DAFC-49FC-9E4E-BF93A4BE1683} = {25C48BCE-DBD6-4261-A047-AA8AB47F5EA6} 71 | {913AE651-2048-4BE1-9199-63C8AB3B8299} = {25C48BCE-DBD6-4261-A047-AA8AB47F5EA6} 72 | {1B813AAE-92C9-4AE5-869C-87AC1B3519CD} = {25C48BCE-DBD6-4261-A047-AA8AB47F5EA6} 73 | {BF2B2FBD-12B0-41BC-ABDD-A39400062361} = {4267E4AE-CBE4-4D18-A4B5-078FD2ED6719} 74 | {A3E24D59-31A3-4E6C-B0DC-EE0642E815A7} = {4267E4AE-CBE4-4D18-A4B5-078FD2ED6719} 75 | {910654BD-EAD7-4887-9F0C-8764FD9EF097} = {4267E4AE-CBE4-4D18-A4B5-078FD2ED6719} 76 | {927A980D-14DD-4EB1-BB7B-1BBE529C7ECD} = {4267E4AE-CBE4-4D18-A4B5-078FD2ED6719} 77 | EndGlobalSection 78 | GlobalSection(ExtensibilityGlobals) = postSolution 79 | SolutionGuid = {CFBAF3FC-372F-4113-B957-F789D18BD6BE} 80 | EndGlobalSection 81 | EndGlobal 82 | -------------------------------------------------------------------------------- /UseDemo/ApiDemo/Startup.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using FluentValidation.AspNetCore; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Caching.Distributed; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Newtonsoft.Json; 11 | using Newtonsoft.Json.Serialization; 12 | using Quartz; 13 | using Quartz.Impl; 14 | using RepositoryDemo; 15 | using Sunny.Api.Midware; 16 | using Sunny.Common.ConfigOption; 17 | using Sunny.Common.Helper; 18 | using Sunny.Common.JsonTypeConverter; 19 | using Swashbuckle.AspNetCore.Swagger; 20 | using System.IO; 21 | 22 | namespace ApiDemo 23 | { 24 | public class Startup 25 | { 26 | public Startup(IConfiguration configuration) 27 | { 28 | var builder = new ConfigurationBuilder() 29 | .SetBasePath(Directory.GetCurrentDirectory()) 30 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); 31 | 32 | Configuration = builder.Build(); 33 | 34 | } 35 | 36 | public IConfiguration Configuration { get; } 37 | 38 | 39 | // This method gets called by the runtime. Use this method to add services to the container. 40 | public void ConfigureServices(IServiceCollection services) 41 | { 42 | DiHelper.AutoRegister(services); 43 | 44 | var connection = Configuration.GetConnectionString("MySql"); 45 | services.AddDbContext(options => 46 | options.UseMySql(connection)); 47 | 48 | ////同时使用多个数据库的DEMO 49 | //var connection2 = Configuration.GetConnectionString("MySq2"); 50 | //services.AddDbContext(options => 51 | // options.UseMySql(connection2)); 52 | 53 | //services.Configure(Configuration.GetSection("ConfigOptions:NetLoggerOption")); 54 | 55 | //根据需要在这里配置要使用的Option,然后在要使用的地方通过构造器注入IOptions得到TOption 56 | services.Configure(Configuration.GetSection("SunnyOptions:TokenValidateOption")); 57 | services.Configure(Configuration.GetSection("SunnyOptions:MailOption")); 58 | services.Configure(Configuration.GetSection("SunnyOptions:IpInfoQueryOption")); 59 | //services.Configure(Configuration.GetSection("SunnyOptions:SmsOption")); 60 | 61 | services.AddMvcCore() 62 | .AddFluentValidation() 63 | .AddJsonFormatters(x => 64 | { 65 | x.Converters.Add(new LongConverter()); 66 | x.Converters.Add(new DecimalConverter()); 67 | x.DateFormatString = "yyyy-MM-dd HH:mm:ss"; 68 | x.ContractResolver = new CamelCasePropertyNamesContractResolver(); 69 | x.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; 70 | }) 71 | .AddCors() 72 | .AddFormatterMappings() 73 | .AddDataAnnotations() 74 | .AddApiExplorer(); 75 | 76 | services.AddAutoMapper(); 77 | services.AddDistributedRedisCache(options => 78 | { 79 | var configOption = Configuration.GetSection("SunnyOptions:RedisOptions").Get(); 80 | options.Configuration = configOption.ConnectionString; 81 | options.InstanceName = configOption.InstanceName; 82 | IDistributedCacheExtend.DefaultSlidingExpiration = configOption.DefaultSlidingExpiration; 83 | }); 84 | services.AddSession(); 85 | services.AddSingleton();//注册ISchedulerFactory的实例。 86 | services.AddSwaggerGen(c => 87 | { 88 | c.SwaggerDoc("v1", new Info { Title = "ApiDemo Project Swagger API", Version = "v1" }); 89 | // 为 Swagger JSON and UI设置xml文档注释路径 90 | var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径) 91 | var xmlPath = Path.Combine(basePath, "ApiDemo.xml"); 92 | c.IncludeXmlComments(xmlPath); 93 | }); 94 | 95 | } 96 | 97 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 98 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ISchedulerFactory schedulerFactory) 99 | { 100 | app.InitServiceProvider(); 101 | app.EnableJob(Configuration, schedulerFactory); 102 | loggerFactory.AddConsoleLoggerUseDefaultFilter(); 103 | loggerFactory.AddNetLoggerUseDefaultFilter(Configuration.GetSection("SunnyOptions:NetLoggerOption").Get()); 104 | IdHelper.InitSnowflake(Configuration.GetSection("SunnyOptions:SnowflakeOption").Get()); 105 | 106 | 107 | if (env.IsDevelopment()) 108 | { 109 | app.UseDeveloperExceptionPage(); 110 | app.UseMiddleware(); 111 | } 112 | else 113 | { 114 | app.UseMiddleware(); 115 | } 116 | 117 | app.UseStaticFiles(); 118 | app.UseSession(); 119 | app.UseMiddleware(); 120 | 121 | app.UseMvc(); 122 | 123 | // Enable middleware to serve generated Swagger as a JSON endpoint. 124 | app.UseSwagger(); 125 | 126 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 127 | // specifying the Swagger JSON endpoint. 128 | app.UseSwaggerUI(c => 129 | { 130 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); 131 | }); 132 | 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Log/NetLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.Logging.Abstractions.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Sunny.Common.Log 8 | { 9 | public class NetLogger : ILogger 10 | { 11 | 12 | 13 | private static readonly string _messagePadding; 14 | private static readonly string _newLineWithMessagePadding; 15 | private readonly NetLoggerProcessor _queueProcessor; 16 | private Func _filter; 17 | 18 | [ThreadStatic] 19 | private static StringBuilder _logBuilder; 20 | 21 | public string Name { get; } 22 | internal IExternalScopeProvider ScopeProvider { get; set; } 23 | 24 | public IDisposable BeginScope(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance; 25 | 26 | 27 | 28 | internal NetLogger(string name, Func filter, IExternalScopeProvider scopeProvider, NetLoggerProcessor loggerProcessor) 29 | { 30 | if (name == null) 31 | { 32 | throw new ArgumentNullException(nameof(name)); 33 | } 34 | 35 | Name = name; 36 | Filter = filter ?? ((category, logLevel) => true); 37 | ScopeProvider = scopeProvider; 38 | _queueProcessor = loggerProcessor; 39 | 40 | } 41 | 42 | public Func Filter 43 | { 44 | get { return _filter; } 45 | set 46 | { 47 | if (value == null) 48 | { 49 | throw new ArgumentNullException(nameof(value)); 50 | } 51 | 52 | _filter = value; 53 | } 54 | } 55 | 56 | private static string GetLogLevelString(LogLevel logLevel) 57 | { 58 | switch (logLevel) 59 | { 60 | case LogLevel.Trace: 61 | return "Trace"; 62 | case LogLevel.Debug: 63 | return "Debug"; 64 | case LogLevel.Information: 65 | return "Info"; 66 | case LogLevel.Warning: 67 | return "Warn"; 68 | case LogLevel.Error: 69 | return "Error"; 70 | case LogLevel.Critical: 71 | return "Fotal"; 72 | default: 73 | throw new ArgumentOutOfRangeException(nameof(logLevel)); 74 | } 75 | 76 | 77 | } 78 | 79 | private void GetScopeInformation(StringBuilder stringBuilder) 80 | { 81 | var scopeProvider = ScopeProvider; 82 | if (scopeProvider != null) 83 | { 84 | var initialLength = stringBuilder.Length; 85 | 86 | scopeProvider.ForEachScope((scope, state) => 87 | { 88 | var (builder, length) = state; 89 | var first = length == builder.Length; 90 | builder.Append(first ? "=> " : " => ").Append(scope); 91 | }, (stringBuilder, initialLength)); 92 | 93 | if (stringBuilder.Length > initialLength) 94 | { 95 | stringBuilder.Insert(initialLength, _messagePadding); 96 | stringBuilder.AppendLine(); 97 | } 98 | } 99 | } 100 | 101 | 102 | public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception) 103 | { 104 | var logBuilder = _logBuilder; 105 | _logBuilder = null; 106 | 107 | if (logBuilder == null) 108 | { 109 | logBuilder = new StringBuilder(); 110 | } 111 | 112 | var logLevelString = GetLogLevelString(logLevel); 113 | // category and event id 114 | logBuilder.Append(logName); 115 | logBuilder.Append($"[eventid={eventId}]"); 116 | 117 | 118 | // scope information 119 | GetScopeInformation(logBuilder); 120 | 121 | if (!string.IsNullOrEmpty(message)) 122 | { 123 | // message 124 | logBuilder.Append(_messagePadding); 125 | 126 | var len = logBuilder.Length; 127 | logBuilder.AppendLine(message); 128 | logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length); 129 | } 130 | 131 | //// Example: 132 | //// System.InvalidOperationException 133 | //// at Namespace.Class.Function() in File:line X 134 | if (exception != null) 135 | { 136 | // exception message 137 | logBuilder.AppendLine(exception.ToString()); 138 | } 139 | 140 | if (logBuilder.Length > 0) 141 | { 142 | var hasLevel = !string.IsNullOrEmpty(logLevelString); 143 | // Queue log message 144 | _queueProcessor.EnqueueMessage(new LogData() 145 | { 146 | Message = logBuilder.ToString(), 147 | LevelString = hasLevel ? logLevelString : null, 148 | Exception = exception 149 | 150 | }); 151 | } 152 | 153 | logBuilder.Clear(); 154 | if (logBuilder.Capacity > 1024) 155 | { 156 | logBuilder.Capacity = 1024; 157 | } 158 | _logBuilder = logBuilder; 159 | } 160 | 161 | public bool IsEnabled(LogLevel logLevel) 162 | { 163 | if (logLevel == LogLevel.None) 164 | { 165 | return false; 166 | } 167 | 168 | return Filter(Name, logLevel); 169 | } 170 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 171 | { 172 | if (!IsEnabled(logLevel)) 173 | { 174 | return; 175 | } 176 | 177 | if (formatter == null) 178 | { 179 | throw new ArgumentNullException(nameof(formatter)); 180 | } 181 | 182 | var message = formatter(state, exception); 183 | 184 | if (!string.IsNullOrEmpty(message) || exception != null) 185 | { 186 | WriteMessage(logLevel, Name, eventId.Id, message, exception); 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/Xml/XMLHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Xml; 5 | 6 | namespace Sunny.Common.Helper 7 | { 8 | public class XMLHelper 9 | { 10 | private static XmlDocument xmlDoc = new XmlDocument(); 11 | private static object locker = new object(); 12 | 13 | /// 14 | /// 获取XML节点的属性值 15 | /// 16 | /// xml文件路径 17 | /// Xpath路径 18 | /// 属性名称 19 | 20 | /// 属性值 21 | public static string GetXmlNodeAttribute(string filePath, string xpath, string key) 22 | { 23 | try 24 | { 25 | lock (locker) 26 | { 27 | xmlDoc.Load(filePath); 28 | XmlNode node = xmlDoc.SelectSingleNode(xpath); 29 | string value = node.Attributes[key].Value; 30 | return value; 31 | } 32 | } 33 | catch (Exception ex) 34 | { 35 | throw new Exception("读取XML文件" + filePath + "的节点" + xpath + "属性" + key + "时出现异常:" + ex); 36 | } 37 | 38 | } 39 | /// 40 | /// 设置XML节点的属性值 41 | /// 42 | /// xml文件路径 43 | /// Xpath路径 44 | /// 属性名称 45 | /// 要赋的值 46 | 47 | /// true表示设置成功 48 | public static bool SetXmlNodeAttribute(string filePath, string xpath, string key, string value) 49 | { 50 | 51 | bool flag = false; 52 | try 53 | { 54 | lock (locker) 55 | { 56 | xmlDoc.Load(filePath); 57 | XmlNode node = xmlDoc.SelectSingleNode(xpath); 58 | node.Attributes[key].Value = value; 59 | xmlDoc.Save(filePath); 60 | flag = true; 61 | } 62 | } 63 | catch (Exception ex) 64 | { 65 | throw new Exception("设置XML文件" + filePath + "的节点" + xpath + "属性" + key + "时出现异常:" + ex); 66 | } 67 | return flag; 68 | } 69 | 70 | 71 | /// 72 | /// 获取xml节点值 73 | /// 74 | /// xml文件 75 | /// xpath访问路径 76 | 77 | /// 节点值 78 | public static String GetXmlNodeValue(string xmlFile, string xpath) 79 | { 80 | 81 | try 82 | { 83 | lock (locker) 84 | { 85 | xmlDoc.Load(xmlFile); 86 | XmlNode node = xmlDoc.SelectSingleNode(xpath); 87 | return node.InnerText; 88 | } 89 | } 90 | catch (Exception ex) 91 | { 92 | throw new Exception("读取XML文件 " + xmlFile + " 的" + xpath + "节点内容时出现异常:" + ex); 93 | } 94 | } 95 | 96 | /// 97 | /// 设置xml节点值 98 | /// 99 | /// xml文件 100 | /// xpath访问路径 101 | /// 要设置的值 102 | 103 | /// true表示设置成功 104 | public static bool SetXmlNodeValue(string xmlFile, string xpath, string value) 105 | { 106 | 107 | bool flag = false; 108 | try 109 | { 110 | lock (locker) 111 | { 112 | xmlDoc.Load(xmlFile); 113 | XmlNode node = xmlDoc.SelectSingleNode(xpath); 114 | node.InnerText = value; 115 | xmlDoc.Save(xmlFile); 116 | flag = true; 117 | } 118 | } 119 | catch (Exception ex) 120 | { 121 | throw new Exception("设置XML文件 " + xmlFile + " 的 " + xpath + " 节点值时出现异常:" + ex); 122 | 123 | } 124 | return flag; 125 | } 126 | 127 | 128 | /// 129 | /// 获取xml节点集合的值 130 | /// 131 | /// xml文件 132 | /// xpath访问路径 133 | 134 | /// 节点值 135 | public static List GetXmlNodeCollectValue(string xmlFile, string xpath) 136 | { 137 | 138 | 139 | try 140 | { 141 | lock (locker) 142 | { 143 | xmlDoc.Load(xmlFile); 144 | XmlNodeList nodes = xmlDoc.SelectNodes(xpath); 145 | List list = new List(); 146 | foreach (XmlNode item in nodes) 147 | { 148 | list.Add(item.InnerText); 149 | } 150 | return list; 151 | } 152 | } 153 | catch (Exception ex) 154 | { 155 | throw new Exception("读取XML文件 " + xmlFile + " 的" + xpath + "节点集合时出现异常:" + ex); 156 | } 157 | } 158 | 159 | /// 160 | /// 获取xml节点集合的2个属性值,封装成Hashtable返回 161 | /// 162 | /// xml文件 163 | /// xpath访问路径 164 | /// 属性值作为Hashtable键的属性名称 165 | /// 属性值作为Hashtable值的属性名称 166 | 167 | /// 节点值 168 | public static Hashtable GetHashtableFromXmlNodeCollectAttr(string xmlFile, string xpath, string keyAttrName, string valueAttrName) 169 | { 170 | 171 | try 172 | { 173 | lock (locker) 174 | { 175 | xmlDoc.Load(xmlFile); 176 | XmlNodeList nodes = xmlDoc.SelectNodes(xpath); 177 | Hashtable ht = new Hashtable(); 178 | foreach (XmlNode item in nodes) 179 | { 180 | ht.Add(item.Attributes[keyAttrName].Value, item.Attributes[valueAttrName].Value); 181 | } 182 | return ht; 183 | } 184 | } 185 | catch (Exception ex) 186 | { 187 | throw new Exception("读取XML文件 " + xmlFile + " 的" + xpath + "节点集合的" + keyAttrName + "," + valueAttrName + "属性时出现异常:" + ex); 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/String/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Sunny.Common.Helper 7 | { 8 | public static class StringHelper 9 | { 10 | /// 11 | /// 获取文件扩展名 12 | /// 13 | /// 14 | /// 15 | public static string GetExName(string fileName) 16 | { 17 | return fileName.Substring(fileName.LastIndexOf('.')); 18 | } 19 | 20 | /// 21 | /// 转为Base64字符串 22 | /// 23 | /// 24 | /// 25 | public static string ToBase64(this string input) 26 | { 27 | if (!string.IsNullOrWhiteSpace(input)) 28 | { 29 | byte[] bytes = Encoding.Default.GetBytes(input); 30 | return Convert.ToBase64String(bytes); 31 | 32 | } 33 | return input; 34 | } 35 | 36 | /// 37 | /// 从base64字符串转成普通字符串 38 | /// 39 | /// 40 | /// 41 | public static string FromBase64(this string input) 42 | { 43 | if (!string.IsNullOrWhiteSpace(input)) 44 | { 45 | byte[] outputb = Convert.FromBase64String(input); 46 | return Encoding.Default.GetString(outputb); 47 | } 48 | return input; 49 | } 50 | 51 | 52 | /// 53 | /// 获取AB两个字符串中最大的相同部分 54 | /// 55 | /// 56 | /// 57 | /// 是否区分大小写 58 | /// 59 | public static List GetMaxSameString(string a, string b, bool diffCaps = true) 60 | { 61 | List res = new List(); 62 | if (!string.IsNullOrWhiteSpace(a) && !string.IsNullOrWhiteSpace(b)) 63 | { 64 | if (a.Length > b.Length) 65 | { 66 | string temp = a; a = b; b = a; 67 | } 68 | List list = new List(); 69 | for (int posA = 0; posA < a.Length; posA++)//将a字符串中的每一个字符作为首字符串进行对比 70 | { 71 | int tempPosA = posA; 72 | StringBuilder sb = new StringBuilder(); 73 | 74 | for (int posB = 0; posB < b.Length; posB++) 75 | { 76 | string tempA = a.Substring(tempPosA, 1); 77 | if (tempPosA == posA)//如果是对比首字符串,那么就用indexof进行快速定位,定位到要开始对比字符串的位置,而不用从头开始逐个对比 78 | { 79 | if (diffCaps) 80 | { 81 | posB = b.IndexOf(tempA, posB); 82 | } 83 | else 84 | { 85 | posB = b.IndexOf(tempA, posB, StringComparison.CurrentCultureIgnoreCase); 86 | } 87 | if (posB == -1) { break; }//如果剩下未对比字符中没有找到以首字符开头的,就退出 88 | } 89 | string tempB = b.Substring(posB, 1); 90 | bool eqRes = false; 91 | if (diffCaps) 92 | { 93 | eqRes = tempA.Equals(tempB); 94 | } 95 | else 96 | { 97 | eqRes = tempA.ToLower().Equals(tempB.ToLower()); 98 | } 99 | if (eqRes)//如果匹配,就接着对比下一字符 100 | { 101 | sb.Append(tempA); 102 | if (tempPosA + 1 < a.Length) 103 | { 104 | ++tempPosA; 105 | } 106 | 107 | } 108 | else//如果不匹配,则-1对比当前字符是否可作为下次对比的首字符 109 | { 110 | tempPosA = posA; 111 | 112 | if (sb.Length > 0) 113 | { 114 | list.Add(sb.ToString()); 115 | sb.Clear(); 116 | --posB; 117 | } 118 | 119 | } 120 | } 121 | } 122 | string maxStr = ""; 123 | foreach (string item in list)//找最大长度的字符 124 | { 125 | if (item.Length > maxStr.Length) 126 | { 127 | maxStr = item; 128 | 129 | } 130 | } 131 | foreach (string item in list)//找与最大长度相等内容不等的字符 132 | { 133 | if (item.Length == maxStr.Length) 134 | { 135 | if (diffCaps) 136 | { 137 | if (!item.Equals(maxStr)) 138 | { 139 | res.Add(item); 140 | } 141 | } 142 | else 143 | { 144 | if (!item.ToLower().Equals(maxStr.ToLower())) 145 | { 146 | res.Add(item); 147 | } 148 | } 149 | 150 | } 151 | } 152 | res.Add(maxStr); 153 | } 154 | return res; 155 | } 156 | 157 | /// 158 | /// 根据长度参数,生成随机字符串 159 | /// 160 | /// 长度 161 | /// 返回随机字符串 162 | public static string CreateValidCode(int codeLen) 163 | { 164 | //下边这个是用于验证码的字符串。 165 | string codeSerial = "2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,S,T,U,V,W,X,Y,Z"; 166 | 167 | string[] arr = codeSerial.Split(','); 168 | 169 | string code = ""; 170 | 171 | int randValue = -1; 172 | 173 | Random rand = new Random(unchecked((int)DateTime.Now.Ticks)); 174 | 175 | for (int i = 0; i < codeLen; i++) 176 | { 177 | randValue = rand.Next(0, arr.Length - 1); 178 | 179 | code += arr[randValue]; 180 | } 181 | 182 | return code; 183 | } 184 | 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/Migrations/20181102012925_v1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace RepositoryDemo.Migrations 6 | { 7 | public partial class v1 : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "category", 13 | columns: table => new 14 | { 15 | id = table.Column(nullable: false) 16 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), 17 | create_time = table.Column(nullable: false), 18 | creater_id = table.Column(nullable: false), 19 | update_time = table.Column(nullable: false) 20 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn), 21 | updater_id = table.Column(nullable: false), 22 | category_name = table.Column(maxLength: 30, nullable: true) 23 | }, 24 | constraints: table => 25 | { 26 | table.PrimaryKey("PK_category", x => x.id); 27 | }); 28 | 29 | migrationBuilder.CreateTable( 30 | name: "IdTest", 31 | columns: table => new 32 | { 33 | Id = table.Column(nullable: false) 34 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), 35 | requestType = table.Column(nullable: false) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_IdTest", x => x.Id); 40 | }); 41 | 42 | migrationBuilder.CreateTable( 43 | name: "passage", 44 | columns: table => new 45 | { 46 | id = table.Column(nullable: false) 47 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), 48 | create_time = table.Column(nullable: false), 49 | creater_id = table.Column(nullable: false), 50 | update_time = table.Column(nullable: false) 51 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn), 52 | updater_id = table.Column(nullable: false), 53 | title = table.Column(maxLength: 30, nullable: true), 54 | last_edit_time = table.Column(nullable: false) 55 | }, 56 | constraints: table => 57 | { 58 | table.PrimaryKey("PK_passage", x => x.id); 59 | }); 60 | 61 | migrationBuilder.CreateTable( 62 | name: "student", 63 | columns: table => new 64 | { 65 | id = table.Column(nullable: false) 66 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), 67 | create_time = table.Column(nullable: false), 68 | creater_id = table.Column(nullable: false), 69 | update_time = table.Column(nullable: false) 70 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn), 71 | updater_id = table.Column(nullable: false), 72 | student_name = table.Column(maxLength: 30, nullable: true), 73 | test = table.Column(maxLength: 30, nullable: true), 74 | a_a2 = table.Column(maxLength: 30, nullable: true), 75 | score = table.Column(type: "decimal(18, 2)", nullable: false) 76 | }, 77 | constraints: table => 78 | { 79 | table.PrimaryKey("PK_student", x => x.id); 80 | }); 81 | 82 | migrationBuilder.CreateTable( 83 | name: "passage_category", 84 | columns: table => new 85 | { 86 | id = table.Column(nullable: false), 87 | create_time = table.Column(nullable: false), 88 | creater_id = table.Column(nullable: false), 89 | update_time = table.Column(nullable: false) 90 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn), 91 | updater_id = table.Column(nullable: false), 92 | category_id = table.Column(nullable: false), 93 | passage_id = table.Column(nullable: false) 94 | }, 95 | constraints: table => 96 | { 97 | table.PrimaryKey("PK_passage_category", x => new { x.passage_id, x.category_id }); 98 | table.ForeignKey( 99 | name: "FK_passage_category_category_category_id", 100 | column: x => x.category_id, 101 | principalTable: "category", 102 | principalColumn: "id", 103 | onDelete: ReferentialAction.Cascade); 104 | table.ForeignKey( 105 | name: "FK_passage_category_passage_passage_id", 106 | column: x => x.passage_id, 107 | principalTable: "passage", 108 | principalColumn: "id", 109 | onDelete: ReferentialAction.Cascade); 110 | }); 111 | 112 | migrationBuilder.CreateTable( 113 | name: "student_address", 114 | columns: table => new 115 | { 116 | id = table.Column(nullable: false) 117 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), 118 | create_time = table.Column(nullable: false), 119 | creater_id = table.Column(nullable: false), 120 | update_time = table.Column(nullable: false) 121 | .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn), 122 | updater_id = table.Column(nullable: false), 123 | address1 = table.Column(maxLength: 30, nullable: true), 124 | zipcode = table.Column(nullable: false), 125 | state = table.Column(maxLength: 30, nullable: true), 126 | country = table.Column(maxLength: 30, nullable: true), 127 | student_id = table.Column(nullable: false) 128 | }, 129 | constraints: table => 130 | { 131 | table.PrimaryKey("PK_student_address", x => x.id); 132 | table.ForeignKey( 133 | name: "FK_student_address_student_student_id", 134 | column: x => x.student_id, 135 | principalTable: "student", 136 | principalColumn: "id", 137 | onDelete: ReferentialAction.Cascade); 138 | }); 139 | 140 | migrationBuilder.CreateIndex( 141 | name: "IX_passage_category_category_id", 142 | table: "passage_category", 143 | column: "category_id"); 144 | 145 | migrationBuilder.CreateIndex( 146 | name: "IX_student_address_student_id", 147 | table: "student_address", 148 | column: "student_id", 149 | unique: true); 150 | } 151 | 152 | protected override void Down(MigrationBuilder migrationBuilder) 153 | { 154 | migrationBuilder.DropTable( 155 | name: "IdTest"); 156 | 157 | migrationBuilder.DropTable( 158 | name: "passage_category"); 159 | 160 | migrationBuilder.DropTable( 161 | name: "student_address"); 162 | 163 | migrationBuilder.DropTable( 164 | name: "category"); 165 | 166 | migrationBuilder.DropTable( 167 | name: "passage"); 168 | 169 | migrationBuilder.DropTable( 170 | name: "student"); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/Migrations/MyDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using RepositoryDemo; 7 | 8 | namespace RepositoryDemo.Migrations 9 | { 10 | [DbContext(typeof(MyDbContext))] 11 | partial class MyDbContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | #pragma warning disable 612, 618 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") 18 | .HasAnnotation("Relational:MaxIdentifierLength", 64); 19 | 20 | modelBuilder.Entity("RepositoryDemo.DbModel.Category", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd() 24 | .HasColumnName("id"); 25 | 26 | b.Property("CategoryName") 27 | .HasColumnName("category_name") 28 | .HasMaxLength(30); 29 | 30 | b.Property("CreateTime") 31 | .HasColumnName("create_time"); 32 | 33 | b.Property("CreaterId") 34 | .HasColumnName("creater_id"); 35 | 36 | b.Property("UpdateTime") 37 | .IsConcurrencyToken() 38 | .ValueGeneratedOnAddOrUpdate() 39 | .HasColumnName("update_time"); 40 | 41 | b.Property("UpdaterId") 42 | .HasColumnName("updater_id"); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.ToTable("category"); 47 | }); 48 | 49 | modelBuilder.Entity("RepositoryDemo.DbModel.IdTest", b => 50 | { 51 | b.Property("Id") 52 | .ValueGeneratedOnAdd(); 53 | 54 | b.Property("requestType"); 55 | 56 | b.HasKey("Id"); 57 | 58 | b.ToTable("IdTest"); 59 | }); 60 | 61 | modelBuilder.Entity("RepositoryDemo.DbModel.Passage", b => 62 | { 63 | b.Property("Id") 64 | .ValueGeneratedOnAdd() 65 | .HasColumnName("id"); 66 | 67 | b.Property("CreateTime") 68 | .HasColumnName("create_time"); 69 | 70 | b.Property("CreaterId") 71 | .HasColumnName("creater_id"); 72 | 73 | b.Property("LastEditTime") 74 | .HasColumnName("last_edit_time"); 75 | 76 | b.Property("Title") 77 | .HasColumnName("title") 78 | .HasMaxLength(30); 79 | 80 | b.Property("UpdateTime") 81 | .IsConcurrencyToken() 82 | .ValueGeneratedOnAddOrUpdate() 83 | .HasColumnName("update_time"); 84 | 85 | b.Property("UpdaterId") 86 | .HasColumnName("updater_id"); 87 | 88 | b.HasKey("Id"); 89 | 90 | b.ToTable("passage"); 91 | }); 92 | 93 | modelBuilder.Entity("RepositoryDemo.DbModel.PassageCategory", b => 94 | { 95 | b.Property("PassageId") 96 | .HasColumnName("passage_id"); 97 | 98 | b.Property("CategoryId") 99 | .HasColumnName("category_id"); 100 | 101 | b.Property("CreateTime") 102 | .HasColumnName("create_time"); 103 | 104 | b.Property("CreaterId") 105 | .HasColumnName("creater_id"); 106 | 107 | b.Property("Id") 108 | .HasColumnName("id"); 109 | 110 | b.Property("UpdateTime") 111 | .IsConcurrencyToken() 112 | .ValueGeneratedOnAddOrUpdate() 113 | .HasColumnName("update_time"); 114 | 115 | b.Property("UpdaterId") 116 | .HasColumnName("updater_id"); 117 | 118 | b.HasKey("PassageId", "CategoryId"); 119 | 120 | b.HasIndex("CategoryId"); 121 | 122 | b.ToTable("passage_category"); 123 | }); 124 | 125 | modelBuilder.Entity("RepositoryDemo.DbModel.Student", b => 126 | { 127 | b.Property("Id") 128 | .ValueGeneratedOnAdd() 129 | .HasColumnName("id"); 130 | 131 | b.Property("AA2") 132 | .HasColumnName("a_a2") 133 | .HasMaxLength(30); 134 | 135 | b.Property("CreateTime") 136 | .HasColumnName("create_time"); 137 | 138 | b.Property("CreaterId") 139 | .HasColumnName("creater_id"); 140 | 141 | b.Property("Score") 142 | .HasColumnName("score") 143 | .HasColumnType("decimal(18, 2)"); 144 | 145 | b.Property("StudentName") 146 | .HasColumnName("student_name") 147 | .HasMaxLength(30); 148 | 149 | b.Property("Test") 150 | .HasColumnName("test") 151 | .HasMaxLength(30); 152 | 153 | b.Property("UpdateTime") 154 | .IsConcurrencyToken() 155 | .ValueGeneratedOnAddOrUpdate() 156 | .HasColumnName("update_time"); 157 | 158 | b.Property("UpdaterId") 159 | .HasColumnName("updater_id"); 160 | 161 | b.HasKey("Id"); 162 | 163 | b.ToTable("student"); 164 | }); 165 | 166 | modelBuilder.Entity("RepositoryDemo.DbModel.StudentAddress", b => 167 | { 168 | b.Property("Id") 169 | .ValueGeneratedOnAdd() 170 | .HasColumnName("id"); 171 | 172 | b.Property("Address1") 173 | .HasColumnName("address1") 174 | .HasMaxLength(30); 175 | 176 | b.Property("Country") 177 | .HasColumnName("country") 178 | .HasMaxLength(30); 179 | 180 | b.Property("CreateTime") 181 | .HasColumnName("create_time"); 182 | 183 | b.Property("CreaterId") 184 | .HasColumnName("creater_id"); 185 | 186 | b.Property("State") 187 | .HasColumnName("state") 188 | .HasMaxLength(30); 189 | 190 | b.Property("StudentId") 191 | .HasColumnName("student_id"); 192 | 193 | b.Property("UpdateTime") 194 | .IsConcurrencyToken() 195 | .ValueGeneratedOnAddOrUpdate() 196 | .HasColumnName("update_time"); 197 | 198 | b.Property("UpdaterId") 199 | .HasColumnName("updater_id"); 200 | 201 | b.Property("Zipcode") 202 | .HasColumnName("zipcode"); 203 | 204 | b.HasKey("Id"); 205 | 206 | b.HasIndex("StudentId") 207 | .IsUnique(); 208 | 209 | b.ToTable("student_address"); 210 | }); 211 | 212 | modelBuilder.Entity("RepositoryDemo.DbModel.PassageCategory", b => 213 | { 214 | b.HasOne("RepositoryDemo.DbModel.Category", "Category") 215 | .WithMany("PassageCategories") 216 | .HasForeignKey("CategoryId") 217 | .OnDelete(DeleteBehavior.Cascade); 218 | 219 | b.HasOne("RepositoryDemo.DbModel.Passage", "Passage") 220 | .WithMany("PassageCategories") 221 | .HasForeignKey("PassageId") 222 | .OnDelete(DeleteBehavior.Cascade); 223 | }); 224 | 225 | modelBuilder.Entity("RepositoryDemo.DbModel.StudentAddress", b => 226 | { 227 | b.HasOne("RepositoryDemo.DbModel.Student", "Student") 228 | .WithOne("Address") 229 | .HasForeignKey("RepositoryDemo.DbModel.StudentAddress", "StudentId") 230 | .OnDelete(DeleteBehavior.Cascade); 231 | }); 232 | #pragma warning restore 612, 618 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /UseDemo/RepositoryDemo/Migrations/20181102012925_v1.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using RepositoryDemo; 8 | 9 | namespace RepositoryDemo.Migrations 10 | { 11 | [DbContext(typeof(MyDbContext))] 12 | [Migration("20181102012925_v1")] 13 | partial class v1 14 | { 15 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") 20 | .HasAnnotation("Relational:MaxIdentifierLength", 64); 21 | 22 | modelBuilder.Entity("RepositoryDemo.DbModel.Category", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd() 26 | .HasColumnName("id"); 27 | 28 | b.Property("CategoryName") 29 | .HasColumnName("category_name") 30 | .HasMaxLength(30); 31 | 32 | b.Property("CreateTime") 33 | .HasColumnName("create_time"); 34 | 35 | b.Property("CreaterId") 36 | .HasColumnName("creater_id"); 37 | 38 | b.Property("UpdateTime") 39 | .IsConcurrencyToken() 40 | .ValueGeneratedOnAddOrUpdate() 41 | .HasColumnName("update_time"); 42 | 43 | b.Property("UpdaterId") 44 | .HasColumnName("updater_id"); 45 | 46 | b.HasKey("Id"); 47 | 48 | b.ToTable("category"); 49 | }); 50 | 51 | modelBuilder.Entity("RepositoryDemo.DbModel.IdTest", b => 52 | { 53 | b.Property("Id") 54 | .ValueGeneratedOnAdd(); 55 | 56 | b.Property("requestType"); 57 | 58 | b.HasKey("Id"); 59 | 60 | b.ToTable("IdTest"); 61 | }); 62 | 63 | modelBuilder.Entity("RepositoryDemo.DbModel.Passage", b => 64 | { 65 | b.Property("Id") 66 | .ValueGeneratedOnAdd() 67 | .HasColumnName("id"); 68 | 69 | b.Property("CreateTime") 70 | .HasColumnName("create_time"); 71 | 72 | b.Property("CreaterId") 73 | .HasColumnName("creater_id"); 74 | 75 | b.Property("LastEditTime") 76 | .HasColumnName("last_edit_time"); 77 | 78 | b.Property("Title") 79 | .HasColumnName("title") 80 | .HasMaxLength(30); 81 | 82 | b.Property("UpdateTime") 83 | .IsConcurrencyToken() 84 | .ValueGeneratedOnAddOrUpdate() 85 | .HasColumnName("update_time"); 86 | 87 | b.Property("UpdaterId") 88 | .HasColumnName("updater_id"); 89 | 90 | b.HasKey("Id"); 91 | 92 | b.ToTable("passage"); 93 | }); 94 | 95 | modelBuilder.Entity("RepositoryDemo.DbModel.PassageCategory", b => 96 | { 97 | b.Property("PassageId") 98 | .HasColumnName("passage_id"); 99 | 100 | b.Property("CategoryId") 101 | .HasColumnName("category_id"); 102 | 103 | b.Property("CreateTime") 104 | .HasColumnName("create_time"); 105 | 106 | b.Property("CreaterId") 107 | .HasColumnName("creater_id"); 108 | 109 | b.Property("Id") 110 | .HasColumnName("id"); 111 | 112 | b.Property("UpdateTime") 113 | .IsConcurrencyToken() 114 | .ValueGeneratedOnAddOrUpdate() 115 | .HasColumnName("update_time"); 116 | 117 | b.Property("UpdaterId") 118 | .HasColumnName("updater_id"); 119 | 120 | b.HasKey("PassageId", "CategoryId"); 121 | 122 | b.HasIndex("CategoryId"); 123 | 124 | b.ToTable("passage_category"); 125 | }); 126 | 127 | modelBuilder.Entity("RepositoryDemo.DbModel.Student", b => 128 | { 129 | b.Property("Id") 130 | .ValueGeneratedOnAdd() 131 | .HasColumnName("id"); 132 | 133 | b.Property("AA2") 134 | .HasColumnName("a_a2") 135 | .HasMaxLength(30); 136 | 137 | b.Property("CreateTime") 138 | .HasColumnName("create_time"); 139 | 140 | b.Property("CreaterId") 141 | .HasColumnName("creater_id"); 142 | 143 | b.Property("Score") 144 | .HasColumnName("score") 145 | .HasColumnType("decimal(18, 2)"); 146 | 147 | b.Property("StudentName") 148 | .HasColumnName("student_name") 149 | .HasMaxLength(30); 150 | 151 | b.Property("Test") 152 | .HasColumnName("test") 153 | .HasMaxLength(30); 154 | 155 | b.Property("UpdateTime") 156 | .IsConcurrencyToken() 157 | .ValueGeneratedOnAddOrUpdate() 158 | .HasColumnName("update_time"); 159 | 160 | b.Property("UpdaterId") 161 | .HasColumnName("updater_id"); 162 | 163 | b.HasKey("Id"); 164 | 165 | b.ToTable("student"); 166 | }); 167 | 168 | modelBuilder.Entity("RepositoryDemo.DbModel.StudentAddress", b => 169 | { 170 | b.Property("Id") 171 | .ValueGeneratedOnAdd() 172 | .HasColumnName("id"); 173 | 174 | b.Property("Address1") 175 | .HasColumnName("address1") 176 | .HasMaxLength(30); 177 | 178 | b.Property("Country") 179 | .HasColumnName("country") 180 | .HasMaxLength(30); 181 | 182 | b.Property("CreateTime") 183 | .HasColumnName("create_time"); 184 | 185 | b.Property("CreaterId") 186 | .HasColumnName("creater_id"); 187 | 188 | b.Property("State") 189 | .HasColumnName("state") 190 | .HasMaxLength(30); 191 | 192 | b.Property("StudentId") 193 | .HasColumnName("student_id"); 194 | 195 | b.Property("UpdateTime") 196 | .IsConcurrencyToken() 197 | .ValueGeneratedOnAddOrUpdate() 198 | .HasColumnName("update_time"); 199 | 200 | b.Property("UpdaterId") 201 | .HasColumnName("updater_id"); 202 | 203 | b.Property("Zipcode") 204 | .HasColumnName("zipcode"); 205 | 206 | b.HasKey("Id"); 207 | 208 | b.HasIndex("StudentId") 209 | .IsUnique(); 210 | 211 | b.ToTable("student_address"); 212 | }); 213 | 214 | modelBuilder.Entity("RepositoryDemo.DbModel.PassageCategory", b => 215 | { 216 | b.HasOne("RepositoryDemo.DbModel.Category", "Category") 217 | .WithMany("PassageCategories") 218 | .HasForeignKey("CategoryId") 219 | .OnDelete(DeleteBehavior.Cascade); 220 | 221 | b.HasOne("RepositoryDemo.DbModel.Passage", "Passage") 222 | .WithMany("PassageCategories") 223 | .HasForeignKey("PassageId") 224 | .OnDelete(DeleteBehavior.Cascade); 225 | }); 226 | 227 | modelBuilder.Entity("RepositoryDemo.DbModel.StudentAddress", b => 228 | { 229 | b.HasOne("RepositoryDemo.DbModel.Student", "Student") 230 | .WithOne("Address") 231 | .HasForeignKey("RepositoryDemo.DbModel.StudentAddress", "StudentId") 232 | .OnDelete(DeleteBehavior.Cascade); 233 | }); 234 | #pragma warning restore 612, 618 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Framework/Sunny.Common/Helper/Di/DIHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyModel; 3 | using Sunny.Common.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace Sunny.Common.Helper 10 | { 11 | public class DiHelper 12 | { 13 | 14 | public static IServiceProvider ServiceProvider { get; set; } 15 | 16 | 17 | /// 18 | /// 创建一个 DI 容器的 Scope 19 | /// 20 | /// 21 | static public IServiceScope CreateScope() 22 | { 23 | var serviceScopeFactory = ServiceProvider.GetRequiredService(); 24 | return serviceScopeFactory.CreateScope(); 25 | } 26 | 27 | /// 28 | /// 获取一个实现了T接口的类对象,如果该类构造函数有其他接口参数,自动注入需要的接口实例 29 | /// 30 | /// 某接口的类型 31 | /// 某接口的实例 32 | static public T GetService() 33 | { 34 | T t = default; 35 | try 36 | { 37 | t = ServiceProvider.GetService(); 38 | } 39 | catch (InvalidOperationException ex) 40 | { 41 | if (ex.Message.Contains("from root provider")) 42 | { 43 | t = CreateScope().ServiceProvider.GetService(); 44 | } 45 | } 46 | 47 | 48 | return t; 49 | } 50 | 51 | /// 52 | /// 从实现ISupportRequiredService接口的IServiceProvider获取实现了T接口的类对象,如果该类构造函数有其他接口参数,自动注入需要的接口实例 53 | /// 54 | /// 某接口的类型 55 | /// 某接口的实例 56 | static public T GetRequiredService() 57 | { 58 | T t = default; 59 | 60 | try 61 | { 62 | t = ServiceProvider.GetRequiredService(); 63 | } 64 | catch (InvalidOperationException ex) 65 | { 66 | if (ex.Message.Contains("from root provider")) 67 | { 68 | t = CreateScope().ServiceProvider.GetRequiredService(); 69 | } 70 | } 71 | 72 | return t; 73 | } 74 | 75 | 76 | 77 | /// 78 | /// 创建一个某类的实例,会自动为该类的构造函数注入需要的接口实例 79 | /// 80 | /// 构造函数参数 81 | /// 通过 DI 构造的实例 82 | static public T CreateInstance(params object[] arguments) 83 | { 84 | T t = default; 85 | 86 | try 87 | { 88 | t = ActivatorUtilities.CreateInstance(ServiceProvider, arguments); 89 | } 90 | catch (InvalidOperationException ex) 91 | { 92 | if (ex.Message.Contains("from root provider")) 93 | { 94 | t = ActivatorUtilities.CreateInstance(CreateScope().ServiceProvider, arguments); 95 | } 96 | 97 | } 98 | return t; 99 | } 100 | 101 | 102 | 103 | 104 | /// 105 | /// 自动为实现了ITransient,IScoped,ISingleton的类型注入实例 106 | /// 107 | /// 108 | static public void AutoRegister(IServiceCollection services) 109 | { 110 | //指定控制台输出编辑为UTF8,不然中文会有乱码 111 | Console.OutputEncoding = System.Text.Encoding.UTF8; 112 | var deps = DependencyContext.Default; 113 | var libs = GetCustomizeCompilationLibraries(); 114 | 115 | foreach (var item in libs) 116 | { 117 | DiHelper.RegisterByAssemblyName(services, item.Name); 118 | } 119 | } 120 | 121 | /// 122 | /// 获取自定义的程序集,排除所有的系统程序集、Nuget下载包的程序集 123 | /// 124 | /// 125 | static public IEnumerable GetCustomizeCompilationLibraries() 126 | { 127 | var deps = DependencyContext.Default; 128 | var list = deps.CompileLibraries.Where(lib => lib.Name.StartsWith("Sunny.")); 129 | return list.Union(deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package")); 130 | } 131 | 132 | 133 | /// 134 | /// 获取自定义类型集合 135 | /// 136 | /// 过虑器 137 | /// 符合过滤条件的类型集合 138 | static public IEnumerable GetCustomizeTypes(Func filter = null) 139 | { 140 | var libs = GetCustomizeCompilationLibraries(); 141 | 142 | List types = new List(); 143 | 144 | foreach (var item in libs) 145 | { 146 | Assembly assembly = Assembly.Load(item.Name); 147 | types.AddRange(assembly.GetTypes().ToList()); 148 | } 149 | return filter == null ? types : types.Where(filter); 150 | 151 | } 152 | 153 | 154 | /// 155 | /// 注册指定程序集的DI 156 | /// 157 | /// 158 | /// 159 | static public void RegisterByAssemblyName(IServiceCollection services, string assemblyName) 160 | { 161 | 162 | //集中注册继承自依赖注入的接口 163 | 164 | var mapping = GetDiClassInterfaceMapping(assemblyName); 165 | 166 | var scoped = mapping.Where(x => typeof(IScoped).IsAssignableFrom(x.Key)); 167 | var transient = mapping.Where(x => typeof(ITransient).IsAssignableFrom(x.Key)); 168 | var singleton = mapping.Where(x => typeof(ISingleton).IsAssignableFrom(x.Key)); 169 | 170 | scoped.ToList().ForEach(x => { x.Value.ToList().ForEach(n => services.AddScoped(n, x.Key)); }); 171 | transient.ToList().ForEach(x => { x.Value.ToList().ForEach(n => services.AddTransient(n, x.Key)); }); 172 | singleton.ToList().ForEach(x => { x.Value.ToList().ForEach(n => services.AddSingleton(n, x.Key)); }); 173 | 174 | //集中注册继承自依赖注入的类 175 | 176 | var mappingClass = GetDiClassMapping(assemblyName); 177 | 178 | var scopedClass = mappingClass.Where(x => typeof(IScoped).IsAssignableFrom(x.Key)); 179 | var transientClass = mappingClass.Where(x => typeof(ITransient).IsAssignableFrom(x.Key)); 180 | var singletonClass = mappingClass.Where(x => typeof(ISingleton).IsAssignableFrom(x.Key)); 181 | 182 | scopedClass.ToList().ForEach(x => services.AddScoped(x.Key)); 183 | transientClass.ToList().ForEach(x => services.AddTransient(x.Key)); 184 | singletonClass.ToList().ForEach(x => services.AddSingleton(x.Key)); 185 | 186 | } 187 | 188 | 189 | /// 190 | /// 获取程序集中继承自依赖注入的接口实现类对应的多个接口 191 | /// 192 | /// 程序集 193 | static public Dictionary GetDiClassInterfaceMapping(string assemblyName) 194 | { 195 | var result = new Dictionary(); 196 | if (!String.IsNullOrEmpty(assemblyName)) 197 | { 198 | Assembly assembly = Assembly.Load(assemblyName); 199 | List ts = assembly.GetTypes().ToList(); 200 | 201 | foreach (var item in ts.Where(s => !s.IsInterface && typeof(IDependency).IsAssignableFrom(s) && !s.GetTypeInfo().IsAbstract)) 202 | { 203 | var interfaceType = item.GetInterfaces().Where(x => x != typeof(IDependency) && x != typeof(IScoped) && x != typeof(ITransient) && x != typeof(ISingleton)); 204 | if(interfaceType.Count()>0) 205 | { 206 | result.Add(item, interfaceType.ToArray()); 207 | } 208 | 209 | } 210 | 211 | } 212 | return result; 213 | } 214 | 215 | 216 | /// 217 | /// 获取程序集中继承自依赖注释接口的类 218 | /// 219 | /// 程序集 220 | static public Dictionary GetDiClassMapping(string assemblyName) 221 | { 222 | var result = new Dictionary(); 223 | if (!String.IsNullOrEmpty(assemblyName)) 224 | { 225 | Assembly assembly = Assembly.Load(assemblyName); 226 | List ts = assembly.GetTypes().ToList(); 227 | 228 | foreach (var item in ts.Where(s => !s.IsInterface && typeof(IDependency).IsAssignableFrom(s) && !s.GetTypeInfo().IsAbstract)) 229 | { 230 | var interfaceType = item.GetInterfaces().Where(x => x != typeof(IDependency)); 231 | if (interfaceType.Count() == 1) 232 | { 233 | var t = interfaceType.First(); 234 | if (t == typeof(IScoped) || t == typeof(ITransient) || t == typeof(ISingleton)) 235 | { 236 | result.Add(item, t); 237 | } 238 | 239 | } 240 | 241 | } 242 | 243 | } 244 | return result; 245 | } 246 | } 247 | } 248 | --------------------------------------------------------------------------------