1. 角色权限模块

1.1 RBAC概述

RBAC通过定义角色的权限,并对用户授予某个角色从而来管制用户的权限,实现了用户和权限的逻辑拆散(区别于ACL模型),极大中央便了权限的治理

上面在解说之前,先介绍一些名词:

  • User(用户):每个用户都有惟一的UID辨认,并被授予不同的角色
  • Role(角色):不同角色具备不同的权限
  • Permission(权限):拜访权限
  • 用户-角色映射:用户和角色之间的映射关系
  • 角色-权限映射:角色和权限之间的映射

    1.2 以后零碎设计

权限零碎日益简单,需求方提出须要反对多种维度受权

如:研发部的员工能够拜访gitlab;java开发工程师能够拜访跳板机;杭州的员工能够看到亚运会信息;P6级别以上能力看到公司利润报表。于是,零碎的受权也变得越来越简单,更有甚者,只有研发部部门的leader能够看到以后部门研发部成员的根本信息...

多tag模型权限设计(tag是反对受权的字段,维度也能够称之为tag标签)

因为通常是将某一类权限赋予给用户,故抽离出权限组的概念。权限组是若单个干权限的汇合

以后零碎:

查问权限的逻辑为

1.依据employeeId查问EmployeeRoleMap表获取角色汇合roleIds

select roleIds from EmployeeRoleMap where employeeId = ? 

2.查问permission表获取权限关联:(以后tag只有RoleDimssionKey.ROLE)

select menuUID,menuGroupId from permission where value in [roleIds...] and key = RoleDimssionKey.ROLE

3.若存在menuGroupId(权限组id),则查问menu_group_mapping(权限-权限组关联表)获取权限组关联的所有menuUID

select menuUID,menuGroupId from menu_group_mapping where menuGroupId in [...]

4.依据menuUID查问所有Menu(若步骤3中存在menuId,累计一起查问)

select * from menu_group_mapping where menuId in [...]

权限组相干逻辑为

权限组配置(经营平台)

商品spu绑定有menuGroup属性(长期解决方案,前期倡议剥离商品属性,间接绑定对应的spu和权限组)

用户购买商品付款胜利后,后盾逻辑会查问出以后sku绑定的菜单组,并增加到permission(tag-权限关联表)中

insert into permission (KEY=ROLEDIMISSION.ROLEID,value=?,MENUGROUPID=?DATA_BI_MENU_GROUP_ID?)

2.sku商品价格计算

为了避免薅羊毛,0元价格商品只能购买一次

2.1 新用户NoneUpgradeSkuFilter

间接查问sku商品价格即可

2.2 降级账号数量UpgradeAccountSkuFilter

锁定时长=离以后套餐最近的时长,账号数量大于以后套餐的账号 的套餐

2.3 降级时长UpgradeTimeSkuFilter

锁定账号数量等于以后套餐的账号 的套餐

代码逻辑为

1.购买时查问organization_payment_detail表,确定能够购买的类型。(购买胜利会更新organization_payment_detail)

organization_payment为空(新用户)前端显示购买按钮,

organization_payment(过期或已购买状态)前端显示降级时长、降级账号按钮

2.前端发动查问sku申请并携带购买类型参数,后端依据购买类型确定filter来进行商品的过滤和价格计算(如NoneUpgradeSkuFilter、UpgradeAccountSkuFilter、UpgradeTimeSkuFilter)

由对应的购买类型如UpgradeAccountSkuFilter负责商品的过滤及价格的计算

计算逻辑为 补差价 (理论价格=应酬价格-差价)

套餐A 1个月 10个 10元

套餐B 1个月 20个 20元

套餐C 1个月 30个 30元

套餐D 4个月 10个 40元

套餐E 5个月 10个 50元

1.路人甲用户 降级账号
case1 假如明天是09-15日,09-01日购买套餐A

则可降级套餐为B\C

如 购买B套餐价格为 (20/30(30-15))-10/30x15=5元 能够简化为须要补15天的差价(30-15)x(20/30-10/30)=5元,以后(套餐变为09-15---->09-30日 20个账号)

2.路人甲用户 降级时长
case1 假如明天是09-15日,09-01日购买套餐A

则可降级套餐为D\E

购买D价格为 40元-10元/30天*未应用天数15天=(40-10/30x15)=35元,以后套餐变为09-15---->09-15后4个月 10个账号

购买E价格为 50元-10元/30天*未应用天数15天=(50-10/30x15)=45元,以后套餐变为09-15---->09-15后5个月 10个账号

3. AD模块

3.1 AD域控根底

AD是windows计算机近程登录的账户管理中心,关上近程利用,会为每个数影用户调配独立的办公空间即创立AD账号。AD账号创立是通过java调用powershell命令行实现的

3.2 连接池

模仿C3P0连接池、线程池等原理实现一个能够复用的powershell连接池

需要剖析:以后零碎powershell次要用于帮助DDC机器、AD相干资源CRUD及其他辅助powershell命令。因为AD域控是连通的,且powershell能够近程运行。故咱们冀望部署在DDC01机器上的agent能够间接管制本机和app01\addc01上powershell的经营

入参:机器名、script脚本

Powershell:近程执行、本地执行

IRecycle可复用对象。id作为惟一标示,reset办法重置所有属性ObjectPool形象可复用资源池,应用LinkedBlockingQueue作为容器,避免多线程并发平安问题DefaultRecyclePowerShellFactory powershell连接池+String getId();+void reset();销毁以后powershell session上下文Remove-Variable * -ErrorAction SilentlyContinue  -Exclude @(...)DefaultRecyclePowerShell powershell可复用对象

4.websocket模块

绝对于传统HTTP每次申请-应答都须要客户端与服务端建设连贯的模式,WebSocket是相似Socket的TCP长连贯通信模式。WebSocket连贯建设后,后续数据都以帧序列的模式传输。

握手阶段

a.浏览器、服务器建设TCP连贯,三次握手。这是通信的根底,传输管制层,若失败后续都不执行。

b. TCP连贯胜利后,浏览器通过HTTP协定向服务器传送WebSocket反对的版本号等信息。(开始前的HTTP握手)

c. 服务器收到客户端的握手申请后,同样采纳HTTP协定回馈数据。

d. 当收到了连贯胜利的音讯后,通过TCP通道进行传输通信。

客户端发送音讯:

GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Version: 13

服务端返回音讯:

HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

技术选型:

原生websocket

springboot websocket(轻量级,spring集成,开发成本小)

Stomp(相似于spring stream音讯,springboot websocket高级协定,前端须要应用SOCKJS)

Netty SocketIO(轻量级,性能好,前端须要引入socket.io.js)

spring websocket次要组件

WebSocketConfigurer websocket配置类:增加音讯处理器和握手拦截器void registerWebSocketHandlers(WebSocketHandlerRegistry registry)如 registry.addHandler(agentWSHandler(), "/api/v1/websocket/dsAgent")                .setAllowedOrigins("*")                .addInterceptors(agentWSInterceptor);  TextWebSocketHandler文本音讯处理器void afterConnectionEstablished(WebSocketSession session)连贯建设胜利之后void handleMessage(WebSocketSession session, WebSocketMessage<?> message) 收到客户端推送的音讯void afterConnectionClosed(WebSocketSession session, CloseStatus status) 连贯断开之前  HandshakeInterceptor握手拦截器beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) 握手之前。能够做音讯的拦挡逻辑解决

websocketSession多实例存在以下问题:

A websocket连贯app1服务器,下次申请负载平衡连贯到了app02服务器。这个时候服务端须要推送websocket音讯

解决方案:形象出WebsocketSender专门负责message的发送。以后实现为RocketMqMessageSender

先查看以后服务是否存在符合条件的websocketSession,若存在间接发送,若不存在发送到rocketMq中期待其余实例拉取生产。(留神死循环问题,不要始终发)

4.1 DSClient-StoreFront

WebsocketConfiguration配置类配置了两条websocket通道:Dsclient侧、前端侧Dsclient-storeFrontDsclientHandler  Dsclient侧websocket音讯处理器 /api/v1/websocket/dsClientExtractParameterInterceptor提取request中的参数并封装到websocketSession中BinderIdCheckInterceptor查看是否申请中具备BinderId参数前端侧-storeFrontWebClientHandler 前端侧websocket音讯处理器 /api/v1/websocket/webClientAuthHttpSessionInterceptor 校验是否登录

WebClientHandler

INIT_BINDER_INFO 服务端返回binderId信息REFRESH_APPLICATION_LIST  服务端转发Dsagent触发的REFRESH_APPLICATION_LIST工夫OPEN_APPLICATION 关上利用,转发给DsagentWEB_CLIENT_DIS_CONNECT 前端退出登录,转发给Dsagent

DsClientHandler

PUSH_LATEST_APPLICATION_INFO 刚连贯时服务端会发送最新的本地利用列表REPORT_LOCAL_APPLICATION_INFO 上报本地利用详情如装置进度,会触发REFRESH_APPLICATION_LIST事件

流程如下:

4.2 DSAgent-AgentManagerWeb

WSConfiguration websocket配置类,配置AgentWSHandler和AgentWSInterceptorAgentWSHandler websocket音讯处理器AgentWSInterceptor提取参数封装到websocketSession上下文中

Dsagent侧-storeFront

AgentWSInterceptor 负责Dsagent侧websocket握手。为了后续不再传递以后session的惟一标识信息,如sessionId、machineSessionName等,故在握手胜利时将这部分身份信息间接放入websocketSession中,相似httpHeader中的cookie标示如ws://localhost:9071/api/v1/websocket/dsAgent?machineName=machineName&machineSessionId=machineSessionId&userName=userNameAgentWSHandler 负责Dsagent音讯解决MACHINE_SESSION_REPORT:Dsagent上报会话利用信息MACHINE_REPORT:Dsagent上报system0机器信息MACHINE_SESSION_LOGOUT:经营平台下发。由服务端转发给Dsagent

流程如下:

session会话信息上报流程:(非system0用户)

1.DsAgent每15秒全量上报以后session信息,即MACHINE_SESSION_REPORT事件

2.服务端存储信息到Redis,过期工夫为20s

3.经营平台前端查看会话治理,反对分页查问,含糊查问

4.经营平台前端点击登记按钮,下发MACHINE_SESSION_LOGOUT给DsAgent

5.Dsagent收到MACHINE_SESSION_LOGOUT,会话胜利登记。服务端webs co ke t断开清空redis中以后session会话信息

机器信息上报流程(system0用户):DsAgent每15秒上报机器信息(MACHINE_REPORT),服务端存储音讯过期工夫为20s

数据分页小工具:

redis作为内存数据库 数据须要分页查问,依赖于SimpleStringCache<T>。SimpleStringCache会基于@CacheIndex注解构建索引Map

例如:

    @Data    @Accessors(chain = true)    static class A{        @CacheIndex        private String name;        @CacheIndex        private String id;    }    public static void main(String[] args) {         List<A> list = new ArrayList();        A haha1 = new A().setName("haha").setId("51");        A haha2 = new A().setName("shiha").setId("761");                list.add(haha1);        list.add(haha2);        SimpleStringCache simpleStringCache = new SimpleStringCache(list);        List<Map> filter = new ArrayList<>();        Map map = new HashMap();        map.put("id", "1");        map.put("name", "sh");        filter.add(map);        simpleStringCache.query(filter).forEach(System.out::println);    }

simpleStringCache会构建如下索引Map用于疾速定位

Originate:<0,haha1><1,haha2>

IndexMap:

<id,51,[0]><id,761,[1]>

<name,haha,[0]>,<name,shiha,[1]>

{  "id": [    {      "51": "0",      "761": "1"    }  ],  "name": [    {      "haha": "0",      "shiha": "1"    }  ]}

查问时会根据传入的List<Map> filter进行含糊查问

[

{ "id": "1",

   "name",:"sh"

}

}]

如上述申请会命中id索引、name索引,首先查问IndexMap依据id=1含糊查问出【0,1】,依据name=sh含糊查问出【1】。and关系故最终只命中【1】,最初后果去originData中查问最终data为<1,haha2>

5.拓展

5.1 散布式调度问题

目前我的项目中应用自定义@DistributeTask注解:通过分布式锁的形式简略躲避了高可用环境下任务调度的并发问题。APP01执行调度时会应用Redission红锁创立一个分布式锁,工作执行完结后开释锁。APP02工作来长期同样会获取这个分布式锁。

举荐Xxx-job解决分布式定时工作

5.2外部服务鉴权问题

目前外部服务接口鉴权是依赖了公共模块dsphere-rpc-auth

须要鉴权的外部服务如dsphere-marketing-platform须要依赖dsphere-rpc-auth-service模块,dsphere-rpc-auth-service会通过spring.factories以springboot starter的形式注入一个HandlerIntecptor,该HandlerIntecptor会拦挡url合乎/api/v1/auth/*申请,确保申请头header中携带AUTHORIZATION=xxx,否则校验失败。

5.3 remote debug

java近程debug依赖

5.4 线上问题排查

arthas 反编译、动静批改并加载clas文件、jvm调优及gc问题剖析

5.5 分布式自增序列id

依赖于数据库InnoDB引擎行锁实现

@Component@Slf4jpublic class SequenceUtil {    @Autowired    private SequenceRepo sequenceRepo;    /**     * INNODB引擎默认行锁,能够保障更改不产生失落(只存在以后一个原子性操作)     * MVCC机制 应用以后读 获取最新版本数据     * @param sequenceEnum     * @return     */    @Transactional(propagation = Propagation.REQUIRES_NEW)    public Integer getId(SequenceEnum sequenceEnum) {        sequenceRepo.incrementCounter(sequenceEnum.getPrimaryKeyId());        int counterByName = sequenceRepo.findCounterById(sequenceEnum.getPrimaryKeyId());        log.info("id "+counterByName);        return counterByName;    }}public interface SequenceRepo extends CrudRepository<Sequence, Integer> {    @Query(value = "update sequence set counter = counter + 1 where id = (:id)", nativeQuery = true)    @Modifying    @Transactional    int incrementCounter(@Param("id")Integer id);    @Query(value = "select counter from sequence where id = (:id)", nativeQuery = true)    int findCounterById(@Param("id")Integer id);}