技术服务于业务,良好的技术设计和实现能够大幅提升业务质量和效率。
PowerDotNet已经形成了自己的开发风格,很多项目已被应用于生产环境,可行性可用性可靠性都得到了生产环境验证。
编程是非常讲究动手实践的科目,我们发明的框架、工具和方法论,如果自己都没有做出有说服力的产品,没有得到充分验证,如何说服别人使用呢?
眼看千遍,嘴说万遍,不如亲自动手实现一遍,咩哈哈。
从本文开始,将会介绍几种像第一篇基础数据平台一样,个人开发过的公共服务中更偏重于业务的公共服务系统,没错,某些业务系统也能成为全局通用的公共服务。
从第2篇到第11篇,更加偏重于公共框架服务、中间件和通用模块,而不是具体业务,这个都是开发业务逻辑程序的前期准备过程,是为了更快更好可扩展可伸缩可运维兼顾灵活性的开发业务系统。
PowerDotNet设计与实现的一个重要目标就是,对于通用和常用模块或功能,解耦并复用,减少重复建设,动动鼠标搞定一切脚手架型的框架搭建。
有了前面那些文章的铺垫,对于业务系统使用公共框架、中间件或服务,基本就是动动鼠标的事情。
我个人接触的广义的CRM,通常包括HCRM(人员或员工管理)、PCRM(个人用户管理)和ECRM(企业用户管理)。
本文只介绍HCRM人员管理平台(侧重于通用权限管理)这一部分,人员管理平台相对基础数据平台业务逻辑更复杂一点,对于PowerDotNet而言只能算是小试牛刀。后续有空再介绍PCRM和ECRM。
我们在开发应用的时候,总需要和不同的人员打交道,比如一线业务、二线支持人员等。根据经验,为他们开发合适的业务系统也是很有挑战的事情,尤其是某些业务(如拉取订单、办公打卡等)某些时间段业务高峰期,设计实现没处理好,很容易遇到性能瓶颈。
经过系统分析抽象和设计,我们发展出了HCRM系统,主要用于人员管理。
在前面介绍PowerDotNet的多篇文章中,我们总是能看到不同系统的管理后台,每个管理后台的菜单几乎都不一样。这些管理后台通用权限的开发,都离不开HCRM的设计与实现。
本文举几个常见功能,说说HCRM的平台建设。
环境准备
1、(必须).Net Framework4.5+
2、(必须)关系型数据库MySQL或SqlServer或PostgreSQL或MariaDB四选一
3、(必须)PowerDotNet数据库管理平台,主要使用DBKey功能
4、(必须)PowerDotNet配置中心Power.ConfigCenter
5、(必须)PowerDotNet注册中心Power.RegistryCenter
6、(必须)PowerDotNet缓存平台Power.Cache
7、(必须)PowerDotNet消息平台Power.Message
8、(必须)PowerDotNet文件平台Power.File(主要包括自定义头像上传、业务单据文件或附件上传等)
9、(必须)PowerDotNet基础数据平台Power.BaseData
根据经验,权限大概可以抽象为如下三个分类
功能权限是我们日常生活中最常见最直观理解的一种权限类别,主要解决能做什么的问题,如增加人员、修改资料等。
根据基于角色的权限控制(RBAC),设计实现人员功能权限管理。

有了用户,就需要关联用户角色(也可以通过用户分组对多个用户进行角色关联,用户分组下面介绍)

按照用户分组批量分配角色的情况也很常用,可以节省大量配置时间

再通过角色关联权限,就可以间接得到用户和权限的关系 (角色之间的权限则可以通过页面工具进行批量复制处理)

上面的示例,基本上是最基础简单高效的用户-角色-权限设计,事实上还可以对角色和权限再进行分组抽象,不过大多数业务没有必要,最基础的RBAC就是最强大的武器。
设计权限时,可以权限到菜单,也可以权限到按钮。权限是一种层级关系:
PowerDotNet在设计实现权限和菜单的时候,参考了很多现有设计,很多现成的方案都是菜单资源直接拿来当做权限使用,PowerDotNet则摒弃了这一做法,设计出了更加通用强大的自定义菜单系统。
一个重要原则是,菜单主要负责显示,有样式,有链接,有表现行为,而权限是静态数据,需要和菜单进行匹配关联才能起作用,权限不等于菜单,从而最大限度的复用权限和菜单功能。
实现权限控制到BS页面或者按钮也很简单,充分利用过滤器特性ActionFilterAttribute就行,提示也非常友好。参考下图:

CS结构的则更加简单,通过一个字典方法即可搞定。
数据权限主要是为了解决能看到哪些(范围)数据的问题,常见的业务场景比如:公司业务部门领导能查看所有下级的业务数据,普通人员只能查看本人的业务单据。
数据权限是对功能权限在纵向的扩展。
字段权限主要是为了解决能看到哪些(字段)信息的问题,常见的业务场景比如:可以禁止指定用户或角色对某些敏感字段(如账户金额、短信内容等)的访问。
字段权限是对功能权限在横向的扩展。
PowerDotNet目前完美支持数据权限和字段权限控制,PowerDotNet把数据权限和字段权限直接抽象为带有DBKey和SQL语句的功能权限。
数据和字段权限控制主要通过抽象出如下几个和SQL查询有关的字段:
SelectSQL:SQL语句的SELECT部分,支持SELECT *或SELECT 具体字段
TableName:SQL语句的FROM部分,支持数据表、临时表、视图、子查询等
WhereSQL:SQL语句的WHERE部分,支持占位符(通过{参数名}的形式),支持参数化@符号,只要构造好查询条件对象,框架自动构造并解析参数
OrderBySQL:SQL语句的ORDER BY部分,对查询数据结果排序
GroupBySQL:SQL语句的GROUP BY部分
HavingSQL:SQL语句的HAVING部分
PagerSQL:SQL语句的分页部分,分页通常由ORM搞定,这里基本用不到,目前支持{CurrentPage}和{RecordCount}两个动态条件
QuerySQL:完整SQL语句,支持参数化,如果查询结构简单,可以直接写完整SQL语句,不需要动态拼接
PowerDotNet的数据库ORM框架提供通用泛型GetPreparedSQL<T>(DataPurview permission, T qo)方法,内部动态拼接数据权限SQL语句,参数通过泛型方法GetDynamicParams<T>(string sql, T qo)反射构造完成。
通过上述两个框架方法,可以控制任意的数据权限和字段权限,绝大多数场景下的查询问题都可迎刃而解。
相比一些其他的数据和字段权限解决方案,PowerDotNet的权限控制不需要构造复杂的条件表达式和数据匹配关系,SQL完全由权限开发者掌握,配合DBKey万能SQL查询接口自动完成数据和字段权限功能。
人员分组是为了满足可扩展性、可伸缩性以及灵活性而特意设计的。
组织机构的设计天生就是用来对人员进行分组的。
对于一般公司而言,基本的组织机构就是公司部门,每次新增一个员工,自动就会进入公司部门分组。
但是常见的公司部门组织机构还远远不够,现实的人员分组情况远远比一般的公司部门型的组织结构更复杂。
前面我们提到,“可以对角色和权限再进行分组抽象”,HCRM经过权衡后没有对角色和权限做出这种复杂设计。
但是人员关系必须要分组才能更好解决问题。
为了更好地管理人员,对人员进行分组归类,PowerDotNet继续抽象,设计出HCRM通用的人员分组。

人员分组之间也有关系,常见的关系结构包括:
1、一维结构:人员分组之间是平等的,没有包含与被包含之分。
2、树形结构:每个人员分组可以有一个或零个父级人员分组。
3、多父结构:每个人员分组可以有多个或一个或零个父级人员分组,但不能形成循环依赖。
HCRM的人员分组原来设计是有经典的树状结构的上下级关系的,但是根据实际使用经验,树状层级关系在人员分组里基本属于过度设计,所以最新版本已经去掉了这个功能。
使用HCRM的人员分组功能,必须遵循一定的规范。
一个分组可以有多个用户,一个人员可以加入多个分组。
对于常见的人员层级关系,比如公司部门有部门领导,仓库有仓库负责人,门店有店长......这种人员层级关系也能通过人员分组的一维结构设计来解决。
人员分组支持设置直接领导,查询时稍加转换,自动支持了人员层级关系。
人员分组管理主要涉及三张表,即人员和分组关系表(emp_refer_group)、人员分组表(emp_group)、分组数据源表(group_data_source)。
分组数据源表(group_data_source)比较特殊,设计它主要是为了拿到对接的业务组织机构ID和名称(biz_dept_id,如公司部门Id、仓库Id、转运中心Id、前置仓Id、配送站点Id等等),因为PowerDotNet的服务治理框架和数据同步平台的存在,这个表通常情况下几乎可以不用。
因为人员分组表(emp_group)设计的好,emp_group的biz_data_source字段支持text、xml、json、或者直接服务名称、数据表名称。绝大多数的数据源场景都覆盖到了,非常有利于动态扩展。

通常对接业务方通过服务治理平台注册下数据源接口就可以了,也可以通过数据同步平台同步业务数据至HCRM分组数据源表(group_data_source),这两种模式非常有利于扩展各种数据源。
PowerDotNet建议直接通过服务治理平台,按照约定好的数据源契约,业务方提供数据源接口,HCRM不写一行代码就在配置中心配置下服务接口名称,服务治理框架自动搞定一切,对于各种新业务或者扩展业务做到完美兼容支持。
PowerDotNet的HCRM系统目前默认完美支持的组织机构类型包括公司部门、仓库、配送中心、配送站点、自提点、网点、转运中心、实体门店(如酒店等)、前置仓、供应商等,只要在管理后台点点按钮就可以支持更多这种可扩展的人员分组关系。
人员分组类型也支持动态“无限”灵活配置。
目前为止,HCRM这一套人员分组设计都运行良好。
简简单单一个登录功能,需要考虑并涉及到很多方面,包括登录用户名设计(通常支持用户名、工号、邮箱和手机)、cookie、session、token、http和https、LDAP、单点登录、“记住我”功能设计、登录试错次数、登录信息有效期、登录信息是否脱敏、强制退出(踢出)、验证码、安全问答、密码强度、重置密码、语音登录、二维码扫码登录、OAuth或OAuth2、登录安全日志等。
登录需要注意的最大问题是信息安全,鲁迅先生说“不惮以最坏的恶意来推测中国人”,对于信息安全,哪怕是内部人员管理系统,也要做最保守的安全防御编程。
别看登录功能基础且简单,设计不好,三天两头出问题,别问我怎么知道的,有些公司开发的系统之让人无语程度绝对超乎你的想象。
HCRM在设计登录功能的时候参考了很多公司和开源项目的现有实现,并提取出精华部分加以利用,已经在生产环境得到了验证。
HCRM这一套安全防御实现对后续开发PCRM和ECRM也有极其重要的参考价值,有空后续文章再介绍。
HCRM对外公开接口,用于用户登录:

每个管理后台基于Asp.Net的安全框架实现Form认证登录功能。

验证码长度可配置,可以根据实际页面效果调整验证码长度,否则验证码图片容易失真。

除了用户名、密码外,还可以通过手机、邮箱、工号等登录,登录功能可以扩展,支持OAuth等方式的授权登录。
支持验证码的动态生成和销毁,是否需要验证码以及验证码长度可通过配置中心动态配置来控制。
支持公共服务统一集成平台的基于Redis加Token的登录方式。
早期通过JWT实现用户登录认证基本逻辑,根据经验,绝大多数情况下JwtToken也就是当一个加密的cookie来用。
后续随着业务变化需要,JWT被改造为直接通过Redis+Token,实现通用的登录、授权、鉴权、安全退出等逻辑。
人事管理完全可以单独部署成独立应用,但是为了介绍方便,还是在HCRM里一起贴图说明下。
个人工作过的公司基本都有一整套完善的HCRM人员管理系统,常见模块包括人事基础、员工档案管理、招聘职位管理、考勤管理、薪资管理、假期管理、在线申请单据管理、报销凭证管理、报表管理、流程管理、组织架构管理、关联账号管理、员工职级管理、投票管理、会议管理、合同管理、常用资料管理等等。有些还和OA整合在一起,极大地提升无纸化办公效率。
PowerDotNet实现的HCRM系统自动集成了人事基础和常用人事管理,中小公司基本够用,简单截图看一下:








人事管理中,员工工资管理模块,对保密性、可靠性和权限控制有更高的要求,对工资的查看和操作都有详细审计日志(日志也要脱敏),保证数据安全,HCRM已经完美支持员工工资和员工调薪管理。
当然本文重点主要介绍最常见最通用的RBAC和登录,这是各种互联系统中最最基础的模块之一。
HCRM人员管理平台实现了常见的丰富的可扩展的人员管理功能,主要包括人员管理、通用权限管理、人员分组管理、登录管理、人事管理等核心模块。
目前PowerDotNet的通用权限管理已经自动集成对接所有PowerDotNet公共服务系统,仅需要很少的模板代码,就可以将权限控制到菜单、按钮和文件。数据和字段权限功能也只需要很少的业务代码(简单的则一行代码也不要接入方写,只要HCRM权限配置好SQL就行)即可自动集成。
现在新的应用想对接PowerDotNet的通用权限,仅需在管理后台点点按钮,后端埋点权限模板代码,不论BS(支持VUE和React等SPA应用)还是CS结构的应用程序,整个权限设置过程不超过5分钟就对接完成了,大大降低了权限控制门槛。
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项
我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)