CMDB开发(二)
一、项目架构:目录规范
- # 遵循软件开发架构目录规范
- bin 启动文件
- src 源文件(核心代码)
- config 配置文件
- lib 公共方法
- tests 测试文件
二、采集规范
- # bin目录下新建start.py
- # Autor:cxiong
- from lib.conf.config import settings
- if __name__ == '__main__':
- print(settings.USER)
- # config下新建custom_settings.py
- USER = '自定义用户配置'
- # lib下新建conf目录,再新建config.py和global_settings.py
- # config.py
- from config import custom_settings
- from . import global_settings
- class Settings:
- def __init__(self):
- # 先设置默认配置,再设置自定义配置
- for name in dir(global_settings):
- if name.isupper():
- k = name
- v = getattr(global_settings, k)
- setattr(self, k, v)
- # 自定义配置
- for name in dir(custom_settings):
- if name.isupper():
- k = name
- v = getattr(custom_settings, k)
- setattr(self, k, v)
- settings = Settings()
- # global_settings.py
- USER = "默认用户配置"
- # 运行start.py后可以查看获得测试结果
插拔式功能
- #直接在start.py中书写逻辑代码
- # mode在settings设置
- # Autor:cxiong
- from lib.conf.config import settings
- if __name__ == '__main__':
- # 先读取配置文件方案配置
- mode = settings.MODE
- # 根据方案的不同,书写不同代码
- if mode == 'agent':
- import subprocess
- res = subprocess.getoutput('ipconfig')
- # 针对获取到的数据进行筛选处理
- print(res)
- elif mode == 'ssh':
- import paramiko
- # 创建对象
- ssh = paramiko.SSHClient()
- # 允许链接不在konows_hosts里的主机
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- # 链接服务器
- ssh.connect(hostname='127.
- 0.0.1',port=2222,username='root',password='123123')
- # 执行命令
- stdin,stdout,stderr = ssh.exec_command('ifconfig')
- # 获取结果
- res = stdout.read()
- # 断开链接
- ssh.close()
- print(res)
- else:
- import salt.client
- local = salt.client.LocalClient()
- res = local.cmd('127.0.0.1','cmd.run',['ifconfig'])
- print(res)
- # 这种方案存在的问题
- 存在的问题:
- 1.面向过程编程,扩展性差,后期不好维护、扩展
- 2.不符合代码设计规范
- # 高内聚低耦合
- """在写函数或者类的时候,代码中尽量不要多一行与函数或者类无关的代码(按照功能的不同拆分细化成不同的代码块)"""
- def get_user():
- # 先获取订单数据
- get_order()
- # 才能获取用户数据
- pass
- def get_order():
- pass
初始版本
- # 遵循高内聚低耦合
- '''
- 将面向过程编程修改为面向对象编程
- 在src文件夹内创建plugins文件夹,在该文件夹内根据信息的不同创建不同的py文件
- 存在的问题:
- 根据业务逻辑的不同,可能需要增加或者减少功能
- 代码需要修改,比较麻烦
- 3.参考django中间件
- 中间件如果我们不想执行某个只需要在配置文件中注释掉一行即可
- 如果想只需要添加一行字符串即可,并且也可以自定义中间件
- '''
- """
- django中间件方法例子
- 需求:开发一个通知系统
- 可以发邮件通知 短信通知 微信通知
- # settings.py
- NOTIFY_LIST = [
- 'notify.email.Email', # 类的字符串路径
- 'notify.message.Message',
- 'notify.weixin.Weixin',
- 'notify.qq.QQ'
- ]
- # notify/目录下
- # email.py
- class Email:
- def __init__(self):
- pass
- def send(self, message):
- print('邮箱通知:%s' %message)
- # settings文件
- # 模仿django配置文件功能
- NOTIFY_LIST = [
- 'notify.email.Email', # 类的字符串路径
- 'notify.message.Message',
- 'notify.weixin.Weixin',
- 'notify.qq.QQ'
- ]
- # __init__.py # 主要配置方法
- import settings
- import importlib
- def send_all(message):
- # 获取到所有发送通知的类,并且实例化产生对象调用send的方法
- for i in settings.NOTIFY_LIST:
- module_path, class_str = i.rsplit('.', maxsplit=1)
- # print(module_path, class_str)
- module = importlib.import_module(module_path) # from notify import email
- class_name = getattr(module,class_str) # 根据字符串获取模块里的变量名
- # print(class_name)
- obj = class_name() # 实例化对象
- obj.send(message) # 调用类里面绑定给对象的方法
- """
迭代版本
上面迭代版本有插拔式模块,可以根据需求增加模块并在settings.py中添加就可以实现
- """
- 最终版本
- 多个采集py文件中出现了大量的重复代码
- 1.将多个类里面相同的属性或者代码抽取出来形成一个父类
- """
- 对象:具有一系列属性和功能的结合体
- 类:多个对象共同的属性和功能的结合体
- 父类:多个类共同的属性和功能的结合体
- """
- class Base:
- # 填写if代码
- class Board(Base):
- pass
- 2.在PluginsManager中定义一个方法传递给所有的对象
- 完善代码
- 1.__cmd_shh需要用户名、密码、端口等信息
- 2.__cmd_salt需要服务器地址
- 也就意味着不同的方案需要有不同的额外参数
- class PluginsManager:
- def __init__(self, hostname=None):
- self.plugins_dict = settings.PLUGINS_DICT
- self.hostname = hostname
- if settings.mode == 'ssh':
- self.port = settings.SSH_PORT
- self.name = settings.SSH_USERNAME
- self.pwd = settings.SSH_PASSWORD
- """
- 这里有一个前提:所有的服务器上都必须有一个相同的用户
- 而这个前提在实际工作中也是可以的实现的,是安全且被允许的
- """
代码:IP以及账号密码暂未处理;服务器信息采集命令就是固定的
- # 服务器账号密码IP端口和命令暂未分离
- # 仅分离了功能
- """ bin/start.py """
- from src.plugins import PluginsManager
- if __name__ == '__main__':
- res = PluginsManager().execute()
- print(res)
- """ config/custom_settings.py """
- # 采集方案
- MODE = 'agent'
- # 基于django中间件思想完成功能的插拔式设计
- PLUGINS_DICT = {
- "board": "src.plugins.board.Board",
- "disk": "src.plugins.disk.Disk",
- "memory": "src.plugins.memory.Memory",
- }
- """lib/conf/config.py"""
- from config import custom_settings
- from . import global_settings
- class Settings:
- def __init__(self):
- # 先设置默认配置,再设置自定义配置
- for name in dir(global_settings):
- if name.isupper():
- k = name
- v = getattr(global_settings, k)
- setattr(self, k, v)
- # 自定义配置
- for name in dir(custom_settings):
- if name.isupper():
- k = name
- v = getattr(custom_settings, k)
- setattr(self, k, v)
- settings = Settings()
- """lib/conf/global_settings.py"""
- USER = "默认用户配置"
- """src/plugins/board.py"""
- # 采集主板信息
- from lib.conf.config import settings
- class Board:
- def process(self,command_func):
- command_func('ipconfig')
- return "board info"
- """src/plugins/__init__.py"""
- from lib.conf.config import settings
- class PluginsManager:
- def __init__(self):
- self.plugins_dict = settings.PLUGINS_DICT
- def execute(self):
- # {'board': 'src.plugins.board.Board', 'disk': 'src.plugins.disk.Disk', 'memory': 'src.plugins.memory.Memory'}
- response = {}
- for k,v in self.plugins_dict.items():
- # k标识,v类路径
- module_path,class_str = v.rsplit('.',maxsplit=1)
- # 利用字符串导入模块
- import importlib
- module_name = importlib.import_module(module_path)
- # 获取类变量名
- class_name=getattr(module_name,class_str)
- #类名加括号实例化对象
- class_obj=class_name()
- # 执行绑定方法process
- res = class_obj.process(self.__cmd_run)
- response[k] = res
- return response
- # 定义一个私有的方法
- def __cmd_run(self,cmd):
- # 根据方案的不同,书写不同代码
- mode = settings.MODE
- if mode == 'agent':
- self.__cmd_agent(cmd)
- elif mode == 'ssh':
- self.__cmd_ssh(cmd)
- # import paramiko
- # # 创建对象
- # ssh = paramiko.SSHClient()
- # # 允许链接不在konows_hosts里的主机
- # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- # # 链接服务器
- # ssh.connect(hostname='127.0.0.1', port=2222, username='root', password='123123')
- # # 执行命令
- # stdin, stdout, stderr = ssh.exec_command(cmd)
- # # 获取结果
- # res = stdout.read()
- # # 断开链接
- # ssh.close()
- # print(res)
- elif mode == 'salt':
- self.__cmd_salt(cmd)
- # import salt.client
- # local = salt.client.LocalClient()
- # res = local.cmd('127.0.0.1', 'cmd.run', [cmd])
- # print(res)
- else:
- print('目前只支持agent/SSH/salt-stack方案')
- # 根据模式的不同拆分不同的方法
- def __cmd_agent(self,cmd):
- import subprocess
- res = subprocess.getoutput(cmd)
- # 针对获取到的数据进行筛选处理
- return res
- def __cmd_ssh(self,cmd):
- import paramiko
- # 创建对象
- ssh = paramiko.SSHClient()
- # 允许链接不在konows_hosts里的主机
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- # 链接服务器
- ssh.connect(hostname='127.0.0.1', port=2222, username='root', password='123123')
- # 执行命令
- stdin, stdout, stderr = ssh.exec_command(cmd)
- # 获取结果
- res = stdout.read()
- # 断开链接
- ssh.close()
- return res
- def __cmd_salt(self,cmd):
- """python不支持salt模块,python2才能使用"""
- # import salt.client
- # local = salt.client.LocalClient()
- # res = local.cmd('127.0.0.1', 'cmd.run', [cmd])
- # return res
- """python3使用subprocess模块代替"""
- import subprocess
- command = 'salt "xxxxx" cmd.run %s' %cmd
- res = subprocess.getoutput(command)
- return res
代码
完善代码:
代码再次改善:分离IP账号密码等
- # 需要修改的代码
- """src/plugins/__init__.py"""
- from lib.conf.config import settings
- class PluginsManager:
- def __init__(self,hostname=None):
- self.plugins_dict = settings.PLUGINS_DICT
- self.hostname =
- # 前提是所有服务器都必须有一个相同的用户,实际工作中也可以实现,是安全也被允许的
- if settings.mode == 'ssh':
- self.port = settings.SSH_PORT
- self.name = settings.SSH_USERNAME
- self.pwd = settings.SSH_PASSWORD
- def execute(self):
- # {'board': 'src.plugins.board.Board', 'disk': 'src.plugins.disk.Disk', 'memory': 'src.plugins.memory.Memory'}
- response = {}
- for k,v in self.plugins_dict.items():
- # k标识,v类路径
- module_path,class_str = v.rsplit('.',maxsplit=1)
- # 利用字符串导入模块
- import importlib
- module_name = importlib.import_module(module_path)
- # 获取类变量名
- class_name=getattr(module_name,class_str)
- #类名加括号实例化对象
- class_obj=class_name()
- # 执行绑定方法process
- res = class_obj.process(self.__cmd_run)
- response[k] = res
- return response
- # 定义一个私有的方法
- def __cmd_run(self,cmd):
- # 根据方案的不同,书写不同代码
- mode = settings.MODE
- if mode == 'agent':
- self.__cmd_agent(cmd)
- elif mode == 'ssh':
- self.__cmd_ssh(cmd)
- elif mode == 'salt':
- self.__cmd_salt(cmd)
- else:
- print('目前只支持agent/SSH/salt-stack方案')
- # 根据模式的不同拆分不同的方法
- def __cmd_agent(self,cmd):
- import subprocess
- res = subprocess.getoutput(cmd)
- # 针对获取到的数据进行筛选处理
- return res
- def __cmd_ssh(self,cmd):
- import paramiko
- # 创建对象
- ssh = paramiko.SSHClient()
- # 允许链接不在konows_hosts里的主机
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- # 链接服务器
- ssh.connect(hostname=self.hostname, port=self.port, username=self.name ,password=self.pwd)
- # 执行命令
- stdin, stdout, stderr = ssh.exec_command(cmd)
- # 获取结果
- res = stdout.read()
- # 断开链接
- ssh.close()
- return res
- def __cmd_salt(self,cmd):
- """python不支持salt模块,python2才能使用"""
- # import salt.client
- # local = salt.client.LocalClient()
- # res = local.cmd('127.0.0.1', 'cmd.run', [cmd])
- # return res
- """python3使用subprocess模块代替"""
- import subprocess
- command = 'salt %s cmd.run %s' %(self.hostname,cmd)
- res = subprocess.getoutput(command)
- return res
- """config/custom_settings"""
- # 采集方案
- MODE = 'agent'
- # 基于django中间件思想完成功能的插拔式设计
- PLUGINS_DICT = {
- "board": "src.plugins.board.Board",
- "disk": "src.plugins.disk.Disk",
- "memory": "src.plugins.memory.Memory",
- }
- SSH_PORT = 22
- SSH_USERNAME = 'root'
- SSH_PASSWORD = '123'
完善代码
三、信息采集
- # 采集主板信息
- class Board:
- def process(self,command_func,debug):
- if debug:
- # 读取本地数据
- output = open(os.path.join(settings.BASEDIR,'files/board.out'),'r',encoding='utf-8').read()
- else:
- # 读取线上服务器数据
- output = command_func('sudo dmidecode |grep -A8 "System Information"')
- return output
- # 针对服务器获取到的数据进行处理大致都是一样的逻辑,对字符串进行切割处理
key_map = {
"Manufacturer": '',
"Product Name": '',
"Serial Number": '',
}
- res = {}
- for i in data.split('\n'):
- row = i.strip().split(':')
- if len(row) == 2:
- # 判断列表第一个元素在不在需要的字典键中
- if row[0] in key_map:
- res[row[0]] = row[1].strip()
- print(res)
四、采集异常处理
各类接口:https://www.juhe.cn/?bd_vid=11652700683301661916
- 1.status_code
- 响应状态码
- 由于在采集数据的时候可能会出现各式各样的错误,我们应该在交互数据的环境添加上响应状态码
- 2.异常处理
- import traceback
- def func():
- name
- try:
- func()
- except Exception as e:
- print('打印的结果:',traceback.format_exc())
- 打印的结果:
- Traceback (most recent call last):
- File "/Users/jiboyuan/PycharmProjects/autoclient/tests/s2.py", line 7, in <module>
- func()
- File "/Users/jiboyuan/PycharmProjects/autoclient/tests/s2.py", line 5, in func
- name
- NameError: name 'name' is not defined
五、服务端数据采集
- 1.需要将采集到的数据发送给服务端
- 但是直接在start.py中书写又不符合代码编写规范
- 2.针对django后端的requests对象
- 当提交post请求获取数据都是用的requests.POST
- 但是只有在contentType参数是urlencoded的时候requests.POST才会有数据
- 如果是application/json,提交post请求数据并不会放到requests.POST中而是原封不动的放在requests.body中
- requests.body中数据都是原封不动的二进制格式(bytes类型)
- 3.针对agent模式我们需要将数据基于网络发送给服务端
- 4.针对ssh和saltstack模式我们并不是需要将数据发送给服务端而是需要从服务端这里获取到我们想要采集的服务器地址
- """api接口中的视图函数需要做get请求和post请求处理"""
- get请求用来给ssh和saltstack返回服务器地址列表
- post请求用来给agent模式发送数据
- def getInfo(requests):
- if requests.method == 'POST':
- server_info = json.loads(requests.body)
- return HttpResponse('OK')
- # 连接后台数据库获取主机名列表并返回
- return ['c1.com','c2.com']
- # 我们为了偷懒直接合并到一个视图函数 其实也可以拆开 都行
django代码:
- # 创建项目
- startapp API
- # 项目注册:settings.py,注销csrf
- # INSTALLED_APPS中新增项目
- 'API',
- # urls.py新增,接收agent发送的数据
- url(r'^getInfo/',views.getInfo)
- # API/views.py
- from django.shortcuts import render, HttpResponse
- # Create your views here.
- import json
- def getInfo(requests):
- # 数据获取
- if requests.method == 'POST':
- server_info = json.loads(requests.body)
- for k,v in server_info.items():
- print(k,v)
- return HttpResponse('OK')
- # 链接后台数据库获取主机名列表并返回
- return ['c1.com','c2.com']
autoserver
5.1进程池与线程池
- """
- python2
- 有进程池但是没有线程池
- python3
- 既有进程池又有线程池
- """
- # 客户端代码修改
- # src/client.py
- # 由于ssh和saltstack都是获取主机名,所以两者直接整合到一起
- class SSHSalt(Base):
- def get_hostnames(self):
- hostnames = requests.get(settings.API_URL)
- return hostnames
- # 暂时用固定代码代替
- # return ['c1.com','c2.com']
- def run(self,hostname):
- server_info = PluginsManager(hostname).execute()
- self.post_data(server_info)
- def collectAndPost(self):
- hostnames = self.get_hostnames()
- # 循环每一个主机名,依次采集,单线程
- # for hostname in hostnames:
- # server_info = PluginsManager(hostname).execute()
- # self.post_data(server_info)
- """当主机名列表过于庞大,上述单线程处理方式非常慢,需要换成多线程"""
- # 采取线程池 多线程
- from concurrent.futures import ThreadPoolExecutor
- p = ThreadPoolExecutor(20)
- for hostname in hostnames:
- p.submit(self.run,hostname)
# bin/start.py修改
from lib.conf.config import settings
from src import client
if __name__ == '__main__':
if settings.MODE == 'agent':
client.Agent().collectAndPost()
else:
client.SSHSalt().collectAndPost()
5.2 优化start.py启动文件,避免出现逻辑判断
- # 针对start.py最最后的优化处理
- # 新建文件src/srcipt.py
- from src.client import Agent,SSHSalt
- from lib.conf.config import settings
- def run():
- if settings.MODE == 'agent':
- obj= Agent()
- else:
- obj = SSHSalt()
- obj.collectAndPost()
- # 启动文件bin/start.py修改
- from src.srcipt import run
- if __name__ == '__main__':
- run()
总结:
- 上述采集功能代码
- 如果是agent模式,只需要将代码部署到服务器上并执行定时任务即可
- 如果是ssh和saltstack方案只需要找一台服务器(中控机),通过api获取需要采集的服务器地址即可
六、唯一标识
- # 在资产统计过程中要想实现数据的更新和新增依据什么字段???
- 原则是在新的post数据中选取一个唯一字段然后到数据库中作为wehre条件获取对应的数据
- # 唯一字段
- 选取sn序列号(mac地址)作为唯一的字段
- 可能存在的问题
- 虚拟机和实体机是共用一个sn的,会导致数据不准确
- 解决的措施
- 1.纯业务层面上,如果公司不需要采集虚拟机信息那么使用sn没有问题(很少见)
- 2.采用hostname作为唯一标识
- 上述方案需要加认为的限制
- 在服务器给开发使用之前需要提前完成下列的操作
- 1.给这些服务器分配唯一的主机名
- 2.将分配好的主机名录入到后台管理的DB server表中
- 3.将采集的client代码运行一次,然后将得到的主机名地址保存到各自服务器某个文件中
- 4.之后就以该文件内主机名地址为准
- """
- 1.针对agent模式 上述方案可以考虑使用
- 2.但是针对ssh和saltstack模式一旦主机名修改没有还原直接导致该服务器资产无法采集到,责任落实到修改者
- """
- # src/client.py文件修改:增加主机名
- class Agent(Base):
- def collectAndPost(self):
- server_info = PluginsManager().execute()
- hostname = server_info['basic']['data']['hostname']
- res = open(os.path.join(settings.BASEDIR, 'config/cert'), 'r', encoding='utf-8').read()
- if not res.strip():
- # 第一次采集,将采集到的hostname写入文件中
- with open(os.path.join(settings.BASEDIR, 'config/cert'), 'w', encoding='utf-8') as f:
- f.write(hostname)
- else:
- # 第二次采集的时候,永远以第一次文件中保存的主机名为准
- server_info['basic']['data']['hostname'] = res
- # for k, v in server_info.items():
- # print(k, v)
- self.post_data(server_info)
唯一字段
CMDB开发(二)的更多相关文章
- Python CMDB开发
Python CMDB开发 运维自动化路线: cmdb的开发需要包含三部分功能: 采集硬件数据 API 页面管理 执行流程:服务器的客户端采集硬件数据,然后将硬件信息发送到API,API负责将获取 ...
- iOS开发-二维码扫描和应用跳转
iOS开发-二维码扫描和应用跳转 序言 前面我们已经调到过怎么制作二维码,在我们能够生成二维码之后,如何对二维码进行扫描呢? 在iOS7之前,大部分应用中使用的二维码扫描是第三方的扫描框架,例如Z ...
- javaweb学习之Servlet开发(二)
javaweb学习总结(六)--Servlet开发(二) 一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件web.xml中,可以使用一个或多个< ...
- Java Web高性能开发(二)
今日要闻: 性价比是个骗局: 对某个产品学上三五天个把月,然后就要花最少的钱买最多最好的东西占最大的便宜. 感谢万能的互联网,他顺利得手,顺便享受了智商上的无上满足以及居高临下的优越感--你们一千块买 ...
- Android开发--二维码开发应用(转载!)
android项目开发 二维码扫描 基于android平台的二维码扫描项目,可以查看结果并且链接网址 工具/原料 zxing eclipse 方法/步骤 首先需要用到google提供的zxin ...
- Android Camera系列开发 (二)通过Intent录制视频
Android Camera系列开发 (二)通过Intent录制视频 作者:雨水 2013-8-18 CSDN博客:http://blog.csdn.net/gobitan/ 概述 使用Camera ...
- C#的百度地图开发(二)转换JSON数据为相应的类
原文:C#的百度地图开发(二)转换JSON数据为相应的类 在<C#的百度地图开发(一)发起HTTP请求>一文中我们向百度提供的API的URL发起请求,并得到了返回的结果,结果是一串JSON ...
- Qt计算器开发(二):信号槽实现数学表达式合法性检查
表达式的合法性 由于我们的计算器不是单步计算的,所以我们能够一次性输入一个长表达式.然而假设用户输入的长表达式不合法的话,那么就会引发灾难.所以有必要对于用户的输入做一个限制. 一些限制举例: 比方, ...
- (Java)微信之个人公众账号开发(二)——接收并处理用户消息(下)
接下来,我们再讲一下图文消息: 如图: 大家可以先从开发者文档中了解一下图文消息的一些参数: 如上图,用户回复4时,ipastor返回了几条图文消息,上图中属于多图文消息,当然还有单图文消息,图文消息 ...
- 以太坊开发(二)使用Ganache CLI在私有链上搭建智能合约
以太坊开发(二)使用Ganache CLI在私有链上搭建智能合约 在上一篇文章中,我们使用Truffle自带的客户端Truffle Develop,在私有链上搭建并运行了官方提供的WebPack智能合 ...
随机推荐
- QT实现参数批量配置
QT实现批量配置 需求 一些参数需要批量化配置 之前搭建的FPGA的寄存器控制模型 使用AXI-lite搭建 直接操作上位机 这里需要一个可以快速配置所有参数的上位机 需要保存文件,可以保留上一次的参 ...
- [ROS串口通信]报错:IO Exception (13): Permission denied, file /tmp/binarydeb/ros-noetic-serial-1.2.1/src/impl/unix.cc, line 151. [ERROR] [1705845384.528602780]: Unable to open port
ROS在串口通信时,当我们插入USB后,catkin_make之后,报错: IO Exception (13): Permission denied, file /tmp/binarydeb/ros- ...
- KingbaseESV8R6表空间与数据库,模式,表的关系
自定义表空间的作用 使用多个表空间可以更灵活地执行数据库操作.当数据库具有多个表空间时,您可以: 1.将用户数据与系统表数据分开存储在不同性能的存储上,以减少I/O争用. 2.将一个应用程序的数据与另 ...
- KingbaseES V8R6 等待事件之CLogControlLock
前言 Kingbase数据库的tuple行头部来标识这条记录的事务结束状态(未知.已提交.已回滚),在事务提交时如果并发更新100万行记录,需要对多个page的tuple进行更改,这种繁重的操作会对数 ...
- KingbaseES 语句like前匹配如何使用索引
前言 有现场同事反馈 sql语句 like 使用后缀通配符 % 不走索引. 至于执行计划没走索引的原因与KingbaseES数据库中的排序规则相关. 测试 测试环境: KingbaseESV8R6C7 ...
- Java生成Json字符串
public class Test01 { public static void main(String[] args) { // StringBuilder responseMsg = new St ...
- Socket.D v2.4.9 发布
Socket.D 是什么东东? 是基于"事件"和"语义消息""流"的网络应用协议.在微服务.移动应用.物联网等场景,可替代 http.web ...
- 30分钟成为Contributor|共建测试子系统,赋能提升项目代码质量
如何优雅地参与开源贡献,向顶级开源项目提交 PR(Pull Request),跟着大咖30分钟成为OpenAtom OpenHarmony(以下简称"OpenHarmony") C ...
- 【Kotlin】类和对象
1 前言 Kotlin 是面向对象编程语言,与 Java 语言类似,都有类.对象.属性.构造函数.成员函数,都有封装.继承.多态三大特性,不同点如下. Java 有静态(static)代码块,Ko ...
- RabbitMQ 08 路由模式
路由模式 路由模式结构图: 定义配置类. import org.springframework.amqp.core.Binding; import org.springframework.amqp.c ...