1. Role permission module
1.1 Overview of RBAC
RBAC controls the user's permissions by defining the permissions of roles and granting a role to the user, which realizes the logical separation of users and permissions (different from the ACL model), which greatly facilitates the management of permissions
Before explaining, let’s introduce some nouns:
- User: Each user has a unique UID identification and is granted different roles
- Role: Different roles have different permissions
- Permission: access permission
- User-role mapping: the mapping relationship between users and roles
Role-permission mapping: mapping between roles and permissions
1.2 Current system design
The authority system is becoming more and more complex, and the demand side proposes the need to support multi-dimensional authorization
For example: employees in the R&D department can access gitlab; java development engineers can access the springboard; employees in Hangzhou can see the Asian Games information; only P6 level and above can see the company's profit report. As a result, the authorization of the system has become more and more complicated. What's more, only the leader of the R&D department can see the basic information of the current members of the R&D department...
Multi-tag model permission design (tag is a field that supports authorization, and the dimension can also be called tag label)
Since a certain type of authority is usually given to the user, the concept of authority group is removed. A permission group is a collection of several individual permissions
Current system:
The logic of query permissions is
1. Query the EmployeeRoleMap table according to employeeId to obtain roleIds
select roleIds from EmployeeRoleMap where employeeId = ?
2. Query the permission table to obtain permission association: (current tag only has RoleDimssionKey.ROLE)
select menuUID,menuGroupId from permission where value in [roleIds...] and key = RoleDimssionKey.ROLE
3. If there is menuGroupId (authority group id), query menu_group_mapping (authority-authority group association table) to get all the menuUIDs associated with the authorization group
select menuUID,menuGroupId from menu_group_mapping where menuGroupId in [...]
4. Query all Menus according to menuUID (if there is menuId in step 3, it will be queried together)
select * from menu_group_mapping where menuId in [...]
Permission group related logic is
Authority group configuration (operating platform)
The product spu is bound with the menuGroup attribute (temporary solution, it is recommended to strip the product attribute later, and directly bind the corresponding spu and permission group)
After the user purchases the goods and the payment is successful, the background logic will query the menu group bound to the current sku and add it to the permission (tag-authority association table)
insert into permission (KEY=ROLEDIMISSION.ROLEID,value=?,MENUGROUPID=?DATA_BI_MENU_GROUP_ID?)
2. sku commodity price calculation
In order to prevent fleece, products with a price of 0 yuan can only be purchased once
2.1 New users NoneUpgradeSkuFilter
You can directly query the price of sku products
2.2 Upgrade Account Number UpgradeAccountSkuFilter
Lock the duration = the duration closest to the current package , number of accounts is larger than the current package account
2.3 Upgrade time UpgradeTimeSkuFilter
Lock the account number equal to the current package account package
The code logic is
1. Query the organization_payment_detail table when purchasing to determine the type of purchase available. (The organization_payment_detail will be updated if the purchase is successful)
If organization_payment is empty (new user), the front end displays the purchase button,
The front end of organization_payment (expired or purchased status) displays the upgrade duration and the upgrade account button
2. The front end initiates a query sku request and carries the purchase type parameters, and the back end determines the filter according to the purchase type to perform product filtering and price calculation (such as NoneUpgradeSkuFilter, UpgradeAccountSkuFilter, UpgradeTimeSkuFilter)
The corresponding purchase type such as UpgradeAccountSkuFilter is responsible for product filtering and price calculation
The calculation logic is make up the difference (actual price = payable price-difference)
Package A 10 yuan per month for 10
Package B 20 RMB 20 per month
Package C 30 RMB 30 per month
Package D 4 months 10 pieces 40 yuan
Package E 5 months 10 50 yuan
1. Passerby user upgrade account
case1 Suppose today is 09-15, and package A is purchased on 09-01
You can upgrade the package to B\C
For example, the purchase price of package B is (20/30 (30-15))-10/30x15=5 yuan, which can be simplified to a 15-day difference (30-15)x(20/30-10/30)=5 Yuan, the current (the package becomes 09-15---->20 accounts on September 30)
2. Upgrade time for passerby users
case1 Suppose today is 09-15, and package A is purchased on 09-01
You can upgrade the package to D\E
Purchase D price is 40 yuan-10 yuan/30 days*unused days 15 days=(40-10/30x15)=35 yuan, the current package becomes 09-15---->4 months after 09-15 10 accounts
The purchase price of E is 50 yuan -10 yuan / 30 days * 15 days of unused days = (50-10/30x15) = 45 yuan, the current package becomes 09-15 ----> 5 months after 09-15 10 accounts
3. AD module
3.1 AD domain control basics
AD is the account management center for remote login of windows computer. Open the remote application, and each digital shadow user will be allocated an independent office space, that is, an AD account will be created. AD account creation is achieved by calling the powershell command line through java
3.2 Connection pool
Simulate C3P0 connection pool, thread pool and other principles to realize a reusable powershell connection pool
Demand analysis: The current system powershell is mainly used to assist DDC machines, AD related resources CRUD and other auxiliary powershell commands. Because AD domain control is connected, and powershell can be run remotely. Therefore, we expect that the agent deployed on the DDC01 machine can directly control the operation of the powershell on the machine and app01\addc01
Entry: machine name, script script
Powershell: remote execution, local execution
IRecycle可复用对象。id作为唯一标示,reset方法重置所有属性
ObjectPool抽象可复用资源池,使用LinkedBlockingQueue作为容器,防止多线程并发安全问题
DefaultRecyclePowerShellFactory powershell连接池
+String getId();
+void reset();销毁当前powershell session上下文
Remove-Variable * -ErrorAction SilentlyContinue -Exclude @(...)
DefaultRecyclePowerShell powershell可复用对象
4.websocket module
Compared with the traditional HTTP mode where each request-response requires the client to establish a connection with the server, WebSocket is a TCP long connection communication mode similar to Socket. After the WebSocket connection is established, subsequent data is transmitted in the form of a sequence of frames.
Handshake phase
a. The browser and the server establish a TCP connection, and a three-way handshake is performed. This is the basis of communication, the transmission control layer, if it fails, it will not be executed subsequently.
b. After the TCP connection is successful, the browser transmits information such as the version number supported by WebSocket to the server through the HTTP protocol. (HTTP handshake before starting)
c. After receiving the handshake request from the client, the server also uses the HTTP protocol to return data.
d. After receiving the message that the connection is successful, the communication is carried out through the TCP channel.
The client sends a message:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13
The server returns a message:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Technical selection:
Native websocket
springboot websocket (lightweight, spring integration, low development cost)
Stomp (similar to spring stream message, springboot websocket advanced protocol, the front end needs to use SOCKJS)
Netty SocketIO (lightweight, good performance, the front end needs to introduce socket.io.js)
spring websocket main components
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) 握手之前。可以做消息的拦截逻辑处理
Multiple instances of websocketSession have the following problems:
A websocket connects to the app1 server, and the next time it requests load balancing, it connects to the app02 server. At this time, the server needs to push websocket messages
Solution: Abstract WebsocketSender is responsible for sending messages. Currently implemented as RocketMqMessageSender
First check whether there is a qualified websocketSession in the current service, if it exists, send it directly, if it does not exist, send it to rocketMq and wait for other instances to raise the cancellation fee. (Pay attention to the problem of infinite loop, don't keep posting)
4.1 DSClient-StoreFront
WebsocketConfiguration配置类配置了两条websocket通道:Dsclient侧、前端侧
Dsclient-storeFront
DsclientHandler Dsclient侧websocket消息处理器 /api/v1/websocket/dsClient
ExtractParameterInterceptor提取request中的参数并封装到websocketSession中
BinderIdCheckInterceptor检查是否请求中具有BinderId参数
前端侧-storeFront
WebClientHandler 前端侧websocket消息处理器 /api/v1/websocket/webClient
AuthHttpSessionInterceptor 校验是否登录
WebClientHandler
INIT_BINDER_INFO 服务端返回binderId信息
REFRESH_APPLICATION_LIST 服务端转发Dsagent触发的REFRESH_APPLICATION_LIST时间
OPEN_APPLICATION 打开应用,转发给Dsagent
WEB_CLIENT_DIS_CONNECT 前端退出登录,转发给Dsagent
DsClientHandler
PUSH_LATEST_APPLICATION_INFO 刚连接时服务端会发送最新的本地应用列表
REPORT_LOCAL_APPLICATION_INFO 上报本地应用详情如安装进度,会触发REFRESH_APPLICATION_LIST事件
The process is as follows:
4.2 DSAgent-AgentManagerWeb
WSConfiguration websocket配置类,配置AgentWSHandler和AgentWSInterceptor
AgentWSHandler websocket消息处理器
AgentWSInterceptor提取参数封装到websocketSession上下文中
Dsagent side-storeFront
AgentWSInterceptor 负责Dsagent侧websocket握手。
为了后续不再传递当前session的唯一标识信息,如sessionId、machineSessionName等,故在握手成功时将这部分身份信息直接放入websocketSession中,类似httpHeader中的cookie标示
如
ws://localhost:9071/api/v1/websocket/dsAgent?machineName=machineName&machineSessionId=machineSessionId&userName=userName
AgentWSHandler 负责Dsagent消息处理
MACHINE_SESSION_REPORT:Dsagent上报会话应用信息
MACHINE_REPORT:Dsagent上报system0机器信息
MACHINE_SESSION_LOGOUT:运营平台下发。由服务端转发给Dsagent
The process is as follows:
Session information reporting process: (non-system0 users)
1. DsAgent reports the current session information in full every 15 seconds, namely the MACHINE_SESSION_REPORT event
2. The server stores information to Redis, and the expiration time is 20s
3. Operation platform front-end view session management, support paging query, fuzzy query
4. Click the logout button on the front end of the operation platform to issue MACHINE_SESSION_LOGOUT to DsAgent
5. Dsagent receives MACHINE_SESSION_LOGOUT, and the session is successfully logged out. Server webs co ke t disconnect and clear the current session information in redis
Machine information reporting process (system0 users): DsAgent reports machine information (MACHINE_REPORT) every 15 seconds, and the server-side storage message expiration time is 20s
Data paging widget:
Redis as an in-memory database data requires paging query, which depends on SimpleStringCache<T>. SimpleStringCache will build an index Map based on the @CacheIndex annotation
E.g:
@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 will build the following index Map for quick positioning
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"
}
]
}
When querying, fuzzy query will be performed based on the passed List<Map> filter
[
{ "id": "1",
"name",:"sh"
}
}]
For example, the above request will hit the id index and the name index. First, query IndexMap to obtain [0, 1] according to id=1 and obtain [1] according to name=sh. The and relationship only hits [1] in the end, and the final result goes to originData to query the final data as <1,haha2>
5. Expand
5.1 Distributed scheduling problem
The custom @DistributeTask annotation is currently used in the project: the concurrency problem of task scheduling in a high-availability environment is simply avoided by means of distributed locks. When APP01 executes scheduling, it uses Redission red lock to create a distributed lock, and releases the lock after the task execution ends. The APP02 task will also acquire this distributed lock when it comes.
Recommend Xxx-job to handle distributed timing tasks
5.2 Internal service authentication issues
At present, the internal service interface authentication relies on the public module dsphere-rpc-auth
Internal services that require authentication, such as dsphere-marketing-platform, need to rely on the dsphere-rpc-auth-service module. dsphere-rpc-auth-service will inject a HandlerIntecptor through spring.factories in the form of springboot starter, and the HandlerIntecptor will intercept URL conformance. /api/v1/auth/* request, make sure that the request header carries AUTHORIZATION=xxx, otherwise the verification will fail.
5.3 remote debug
java remote debug dependency
5.4 Online troubleshooting
arthas decompilation, dynamic modification and loading of clas files, jvm tuning and gc problem analysis
5.5 Distributed auto-increasing sequence id
Depends on the database InnoDB engine row lock implementation
@Component
@Slf4j
public 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);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。