ABP vNext微服务架构详细教程——分布式权限框架(上)

2023-11-05 19:38

本文主要是介绍ABP vNext微服务架构详细教程——分布式权限框架(上),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1

简介

3e5764ada323a9ea07d18cdf184d98f9.png

ABP vNext框架本身提供了一套权限框架,其功能非常丰富,具体可参考官方文档:https://docs.abp.io/en/abp/latest/Authorization

0768681cd19340aa43ba9743ea22e945.gif

但是我们使用时会发现,对于正常的单体应用,ABP vNext框架提供的权限系统没有问题, 但是在微服务架构下,这种权限系统并不是非常的友好。

a58eca97ed79796317a83504dc349bee.gif

我希望我的权限系统可以满足以下要求:

每个聚合服务持有独立的权限集合

每个聚合服务可以独立声明、使用其接口访问所需的权限。

提供统一接口负责管理、存储所有服务权限并实现对角色的授权。

每个接口可以灵活组合使用一个或多个权限码。

权限框架使用尽量简单,减少额外编码量。

8934846192858daf5531d12bde980b57.gif

在ABP vNext框架基础上,重新编写了一套分布式权限框架,大体规则如下:

使用ABP vNext框架中提供的用户、角色模型不做改变,替代重新定义权限模型,重新定义权限的实体及相关服务接口。

在身份管理服务中,实现权限的统一管理、角色授权和权限认证。

在聚合服务中定义其具有的权限信息、权限关系并通过特性声明各接口所需要的权限。

在聚合服务启动时,自动将其权限信息注册到身份管理服务。

客户端访问聚合服务层服务时在聚合服务层中间件中验证当前用户是否具有该接口权限,验证过程需调用身份管理服务对应接口。 

796fc6bddb913ceedc406e955c9db6a3.gif

权限系统具体实现见下文。

2

身份认证服务

2be48d4482e970fa122386e05f90f3fa.gif

在之前的文章中我们已经搭建了身份认证服务的基础框架,这里我们直接在此基础上新增代码。

0063425d49ab05abe57474c708b9a62d.gif

在Demo.Identity.Domain项目中添加Permissions文件夹,并添加Entities子文件夹。在此文件夹下添加实体类SysPermission和RolePermissions如下:

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;namespace Demo.Identity.Permissions.Entities;/// <summary>
/// 权限实体类
/// </summary>
public class SysPermission : Entity<Guid>
{/// <summary>/// 服务名称/// </summary>[MaxLength(64)]public string ServiceName { get; set; }/// <summary>/// 权限编码/// </summary>[MaxLength(128)]public string Code { get; set; }/// <summary>/// 权限名称/// </summary>[MaxLength(64)]public string Name { get; set; }/// <summary>/// 上级权限ID/// </summary>[MaxLength(128)]public string ParentCode { get; set; }/// <summary>/// 判断两个权限是否相同/// </summary>/// <param name="obj"></param>/// <returns></returns>public override bool Equals(object? obj)
{return obj is SysPermission permission&& permission.ServiceName == ServiceName&& permission.Name == Name&& permission.Code == Code&& permission.ParentCode == ParentCode;}/// <summary>/// 设置ID的值/// </summary>/// <param name="id"></param>public void SetId(Guid id)
{Id = id;}
}
using System;
using Volo.Abp.Domain.Entities;namespace Demo.Identity.Permissions.Entities;/// <summary>
/// 角色权限对应关系
/// </summary>
public class RolePermissions : Entity<Guid>
{/// <summary>/// 角色编号/// </summary>public Guid RoleId { get; set; }/// <summary>/// 权限编号/// </summary>public Guid PermissionId { get; set; }
}

02f6bdec93f849b88bd8485abbdf1cd9.gif

将Demo.Identity.Application.Contracts项目中原有Permissions文件夹中所有类删除,并添加子文件夹Dto。在此文件夹下添加SysPermissionDto、PermissionTreeDto、SetRolePermissionsDto

类如下:

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;namespace Demo.Identity.Permissions.Dto;/// <summary>
/// 权限DTO
/// </summary>
public class SysPermissionDto:EntityDto<Guid>
{/// <summary>/// 服务名称/// </summary>[MaxLength(64)]public string ServiceName { get; set; }/// <summary>/// 权限编码/// </summary>[MaxLength(128)]public string Code { get; set; }/// <summary>/// 权限名称/// </summary>[MaxLength(64)]public string Name { get; set; }/// <summary>///     上级权限ID/// </summary>[MaxLength(128)]public string ParentCode { get; set; }
}
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;namespace Demo.Identity.Permissions.Dto;/// <summary>
/// 权限树DTO
/// </summary>
public class PermissionTreeDto : EntityDto<Guid>
{/// <summary>/// 服务名称/// </summary>public string ServiceName { get; set; }/// <summary>/// 权限编码/// </summary>public string Code { get; set; }/// <summary>/// 权限名称/// </summary>public string Name { get; set; }/// <summary>/// 上级权限ID/// </summary>public string ParentCode { get; set; }/// <summary>/// 子权限/// </summary>public List<PermissionTreeDto> Children { get; set; }}
using System;
using System.Collections.Generic;namespace Demo.Identity.Permissions.Dto;/// <summary>
/// 设置角色权限DTO
/// </summary>
public class SetRolePermissionsDto
{/// <summary>/// 角色编号/// </summary>public Guid RoleId { get; set; }/// <summary>/// 权限ID列表/// </summary>public List<Guid> Permissions { get; set; }
}

282a8c294ce4f62ab02dd1a7d3dc66ba.gif

将Demo.Identity.Application.Contracts项目中Permissions文件夹下添加接口IRolePermissionsAppService如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Volo.Abp.Application.Services;namespace Demo.Identity.Permissions;/// <summary>
///     角色管理应用服务接口
/// </summary>
public interface IRolePermissionsAppService: IApplicationService
{/// <summary>/// 获取角色所有权限/// </summary>/// <param name="roleId">角色ID</param>/// <returns></returns>Task<List<PermissionTreeDto>> GetPermission(Guid roleId);/// <summary>/// 设置角色权限/// </summary>/// <param name="dto">角色权限信息</param>/// <returns></returns>Task SetPermission(SetRolePermissionsDto dto);
}

33cb301ffb60cce4ba8bad696d3bd99c.gif

将Demo.Identity.Application.Contracts项目中Permissions文件夹下添加接口ISysPermissionAppService如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Volo.Abp.Application.Services;namespace Demo.Identity.Permissions;/// <summary>
/// 权限管理应用服务接口 
/// </summary>
public interface ISysPermissionAppService:IApplicationService
{/// <summary>/// 按服务注册权限/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="permissions">权限列表</param>/// <returns></returns>Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions);/// <summary>/// 按服务获取权限/// </summary>/// <param name="serviceName">服务名称</param>/// <returns>查询结果</returns>Task<List<SysPermissionDto>> GetPermissions(string serviceName);/// <summary>/// 获取完整权限树/// </summary>/// <param name="Permission"></param>/// <returns>查询结果</returns>Task<List<PermissionTreeDto>> GetPermissionTree();/// <summary>/// 获取用户权限码/// </summary>/// <param name="userId">用户编号</param>/// <returns>查询结果</returns>Task<List<string>> GetUserPermissionCode(Guid userId);
}

a842c0641ab27358777cf7627f48ef31.gif

在公共类库文件夹common中创建.Net6类库项目项目Demo.Core,用于存放通用类。

3278852ff639c4fe7f82826c9a3e471c.gif

这里我们在Demo.Core中添加文件夹CommonExtension用于存放通用扩展,添加EnumExtensions和ListExtensions类如下:

namespace Demo.Core.CommonExtension;/// <summary>
/// 枚举扩展类
/// </summary>
public static class EnumExtensions
{/// <summary>/// 获取描述特性/// </summary>/// <param name="enumValue">枚举值</param>/// <returns></returns>public static string GetDescription(this Enum enumValue){string value = enumValue.ToString();FieldInfo field = enumValue.GetType().GetField(value);object[] objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false);  //获取描述属性if (objs == null || objs.Length == 0)  //当描述属性没有时,直接返回名称return value;DescriptionAttribute descriptionAttribute = (DescriptionAttribute)objs[0];return descriptionAttribute.Description;}
}
namespace Demo.Core.CommonExtension;public static class ListExtensions
{/// <summary>/// 集合去重/// </summary>/// <param name="lst">目标集合</param>/// <param name="keySelector">去重关键字</param>/// <typeparam name="T">集合元素类型</typeparam>/// <typeparam name="TKey">去重关键字数据类型</typeparam>/// <returns>去重结果</returns>public static List<T> Distinct<T,TKey>(this List<T> lst,Func<T, TKey> keySelector){List<T> result = new List<T>();HashSet<TKey> set = new HashSet<TKey>();foreach (var item in lst){var key = keySelector(item);if (!set.Contains(key)){set.Add(key);result.Add(item);}}return result;}
}

84a26677b92c9a80074d86c7de486d64.gif

在Demo.Core项目中添加文件夹CommonFunction用于存放通用方法,这里我们添加用于集合比对的ListCompare类如下:

using VI.Core.CommonExtension;namespace VI.Core.CommonFunction;/// <summary>
/// 集合比对
/// </summary>
public class ListCompare
{/** 调用实例:*  MutiCompare<Permission, string>(lst1, lst2, x => x.Code, (obj, isnew) =>*  {*      if (isnew)*      {*          Console.WriteLine($"新增项{obj.Id}");*      }*      else*      {*          Console.WriteLine($"已存在{obj.Id}");*      }*  }, out var lstNeedRemove);*//// <summary>/// 对比源集合和目标集合,处理已有项和新增项,并找出需要删除的项/// </summary>/// <param name="lstSource">源集合</param>/// <param name="lstDestination">目标集合</param>/// <param name="keySelector">集合比对关键字</param>/// <param name="action">新增或已有项处理方法,参数:(数据项, 是否是新增)</param>/// <param name="needRemove">需要删除的数据集</param>/// <typeparam name="TObject">集合对象数据类型</typeparam>/// <typeparam name="TKey">对比关键字数据类型</typeparam>public static void MutiCompare<TObject,TKey>(List<TObject> lstDestination,List<TObject> lstSource,Func<TObject, TKey> keySelector,Action<TObject, bool> action, out Dictionary<TKey, TObject> needRemove){//目标集合去重lstDestination.Distinct(keySelector);//将源集合存入字典,提高查询效率needRemove = new Dictionary<TKey, TObject>();foreach (var item in lstSource){needRemove.Add(keySelector(item),item);}//遍历目标集合,区分新增项及已有项//在字典中排除目标集合中的项,剩余的即为源集合中需删除的项foreach (var item in lstDestination){if (needRemove.ContainsKey(keySelector(item))){action(item, false);needRemove.Remove(keySelector(item));}else{action(item, true);}}}
}

774eda5f91fa5f9f23e62b8ce752335d.gif

在Demo.Identity.Application项目中添加Permissions文件夹。

316da11e90fa1461b08d804effc01b68.gif

在Demo.Identity.Application项目Permissions文件夹中添加PermissionProfileExtensions类用于定义对象映射关系如下:

using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;namespace Demo.Identity.Permissions;public static class PermissionProfileExtensions
{/// <summary>/// 创建权限领域相关实体映射关系/// </summary>/// <param name="profile"></param>public static void CreatePermissionsMap(this IdentityApplicationAutoMapperProfile profile){profile.CreateMap<SysPermission, PermissionTreeDto>();profile.CreateMap<SysPermission,SysPermissionDto>();profile.CreateMap<SysPermissionDto,SysPermission>();}
}

8bc6c8668e75a02b4f5c412b9542eba5.gif

在Demo.Identity.Application项目IdentityApplicationAutoMapperProfile类的IdentityApplicationAutoMapperProfile方法中添加如下代码:

this.CreatePermissionsMap();

75171c85c6a87c6f43a105900b24fdff.gif

在Demo.Identity.Application项目Permissions文件夹中添加PermissionTreeBuilder类,定义构造权限树形结构的通用方法如下:

using System.Collections.Generic;
using System.Linq;
using Demo.Identity.Permissions.Dto;namespace Demo.Identity.Permissions;/// <summary>
/// 权限建树帮助类
/// </summary>
public static class PermissionTreeBuilder
{/// <summary>/// 建立树形结构/// </summary>/// <param name="lst"></param>/// <returns></returns>public static List<PermissionTreeDto> Build(List<PermissionTreeDto> lst){var result = lst.ToList();for (var i = 0; i < result.Count; i++){if (result[i].ParentCode == null){continue;}foreach (var item in lst){item.Children ??= new List<PermissionTreeDto>();if (item.Code != result[i].ParentCode){continue;}item.Children.Add(result[i]);result.RemoveAt(i);i--;break;}}return result;}
}

030c797a2c1931482430a4de28c4d644.gif

之后我们在Demo.Identity.Application项目Permissions文件夹中添加权限管理实现类SysPermissionAppService如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.Core.CommonFunction;
using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Demo.Core.CommonExtension;namespace Demo.Identity.Permissions
{/// <summary>/// 权限管理应用服务/// </summary>public class SysPermissionAppService : IdentityAppService, ISysPermissionAppService{#region 初始化private readonly IRepository<RolePermissions> _rolePermissionsRepository;private readonly IRepository<SysPermission> _sysPermissionsRepository;private readonly IRepository<IdentityUserRole> _userRolesRepository;public SysPermissionAppService(IRepository<RolePermissions> rolePermissionsRepository,IRepository<SysPermission> sysPermissionsRepository,IRepository<IdentityUserRole> userRolesRepository
){_rolePermissionsRepository = rolePermissionsRepository;_sysPermissionsRepository = sysPermissionsRepository;_userRolesRepository = userRolesRepository;}#endregion#region 按服务注册权限/// <summary>/// 按服务注册权限/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="permissions">权限列表</param>/// <returns></returns>public async Task<bool> RegistPermission(string serviceName, List<SysPermissionDto> permissions){//根据服务名称查询现有权限var entities = await AsyncExecuter.ToListAsync( (await _sysPermissionsRepository.GetQueryableAsync()).Where(c => c.ServiceName == serviceName));var lst = ObjectMapper.Map<List<SysPermissionDto>, List<SysPermission>>(permissions);ListCompare.MutiCompare(lst, entities, x => x.Code, async (entity, isNew) =>{if (isNew){//新增await _sysPermissionsRepository.InsertAsync(entity);}else{//修改var tmp = lst.FirstOrDefault(x => x.Code == entity.Code);//调用权限判断方法,如果code和name相同就不进行添加if (!entity.Equals(tmp)&&tmp!=null){entity.SetId(tmp.Id);await _sysPermissionsRepository.UpdateAsync(entity);}}}, out var needRemove);foreach (var item in needRemove){//删除多余项await _sysPermissionsRepository.DeleteAsync(item.Value);}return true;}#endregion#region 按服务获取权限/// <summary>///     按服务获取权限/// </summary>/// <param name="serviceName">服务名称</param>/// <returns>查询结果</returns>public async Task<List<SysPermissionDto>> GetPermissions(string serviceName){var query = (await _sysPermissionsRepository.GetQueryableAsync()).Where(x => x.ServiceName == serviceName);//使用AsyncExecuter进行异步查询var lst = await AsyncExecuter.ToListAsync(query);//映射实体类到dtoreturn ObjectMapper.Map<List<SysPermission>, List<SysPermissionDto>>(lst);}#endregion#region 获取完整权限树/// <summary>/// 获取完整权限树/// </summary>/// <returns>查询结果</returns>public async Task<List<PermissionTreeDto>> GetPermissionTree(){var per = await _sysPermissionsRepository.ToListAsync();var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(per);return PermissionTreeBuilder.Build(lst);}#endregion#region 获取用户权限码/// <summary>/// 获取用户权限码/// </summary>/// <param name="userId">用户编号</param>/// <returns>查询结果</returns>public async Task<List<string>> GetUserPermissionCode(Guid userId){var query = from user in (await _userRolesRepository.GetQueryableAsync()).Where(c => c.UserId == userId)join rp in (await _rolePermissionsRepository.GetQueryableAsync()) on user.RoleId equals rp.RoleIdjoin pe in (await _sysPermissionsRepository.GetQueryableAsync()) on rp.PermissionId equals pe.Idselect pe.Code;var permission = await AsyncExecuter.ToListAsync(query);return permission.Distinct(x=>x);}#endregion}
}

2e9636e35cd687e69a7bda0f414e2491.gif

添加角色权限关系管理实现类RolePermissionsAppService如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Demo.Identity.Permissions.Dto;
using Demo.Identity.Permissions.Entities;
using Volo.Abp.Domain.Repositories;namespace Demo.Identity.Permissions
{/// <summary>/// 角色管理应用服务/// </summary>public class RolePermissionsAppService : IdentityAppService, IRolePermissionsAppService{#region 初始化private readonly IRepository<RolePermissions> _rolePermissionsRepository;private readonly IRepository<SysPermission> _sysPermissionsRepository;public RolePermissionsAppService(IRepository<RolePermissions> rolePermissionsRepository,IRepository<SysPermission> sysPermissionsRepository
){_rolePermissionsRepository = rolePermissionsRepository;_sysPermissionsRepository = sysPermissionsRepository;}#endregion#region 获取角色所有权限/// <summary>/// 获取角色所有权限/// </summary>/// <param name="roleId">角色ID</param>/// <returns></returns>public async Task<List<PermissionTreeDto>> GetPermission(Guid roleId){var query = from rp in (await _rolePermissionsRepository.GetQueryableAsync()).Where(x => x.RoleId == roleId)join permission in (await _sysPermissionsRepository.GetQueryableAsync())on rp.PermissionId equals permission.Idselect permission;var permissions = await AsyncExecuter.ToListAsync(query);var lst = ObjectMapper.Map<List<SysPermission>, List<PermissionTreeDto>>(permissions);return PermissionTreeBuilder.Build(lst);}#endregion#region 设置角色权限/// <summary>/// 设置角色权限/// </summary>/// <param name="roleId">橘色编号</param>/// <param name="permissions">权限编号</param>/// <returns></returns>public async Task SetPermission(SetRolePermissionsDto dto){await _rolePermissionsRepository.DeleteAsync(x => x.RoleId == dto.RoleId);foreach (var permissionId in dto.Permissions){RolePermissions entity = new RolePermissions(){PermissionId = permissionId,RoleId = dto.RoleId,};await _rolePermissionsRepository.InsertAsync(entity);}}#endregion}
}

533db24a7e27c1df73b6eb3a7547613b.gif

 在Demo.Identity.EntityFrameworkCore项目IdentityDbContext类中加入以下属性:

public DbSet<SysPermission> SysPermissions { get; set; }
public DbSet<RolePermissions> RolePermissions { get; set; }

541fee81fe0e6e01440cada0ca593859.gif

在Demo.Identity.EntityFrameworkCore项目目录下启动命令提示符,执行以下命令分别创建和执行数据迁移:

dotnet-ef migrations add AddPermissions
dotnet-ef database update

7b7b9ffcbbf1363fbedb091b3b45329d.gif

在Demo.Identity.EntityFrameworkCore项目IdentityEntityFrameworkCoreModule类ConfigureServices方法中找到 options.AddDefaultRepositories(includeAllEntities: true); ,在其后面加入以下代码:

options.AddDefaultRepository<IdentityUserRole>();

51cc46674ce16b0cac6aa979d588dcf6.png

完成后运行身份管理服务,可正常运行和访问各接口,则基础服务层修改完成。后续操作请看下一篇

f8e71b4940c5412ab101fd7014ee2fb3.png

end

781c6fa3e12d116dbb90390fd14e7a12.png

cc34818bd82fcfd1bb2808e7b6dbe677.png

5b994a5a44dd2ca3f1d7d13201560b2a.png

更多精彩

关注我获得

4d2710f8e7134bc22784242e2f96ca64.png

这篇关于ABP vNext微服务架构详细教程——分布式权限框架(上)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/352041

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF