应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

在Saas系统下多租户零脚本分表分库读写分离解决方案

  前言
  您是否有以下场景:多租户系统,数据库级别隔离大数据量,需要分表分库(动态添加),分库分表全自动维护处理租户之前可能需要使用不同的数据库模式,譬如有些租户要求用oracle,或者mmsql,或者mysql或者pgsql多租户系统在不同的数据库环境下需要维护的表结构复杂繁琐,需要维护许多脚本业务代码需要进行大范围的妥协来适应上述支持系统需要支持读写分离(动态添加)无需停机状态实时添加租户(租户线上签约)
  当然我是一开始想先写这篇文章,但是写着写着发现有些时候这个问题就来了,譬如多数据库下efcore默认不支持迁移,经过不断地努力,大脑的思维宫殿我下意识就发现了解决方案,最终用一天时间解决了就是前面的一篇文章EFCore高级Saas系统下单DbContext如何支持不同数据库的迁移那么我们话不多说马上开始
  接下来我们将实现A,B,C三个租户,其中A租户我们使用MSSQL的订单表使用按月分表,B租户我们使用MYSQL的订单表我们采用Id取模分表,C租户我们使用MSSQL也是使用订单按月分表但是起始时间和A不一样管理租户数据
  首先我们新建一个DbContext用来管理我们的租户信息租户用户表
  首先我们新建一张租户登录的用户表,每个用户就是我们对外的租户publicclassSysUser{publicstringId{get;set;}publicstringName{get;set;}publicstringPassword{get;set;}publicDateTimeCreationTime{get;set;}publicboolIsDeleted{get;set;}}租户配置表
  然后我们新建一张租户的配置信息表用来后续初始化配置publicclassSysUserTenantConfig{publicstringId{get;set;}publicstringUserId{get;set;}summary添加ShardingTenantOptions的Json包summarypublicstringConfigJson{get;set;}publicDateTimeCreationTime{get;set;}publicboolIsDeleted{get;set;}}定义租户配置为了满足上述需求我们需要对数据库和订单分片方式进行区分publicclassShardingTenantOptions{summary默认数据源名称summarypublicstringDefaultDataSourceName{get;set;}summary默认数据库地址summarypublicstringDefaultConnectionString{get;set;}summary数据库类型summarypublicDbTypeEnumDbType{get;set;}summary分片模式取模还是按月summarypublicOrderShardingTypeEnumOrderShardingType{get;set;}summary按月分片其实时间summarypublicDateTimeBeginTimeForSharding{get;set;}summary分片迁移的命名空间summarypublicstringMigrationNamespace{get;set;}}publicenumDbTypeEnum{MSSQL1,MYSQL2}publicenumOrderShardingTypeEnum{Mod1,ByMonth2}租户持久化DbContext
  新建一个dbcontext用来存储我们的租户信息,当然你也可以使用文件或者redis之类的都行publicclassIdentityDbContext:DbContext{publicIdentityDbContext(DbContextOptionsIdentityDbContextoptions):base(options){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){base。OnModelCreating(modelBuilder);modelBuilder。ApplyConfiguration(newSysUserMap());modelBuilder。ApplyConfiguration(newSysUserTenantConfigMap());}}
  这样我们就完成了租户信息的存储租户管理者
  我们拥有了租户信息持久化的数据后需要对租户信息的使用进行配置
  首先我们新建一个接口可以用来管理租户信息publicinterfaceITenantManager{summary获取所有的租户summaryreturnsreturnsListstringGetAll();summary获取当前租户summaryreturnsreturnsTenantContextGetCurrentTenantContext();summary添加租户信息summaryparamnametenantIdparamparamnameshardingRuntimeContextparamreturnsreturnsboolAddTenantSharding(stringtenantId,IShardingRuntimeContextshardingRuntimeContext);summary创建租户环境summaryparamnametenantIdparamreturnsreturnsTenantScopeCreateScope(stringtenantId);}租户的默认管理实现publicclassDefaultTenantManager:ITenantManager{privatereadonlyITenantContextAccessortenantContextAccessor;privatereadonlyConcurrentDictionarystring,IShardingRuntimeContextcachenew();publicDefaultTenantManager(ITenantContextAccessortenantContextAccessor){tenantContextAccessortenantContextAccessor;}publicListstringGetAll(){returncache。Keys。ToList();}publicTenantContextGetCurrentTenantContext(){returntenantContextAccessor。TenantContext;}publicboolAddTenantSharding(stringtenantId,IShardingRuntimeContextshardingRuntimeContext){returncache。TryAdd(tenantId,shardingRuntimeContext);}publicTenantScopeCreateScope(stringtenantId){if(!cache。TryGetValue(tenantId,outvarshardingRuntimeContext)){thrownewInvalidOperationException(未找到对应租户的配置);}tenantContextAccessor。TenantContextnewTenantContext(shardingRuntimeContext);returnnewTenantScope(tenantContextAccessor);}}当前租户上下文访问者publicinterfaceITenantContextAccessor{TenantContext?TenantContext{get;set;}}当前租户上下文访问者实现publicclassTenantContextAccessor:ITenantContextAccessor{privatestaticreadonlyAsyncLocalTenantContext?tenantContextnewAsyncLocalTenantContext?();publicTenantContext?TenantContext{gettenantContext。Value;settenantContext。Valuevalue;}}租户上下文publicclassTenantContext{privatereadonlyIShardingRuntimeContextshardingRuntimeContext;publicTenantContext(IShardingRuntimeContextshardingRuntimeContext){shardingRuntimeContextshardingRuntimeContext;}publicIShardingRuntimeContextGetShardingRuntimeContext(){returnshardingRuntimeContext;}}用来切换实现当前操作租户环境publicclassTenantScope:IDisposable{publicTenantScope(ITenantContextAccessortenantContextAccessor){TenantContextAccessortenantContextAccessor;}publicITenantContextAccessorTenantContextAccessor{get;}publicvoidDispose(){}}折叠构思ShardingCore如何不通过依赖注入使用
  其实ShardingCore可以默认不在依赖注入中进行依赖注入,首先我们看下普通情况下ShardingCore如何实现非依赖注入获取分片上下文varshardingRuntimeContextnewShardingRuntimeBuilderDefaultShardingDbContext()。UseRouteConfig(o{o。AddShardingTableRouteSysUserTableRoute();})。UseConfig(o{o。ThrowIfQueryRouteNotMatchfalse;o。UseShardingQuery((conStr,builder){builder。UseMySql(conStr,newMySqlServerVersion(newVersion()))。UseLoggerFactory(efLogger)。UseQueryTrackingBehavior(QueryTrackingBehavior。NoTracking);});o。UseShardingTransaction((connection,builder){builder。UseMySql(connection,newMySqlServerVersion(newVersion()))。UseLoggerFactory(efLogger)。UseQueryTrackingBehavior(QueryTrackingBehavior。NoTracking);});o。AddDefaultDataSource(ds0,server127。0。0。1;port3306;databasedbdbd0;useridroot;passwordroot;);o。UseShardingMigrationConfigure(b{b。ReplaceServiceIMigrationsSqlGenerator,ShardingMySqlMigrationsSqlGenerator();});})。ReplaceServiceITableEnsureManager,MySqlTableEnsureManager(ServiceLifetime。Singleton)。Build();
  这样我们就获得了IShardingRuntimeContext,将不同的IShardingRuntimeContext放到不同的数据库中我们就可以实现不同的租户了订单表publicclassOrder{publicstringId{get;set;}publicstringName{get;set;}publicDateTimeCreationTime{get;set;}publicboolIsDeleted{get;set;}}租户DbContextpublicclassTenantDbContext:AbstractShardingDbContext,IShardingTableDbContext{publicTenantDbContext(DbContextOptionsTenantDbContextoptions):base(options){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){base。OnModelCreating(modelBuilder);modelBuilder。ApplyConfiguration(newOrderMap());}publicIRouteTailRouteTail{get;set;}}创建订单路由订单按月分片路由
  注意这边我们简单的通过采用一个静态字段来实现publicclassOrderMonthTableRoute:AbstractSimpleShardingMonthKeyDateTimeVirtualTableRouteOrder{privatereadonlyShardingTenantOptionsshardingTenantOptions;publicOrderMonthTableRoute(ShardingTenantOptionsshardingTenantOptions){shardingTenantOptionsshardingTenantOptions;}publicoverridevoidConfigure(EntityMetadataTableBuilderOrderbuilder){builder。ShardingProperty(oo。CreationTime);}publicoverrideboolAutoCreateTableByTime(){returntrue;}publicoverrideDateTimeGetBeginTime(){returnshardingTenantOptions。BeginTimeForSharding;}}订单取模分片路由publicclassOrderModTableRoute:AbstractSimpleShardingModKeyStringVirtualTableRouteOrder{privatereadonlyShardingTenantOptionsshardingTenantOptions;publicOrderModTableRoute(ShardingTenantOptionsshardingTenantOptions):base(2,5){shardingTenantOptionsshardingTenantOptions;}publicoverridevoidConfigure(EntityMetadataTableBuilderOrderbuilder){builder。ShardingProperty(oo。Id);}}实现多数据库的codefirst迁移
  具体参考之前的博客EFCore高级Saas系统下单DbContext如何支持不同数据库的迁移
  https:www。cnblogs。comxuejiamingp16510482。html分片创建者publicinterfaceIShardingBuilder{IShardingRuntimeContextBuild(ShardingTenantOptionstenantOptions);}publicclassDefaultShardingBuilder:IShardingBuilder{publicstaticreadonlyILoggerFactoryefLoggerLoggerFactory。Create(builder{builder。AddFilter((category,level)categoryDbLoggerCategory。Database。Command。NamelevelLogLevel。Information)。AddConsole();});privatereadonlyIServiceProviderserviceProvider;publicDefaultShardingBuilder(IServiceProviderserviceProvider){serviceProviderserviceProvider;}publicIShardingRuntimeContextBuild(ShardingTenantOptionstenantOptions){varshardingRuntimeBuildernewShardingRuntimeBuilderTenantDbContext()。UseRouteConfig(o{if(tenantOptions。OrderShardingTypeOrderShardingTypeEnum。Mod){o。AddShardingTableRouteOrderModTableRoute();}if(tenantOptions。OrderShardingTypeOrderShardingTypeEnum。ByMonth){o。AddShardingTableRouteOrderMonthTableRoute();}})。UseConfig(o{o。ThrowIfQueryRouteNotMatchfalse;o。UseShardingQuery((conStr,builder){if(tenantOptions。DbTypeDbTypeEnum。MYSQL){builder。UseMySql(conStr,newMySqlServerVersion(newVersion()))。UseMigrationNamespace(newMySqlMigrationNamespace());}if(tenantOptions。DbTypeDbTypeEnum。MSSQL){builder。UseSqlServer(conStr)。UseMigrationNamespace(newSqlServerMigrationNamespace());}builder。UseLoggerFactory(efLogger)。UseQueryTrackingBehavior(QueryTrackingBehavior。NoTracking)。ReplaceServiceIMigrationsAssembly,MultiDatabaseMigrationsAssembly();});o。UseShardingTransaction((connection,builder){if(tenantOptions。DbTypeDbTypeEnum。MYSQL){builder。UseMySql(connection,newMySqlServerVersion(newVersion()));。UseMigrationNamespace(newMySqlMigrationNamespace());迁移只会用connectionstring创建所以可以不加}if(tenantOptions。DbTypeDbTypeEnum。MSSQL){builder。UseSqlServer(connection);。UseMigrationNamespace(newSqlServerMigrationNamespace());}builder。UseLoggerFactory(efLogger)。UseQueryTrackingBehavior(QueryTrackingBehavior。NoTracking);});o。AddDefaultDataSource(tenantOptions。DefaultDataSourceName,tenantOptions。DefaultConnectionString);注意这个迁移必须要十分重要注意这个迁移必须要十分重要注意这个迁移必须要十分重要注意这个迁移必须要十分重要o。UseShardingMigrationConfigure(b{if(tenantOptions。DbTypeDbTypeEnum。MYSQL){b。ReplaceServiceIMigrationsSqlGenerator,ShardingMySqlMigrationsSqlGenerator();}if(tenantOptions。DbTypeDbTypeEnum。MSSQL){b。ReplaceServiceIMigrationsSqlGenerator,ShardingSqlServerMigrationsSqlGenerator();}});})。AddServiceConfigure(s{IShardingRuntimeContext内部的依赖注入s。AddSingleton(tenantOptions);});if(tenantOptions。DbTypeDbTypeEnum。MYSQL){shardingRuntimeBuilder。ReplaceServiceITableEnsureManager,MySqlTableEnsureManager(ServiceLifetime。Singleton);}if(tenantOptions。DbTypeDbTypeEnum。MSSQL){shardingRuntimeBuilder。ReplaceServiceITableEnsureManager,SqlServerTableEnsureManager(ServiceLifetime。Singleton);}returnshardingRuntimeBuilder。Build(serviceProvider);}}折叠
  到此为止基本上我们已经完成了多租户的大部分配置了,jwt部分就不在这边赘述了因为之前有实现过Startup
  主要关键的启动点我们应该怎么配置呢启动初始化租户
  首先我们需要针对程序启动后进行租户的初始化操作publicstaticclassTenantExtension{publicstaticvoidInitTenant(thisIServiceProviderserviceProvider){vartenantManagerserviceProvider。GetRequiredServiceITenantManager();varshardingBuilderserviceProvider。GetRequiredServiceIShardingBuilder();using(varscopeserviceProvider。CreateScope()){varidentityDbContextscope。ServiceProvider。GetRequiredServiceIdentityDbContext();identityDbContext。Database。Migrate();varsysUserTenantConfigsidentityDbContext。SetSysUserTenantConfig()。ToList();if(sysUserTenantConfigs。Any()){foreach(varsysUserTenantConfiginsysUserTenantConfigs){varshardingTenantOptionsJsonConvert。DeserializeObjectShardingTenantOptions(sysUserTenantConfig。ConfigJson);varshardingRuntimeContextshardingBuilder。Build(shardingTenantOptions);tenantManager。AddTenantSharding(sysUserTenantConfig。UserId,shardingRuntimeContext);}}}vartenantIdstenantManager。GetAll();foreach(vartenantIdintenantIds){using(tenantManager。CreateScope(tenantId))using(varscopeserviceProvider。CreateScope()){varshardingRuntimeContexttenantManager。GetCurrentTenantContext()。GetShardingRuntimeContext();开启定时任务shardingRuntimeContext。UseAutoShardingCreate();vartenantDbContextscope。ServiceProvider。GetServiceTenantDbContext();tenantDbContext。Database。Migrate();补偿表shardingRuntimeContext。UseAutoTryCompensateTable();}}}}请求租户中间件
  为了让我们的所有请求都可以使用指定对应的租户数据库publicclassTenantSelectMiddleware{privatereadonlyRequestDelegatenext;privatereadonlyITenantManagertenantManager;publicTenantSelectMiddleware(RequestDelegatenext,ITenantManagertenantManager){nextnext;tenantManagertenantManager;}summary1。中间件的方法必须叫Invoke,且为public,非static。2。Invoke方法第一个参数必须是HttpContext类型。3。Invoke方法必须返回Task。4。Invoke方法可以有多个参数,除HttpContext外其它参数会尝试从依赖注入容器中获取。5。Invoke方法不能有重载。summaryAuthor:NapoleonCreated:202013021:30publicasyncTaskInvoke(HttpContextcontext){if(context。Request。Path。ToString()。StartsWith(apitenant,StringComparison。CurrentCultureIgnoreCase)){if(!context。User。Identity。IsAuthenticated){awaitnext(context);return;}vartenantIdcontext。User。Claims。FirstOrDefault((o)o。Typeuid)?。Value;if(string。IsNullOrWhiteSpace(tenantId)){awaitDoUnAuthorized(context,notfoundtenantid);return;}using(tenantManager。CreateScope(tenantId)){awaitnext(context);}}else{awaitnext(context);}}privateasyncTaskDoUnAuthorized(HttpContextcontext,stringmsg){context。Response。StatusCode403;awaitcontext。Response。WriteAsync(msg);}}折叠编写登录注册操作
  startup处配置〔Route(api〔controller〕〔action〕)〕〔ApiController〕〔AllowAnonymous〕publicclassPassportController:ControllerBase{privatereadonlyIServiceProviderserviceProvider;privatereadonlyIdentityDbContextidentityDbContext;privatereadonlyITenantManagertenantManager;privatereadonlyIShardingBuildershardingBuilder;publicPassportController(IServiceProviderserviceProvider,IdentityDbContextidentityDbContext,ITenantManagertenantManager,IShardingBuildershardingBuilder){serviceProviderserviceProvider;identityDbContextidentityDbContext;tenantManagertenantManager;shardingBuildershardingBuilder;}〔HttpPost〕publicasyncTaskIActionResultRegister(RegisterRequestrequest){if(awaitidentityDbContext。SetSysUser()。AnyAsync(oo。Namerequest。Name))returnBadRequest(usernotexists);varsysUsernewSysUser(){IdGuid。NewGuid()。ToString(n),Namerequest。Name,Passwordrequest。Password,CreationTimeDateTime。Now};varshardingTenantOptionsnewShardingTenantOptions(){DbTyperequest。DbType,OrderShardingTyperequest。OrderShardingType,BeginTimeForShardingrequest。BeginTimeForSharding。Value,DefaultDataSourceNameds0,DefaultConnectionStringGetDefaultString(request。DbType,sysUser。Id)};varsysUserTenantConfignewSysUserTenantConfig(){IdGuid。NewGuid()。ToString(n),UserIdsysUser。Id,CreationTimeDateTime。Now,ConfigJsonJsonConvert。SerializeObject(shardingTenantOptions)};awaitidentityDbContext。AddAsync(sysUser);awaitidentityDbContext。AddAsync(sysUserTenantConfig);awaitidentityDbContext。SaveChangesAsync();varshardingRuntimeContextshardingBuilder。Build(shardingTenantOptions);tenantManager。AddTenantSharding(sysUser。Id,shardingRuntimeContext);using(tenantManager。CreateScope(sysUser。Id))using(varscopeserviceProvider。CreateScope()){varruntimeContexttenantManager。GetCurrentTenantContext()。GetShardingRuntimeContext();runtimeContext。UseAutoShardingCreate();启动定时任务vartenantDbContextscope。ServiceProvider。GetServiceTenantDbContext();tenantDbContext。Database。Migrate();runtimeContext。UseAutoTryCompensateTable();}returnOk();}〔HttpPost〕publicasyncTaskIActionResultLogin(LoginRequestrequest){varsysUserawaitidentityDbContext。SetSysUser()。FirstOrDefaultAsync(oo。Namerequest。Nameo。Passwordrequest。Password);if(sysUsernull)returnBadRequest(nameorpassworderror);秘钥,就是标头,这里用Hmacsha256算法,需要256bit的密钥varsecurityKeynewSigningCredentials(newSymmetricSecurityKey(Encoding。ASCII。GetBytes(123123!!123123)),SecurityAlgorithms。HmacSha256);Claim,JwtRegisteredClaimNames中预定义了好多种默认的参数名,也可以像下面的Guid一样自己定义键名。ClaimTypes也预定义了好多类型如role、email、name。Role用于赋予权限,不同的角色可以访问不同的接口相当于有效载荷varclaimsnewClaim〔〕{newClaim(JwtRegisteredClaimNames。Iss,https:localhost:5000),newClaim(JwtRegisteredClaimNames。Aud,api),newClaim(id,Guid。NewGuid()。ToString(n)),newClaim(uid,sysUser。Id),};SecurityTokensecurityTokennewJwtSecurityToken(signingCredentials:securityKey,expires:DateTime。Now。AddHours(2),过期时间claims:claims);vartokennewJwtSecurityTokenHandler()。WriteToken(securityToken);returnOk(token);}privatestringGetDefaultString(DbTypeEnumdbType,stringuserId){switch(dbType){caseDbTypeEnum。MSSQL:return34;DataSourcelocalhost;InitialCatalogDB{userId};IntegratedSecurityTrue;;caseDbTypeEnum。MYSQL:return34;server127。0。0。1;port3306;databaseDB{userId};useridroot;passwordL6yBtV6qNENrwBy7;;default:thrownewNotImplementedException();}}}publicclassRegisterRequest{publicstringName{get;set;}publicstringPassword{get;set;}publicDbTypeEnumDbType{get;set;}publicOrderShardingTypeEnumOrderShardingType{get;set;}publicDateTime?BeginTimeForSharding{get;set;}}publicclassLoginRequest{publicstringName{get;set;}publicstringPassword{get;set;}}折叠启动配置varbuilderWebApplication。CreateBuilder(args);Addservicestothecontainer。builder。Services。AddControllers();builder。Services。AddAuthentication();region用户系统配置builder。Services。AddDbContextIdentityDbContext(oo。UseSqlServer(DataSourcelocalhost;InitialCatalogIdDb;IntegratedSecurityTrue;));生成密钥varkeyByteArrayEncoding。ASCII。GetBytes(123123!!123123);varsigningKeynewSymmetricSecurityKey(keyByteArray);认证参数builder。Services。AddAuthentication(Bearer)。AddJwtBearer(o{o。TokenValidationParametersnewTokenValidationParameters{ValidateIssuerSigningKeytrue,IssuerSigningKeysigningKey,ValidateIssuertrue,ValidIssuerhttps:localhost:5000,ValidateAudiencetrue,ValidAudienceapi,ValidateLifetimetrue,ClockSkewTimeSpan。Zero,RequireExpirationTimetrue,};});endregionbuilder。Services。AddSingletonITenantManager,DefaultTenantManager();builder。Services。AddSingletonITenantContextAccessor,TenantContextAccessor();builder。Services。AddSingletonIShardingBuilder,DefaultShardingBuilder();region配置ShardingCorevarproviderbuilder。Configuration。GetValue(Provider,UnKnown);AddMigrationInitialCreateContextTenantDbContextOutputDirMigrationsSqlServerArgsproviderSqlServerAddMigrationInitialCreateContextTenantDbContextOutputDirMigrationsMySqlArgsproviderMySqlbuilder。Services。AddDbContextTenantDbContext((sp,b){vartenantManagersp。GetRequiredServiceITenantManager();varcurrentTenantContexttenantManager。GetCurrentTenantContext();如果有上下文那么创建租户dbcontext否则就是启动命令AddMigrationif(currentTenantContext!null){varshardingRuntimeContextcurrentTenantContext。GetShardingRuntimeContext();b。UseDefaultShardingTenantDbContext(shardingRuntimeContext);}if(args。IsNotEmpty()){命令启动时为了保证AddMigration正常运行if(providerMySql){b。UseMySql(server127。0。0。1;port3306;databaseTenantDb;useridroot;passwordL6yBtV6qNENrwBy7;,newMySqlServerVersion(newVersion()))。UseMigrationNamespace(newMySqlMigrationNamespace())。ReplaceServiceIMigrationsAssembly,MultiDatabaseMigrationsAssembly();return;}if(providerSqlServer){b。UseSqlServer(DataSourcelocalhost;InitialCatalogTenantDb;IntegratedSecurityTrue;)。UseMigrationNamespace(newSqlServerMigrationNamespace())。ReplaceServiceIMigrationsAssembly,MultiDatabaseMigrationsAssembly();return;}}});endregionvarappbuilder。Build();初始化启动配置租户信息app。Services。InitTenant();app。UseAuthorization();在认证后启用租户选择中间件app。UseMiddlewareTenantSelectMiddleware();app。MapControllers();app。Run();折叠添加迁移脚本
  持久化identity迁移
  多租户SqlServer版本
  多租户MySql版本
  启动程序
  启动程序我们发现IdentityDbContext已经创建好了,并且支持了自动迁移
  创建A租户{Name:A,Password:A,DbType:1,OrderShardingType:2,BeginTimeForSharding:20220101,MigrationNamespace:ShardingCoreMultiTenantSys。Migrations。SqlServer}
  注意:MigrationNamespace应该自动生成,这边只是为了演示方便没写
  完成创建B租户{Name:B,Password:B,DbType:2,OrderShardingType:1,BeginTimeForSharding:20220101,MigrationNamespace:ShardingCoreMultiTenantSys。Migrations。Myql}
  完美创建
  创建C租户{Name:C,Password:C,DbType:1,OrderShardingType:2,BeginTimeForSharding:20220601,MigrationNamespace:ShardingCoreMultiTenantSys。Migrations。SqlServer}
  C租户完美创建并且和A租户采用一样的分片规则不一样的分片起始时间
  分别对abc进行crud
  首先获取token,然后插入
  A租户
  B租户
  C租户
  最后完成
  文章来自https:www。cnblogs。comxuejiamingp16508446。html

两回合9比0大胜河北队!武汉三镇队5球大胜领跑中超昨天,中超联赛第六轮最后3场比赛战罢。在引人关注的一场比赛中,升班马武汉三镇队5比0大胜积分垫底的河北队,两回合9比0双杀对手,5胜1平积16分继续稳居积分榜首位。在首回……茶叶是无价宝长期饮用效果好茶叶的好处和健康保健作用我们以往的文章中也整理了很多这次综合整理一下。茶对人类健康有着重要的贡献已是不争的事实,19世纪前却没有人能明确告诉我们茶为什么有这么……长大后和父母不亲的孩子,有以下几个特征!看看你孩子有没有?真想钻进孩子的脑子,看看里面是什么!看到这样的留言,就知道又有家长被孩子弄崩溃了:玩游戏输了,哭;不给玩手机,哭;不给买玩具,躺着哭;爱哭还脾气大,怎么……新疆兵团四师可克达拉市绿水环绕繁花满眼作者:赵爽杨俊钦蓝天、白云、清新的空气,芳草依依、树木成林、繁花似锦的绿地,小桥流水、鸟儿嬉戏、船儿游荡的水面这是在新疆生产建设兵团第四师可克达拉市建城初期,人们脑海中想……多图!各代表团运动员入场,现场气氛嗨不停北京冬奥会闭幕式20日晚在国家体育场举行。在代表团旗帜和运动员入场环节,全体观众主动报以持续不停的掌声和欢呼声,而各代表团运动员也以自己的独特方式庆祝,现场气氛达到高潮。……它们上天,中国卫星更不安全了美国太空军于21日发射了两颗新的空间域感知卫星。拥有全球最多军用卫星的五角大楼,对这次发射说得倒是轻描淡写。但若仔细分析这次发射的卫星,美国在太空领域不可见人的一面就暴露……云南天文台超新星研究取得重要进展上图:碳氧白矮星吸积氦星;下图:碳氧白矮星吸积亚巨阶段的氦星云南天文台韩占文创新团队近来在Ia型超新星前身星领域的研究工作取得了新的进展。以博士研究生王博为第一作者撰写的……手机厂商为何扎堆儿盯上了折叠屏?我早就跟团队说tm价格肯定定便宜了。这一幕发生在OPPOFindN发布会结束后。OPPOFindN刚刚发布,预约通道也刚刚开启,OPPO高级副总裁、首席产品官刘作虎久违地……孩子越吃越聪明的粥怎么煮孩子吃了非常补的粥怎么煮呢,首先准备骨头,鸡蛋,葱头,鸟蛋,红萝卜,一点点瘦肉。第一部:先把骨头放下锅去熬汤,要放点酒和放一点醋和盐来熬骨头汤。汤熬半个小时之后,开始洗米用另外……暴饮暴食拒绝参加训练,球队开会自己睡觉,Zion这么搞不胖才随着时间的推移,詹姆斯、保罗、安东尼、霍华德这批老派球星正在慢慢老去,与此同时,东契奇、特雷杨、莫兰特、艾顿等潜力新星也都在期待着,未来能够从前辈们手中接过时代的接力棒。值得一……体验恒温恒湿恒氧的智能生态技术云米Milano2Max新风空夏季来临,空调直吹造成的空调病成了非常普遍的问题,我也不堪其扰。去年夏天我去一个朋友家玩,当时感觉到既凉爽又舒适,朋友告诉我,因为她们购房时小区统一装了恒温恒湿恒氧的新风系统,……斯坦福大学CS博士新作Attention提速24倍,BERT机器之心报道编辑:陈萍FlashAttention是一种具有IO感知,且兼具快速、内存高效的新型注意力算法。一种快速、内存高效的注意力算法来了,被命名为Fla……
曝周琦加盟上海男篮,辽宁男篮空欢喜,上海男篮拥有四大优势!头条创作挑战赛CBA联赛目前最大的新闻,就是周琦恢复自由身了,这件事也引发了众多球迷的热议。除了新疆男篮操作违规外,球迷最为关注的就是周琦下赛季会加盟到哪支球队了。其中讨……提信心抢开局稳增长金融帮扶助力企业开年即奔跑来源:【嘉兴日报嘉兴在线】编者按:谋定快动抢占先机,抓早抓实赢得主动。日前,嘉兴重磅发布提信心抢开局稳增长18条政策,认真贯彻落实中央经济工作会议部署要求,以政府快……这三部安卓机不足千元,标配128G存储与5000mAh电池如果你还在用四五年前的古董手机,不如换一部现在的安卓百元机。随着安卓手机的迅速发展,这三部百元安卓机也有了不错的性能表现,并且标配128G存储空间与5000mAh大容量电池。……研究人员利用驻波场中悬浮小球的振动实现物性参数的反演密度、粘度等物性参数的传统测量方法多需要借助容器,且要将测量仪器与待测样品直接接触。然而,在生物医药等领域的某些特殊场景中,待测样品可能为高活性、放射性或高温熔融态物质,难以采……2023年珠宝圈流行风向标来啦!这几个苗头你抓住了吗?新年伊始,2023年珠宝圈会有哪些宝石将成新宠?哪些风格就再次翻红?哪些品类将成为大热爆款?芭珠姐为你抓到几个苗头,提前预测一波走起这些宝石将成贵圈新宠说到今年要流……自学烘焙原味古早蛋糕原味古早蛋糕的配方是参考Tinrry甜悦家的,这个配方蛋糕体比较软嫩,老人小孩也容易入口,试过有些配方做出来蛋糕体比较干噎,这个配方烤完又有浓浓蛋香味,也没有加香草香精值得一试……电影满江红观后感高燃影视季电影满江红观后感,心情很激动,迫不及待的跟大家聊一聊这部电影。满江红电影情节发生在岳飞死后四年,以秦桧率兵与金国会谈前夜为时间背景,讲述沈腾饰演的小兵张大与易烊……旗舰换次旗舰是消费降级?一加老用户现身说法体验反而升级了在坚持了近四年之后,我手上的一加7Pro终于退役了!回想起2019年夏天购买这部手机的情景,当时真的很惊喜,因为一加7Pro配备了一块90Hz屏幕,非常抢手,我是抢了好久才抢到……刘伟元西行日记我的快乐人活这一世,无非是吃穿二事,再华贵的服饰首要的也是为了蔽体,再美味的珍馐前提也是为了果腹。当吃穿满足之后,人们又开始追求精神上的愉悦和心灵上的满足了。君子以得到美名而欢快……抵抗肌肤衰老的方法,广东郑明明红颜紧致套组介绍25岁开始,人体的新陈代谢变慢、胶原蛋白流失速度加快,皮肤逐渐出现初期衰老表现,后来随着年龄的增长,这些表现将日益加深,因此皮肤抗衰老进行得越早,在同龄人中越有优势,随着时间的……中国男篮一哥或将回归CBA,土豪球队或成最大赢家众所周知,这个赛季周琦在澳大利亚NBL墨尔本凤凰队打的并不出色,在联赛还剩下几轮的情况下,周琦提前回到国内,年前人一直在海南。周琦回到国内,一方面是为了能有更多的时间陪伴……这7个传言不靠谱,别再被忽悠了!养生,是个贯穿古今的热门话题,也是谣言的重灾区。不管是家里的长辈还是年轻人,稍不留神都会被忽悠住。下面这些常见的养生说法都是真的吗?快来看看你有没有被骗到1hranima……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网