热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CMDB资产采集笔记

一、资产采集四种方式1.Agent方式API:Django接收数据并入库程序:放置在每台服务器应用场景:针对服务器较多的公司步骤一:#执行

 一、资产采集四种方式

 1. Agent方式

API:Django接收数据并入库程序:放置在每台服务器应用场景:针对服务器较多的公司步骤一:#执行本地命令的库import subprocesssub = subprocess.getoutput("要执行的命令名")每台机器通过用户名密码链接数据库,获取要执行的命令步骤二: 采集数据import subprocess# 采集到本机运行ipconfig命令的输出结果result = subprocess.getoutput("ipconfig")print(result)步骤三: 筛选整理数据# 正则等方式result_dic = {"网络信息":result,}步骤四:发送数据到api# 以post方式将数据发送到urlimport requestsrequests.post("url", result_dic)

2. SSH方式

import paramiko
# 通过paramiko库链接各台机器,获取命令
#缺点是慢API:Django接收数据并入库程序:放在中控机应用场景:针对服务器较少的公司SSH方式有三种工具,paramiko、Fabric、Ansible,其中Fabric、Ansible内部原理也是通过paramiko来实现得。
paramiko安装:pip3 install paramikoandible,fabic等工具对paramiko进行了封装,方便使用 速度更快一些!import requests,paramiko
# 获取今天未采集的主机名
# 从url中获取信息
host_list = requests.get("url")# 通过paramiko连接远程服务器执行命令
# 创建ssh对象
ssh = paramiko.SSHClient()# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())# 连接服务器
ssh.connect(hostname="hostname", port=22, username="root", password="password")# 执行命令
stdin, stdout, stderr = ssh.exec_command("hostname")# 获取结果
result = stdout.read()
print(result)# 关闭连接
ssh.close()# 筛选整理数据
result_dic = {"网络信息":result,
}# 发送获取的数据
requests.post("url", result_dic)

3. saltstack【python语言】

方式同ssh,原理不同
优点:提高速度,开发成本低
缺点:依赖saltstack工具
可以远程执行命令http://www.cnblogs.com/wupeiqi/articles/6415436.htmlAPI:Django接收数据并入库应用场景:针对服务器较多的公司【推荐】master:v = subprocess.getoutput('salt "*" cmd.run "ls"')saltstack内部原理是通过RPC来实现得消息队列步骤一: 安装saltstackrpm --import https://repo.saltstack.com/yum/redhat/7/x86_64/latest/SALTSTACK-GPG-KEY.pubyum install salt-master 服务端yum install salt-minion 客户端步骤二: Master准备a.配置文件,修改监听ip/etc/salt/masterinterface: 本机ip地址b.启动Master/etc/init.d/salt-master start步骤三: Slave准备a.修改配置文件,连接哪个master/etc/salt/minionmaster: 远程master的ipb.启动Slave/etc/init.d/salt-slave start步骤四: 创建关系Master:salt-key -L 查看哪些连接salt-key -a 接受连接salt-key -A 接收全部连接步骤五: 执行命令Master:salt "连接" cmd.run "命令"# salt 是一个.py文件
#!/usr/bin/python
# Publish commands to the salt system from the command line on the master.from salt.scripts import salt_main
if __name__ == '__main__':salt_main()# 在Python文件中使用salt的方法(需要提前配置好)
import salt.client
local = salt.client.LocalClient()
result = local.cmd('对方ip', 'cmd.run', ['命令']) # result是一个字典
result.keys() # 命令
result.values() # 执行结果

4. puppet【ruby语言】

内部原理:puppet客户端每30秒钟自动汇报数据给puppet服务端。应用场景:主要是针对已经在使用puppet的公司。基于ruby 优点:自动汇报 缺点:要用ruby写

 

 二、高级配置文件路径

1.目录结构

2.整合配置文件的代码

import os
import importlib
from . import global_settingsclass Settings(object):def __init__(self):# ------------ 找到默认配置文件----------------for name in dir(global_settings):if name.upper(): # 配置文件里的变量名需要大写value = getattr(global_settings,name) # 拿到值setattr(self,name,value) # 设置值# ------------ 找到用户自定义配置文件------------settings_module = os.environ.get('USER_SETTINGS') # 加载设置到环境变量中的配置文件路径名if not settings_module: # 如果用户没有自定义配置文件或没有初始化自定义文件return # 直接返回不做其他操作user_settings = importlib.import_module(settings_module)for name in dir(user_settings):if name.upper(): # 配置文件里的变量名需要大写value = getattr(user_settings,name) # 拿到值setattr(self,name,value) # 设置值settings = Settings() # 实例化对象

  

 3.加载自定义配置文件的名称到环境变量中

# 以下代码放置需要执行的py文件中
import os
os.environ['USER_SETTINGS'] = "config.settings"
from lib.conf.config import settings # 通过settings可以点出在自定义或默认配置文件中的相关变量名

  

三、可插拔式插件 (资产采集)

1.目录结构

 

 

2.流程介绍

主程序start.py 去执行 script.run()

import os
os.environ['USER_SETTINGS'] = "config.settings" # 初始化
import sys
BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR) #上级目录加入环境变量中
from src import script # 导入执行代码
if __name__ == '__main__':script.run()

  

 script.py中 根据MODE去执行相应的方法

MODE是资产采集中四种方法中的一种

from lib.conf.config import settings
from .client import Agent
from .client import SSHSALTdef run():if settings.MODE == 'AGENT': # 配置文件中MODE的值为AGENT 或 ALT,SSHobj = Agent()else:obj = SSHSALT()obj.execute()

  

client.py中主要是拿到查到的主机的硬盘、CPU、内存等信息发给API

如果是Agent:其中Agent 每个客户端自己查询到信息后直接发给API

如果是SSHSALT:中控机服务器先向API发送请求得到要查询的主机列表

然后遍历列表,

PluginManager(host).exec_plugin() # 依次传入每个主机的host信息,并拿到返回的查询信息

 

import requests
import json
from src.plugins import PluginManager
from lib.conf.config import settings# 基类 只有向API发送查询到信息的方法
class Base(object):def post_asset(self,server_info):requests.post(settings.API,json=server_info)# body: json.dumps(server_info) 直接写json=server_info可以把里面的东西改成json字符串# headers= {'content-type':'application/json'}# request.body# json.loads(request.body)# 继承基类,每个客户端自己向API发送查询到的信息
class Agent(Base):def execute(self):server_info = PluginManager().exec_plugin()self.post_asset(server_info) # 向API发送查询到信息class SSHSALT(Base):def get_host(self):# 获取未采集信息的主机列表response = requests.get(settings.API) # 中控机向API发送get请求获取要查询的主机信息result = json.loads(response.text) # "{status:'True',data: ['c1.com','c2.com']}"if not result['status']: # 状态发生异常,则直接结束跳过异常的主机returnreturn result['data']def execute(self):host_list = self.get_host() # 拿到主机列表清单for host in host_list: # 遍历拿到每个主机信息server_info = PluginManager(host).exec_plugin() # 依次传入每个主机的host信息,并拿到返回的查询信息self.post_asset(server_info) # 向API发送查询到信息

  

在plugins目录下的__init__.py文件中

函数 exec_plugin 获取所有的插件,执行插件里边的方法并获取插件返回值

从配置文件中获取到插件路径和类名

model_path,class_name = v.rsplit('.',1) # 从右向左按点分割一次,并赋值给相应变量

 

# 'basic': "src.plugins.basic.Basic" 数据结构
v 是"src.plugins.basic.Basic"

 

model_path,class_name = v.rsplit('.',1) # 从右向左按点分割一次,并赋值给相应变量
model_file = importlib.import_module(model_path) # 以字符串的形式导入模块
cls = getattr(model_file,class_name) # 利用反射机制拿到类对象

import importlib
import traceback
from lib.conf.config import settingsclass PluginManager(object):def __init__(self,hostname=None):self.hostname = hostnameself.plugin_dict = settings.PLUGINS_DICT # 插件存放的目录self.mode = settings.MODEself.debug = settings.DEBUGif self.mode == 'SSH':# 拿到配置文件中用来SSH登录的相关信息self.ssh_user = settings.SSH_USERself.ssh_pwd = settings.SSH_PWDself.ssh_port = settings.SSH_PORTself.ssh_key = settings.SSH_KEYdef exec_plugin(self):"""获取所有的插件,并执行获取插件返回值:return:"""response = {}for k,v in self.plugin_dict.items():# 'basic': "src.plugins.basic.Basic" 数据结构ret = {'status':True,'data':None}try:model_path,class_name = v.rsplit('.',1) # 从右向左按点分割一次,并赋值给相应变量model_file = importlib.import_module(model_path) # 以字符串的形式导入模块cls = getattr(model_file,class_name) # 拿到类对象if hasattr(cls,'initial'): # 如果类中有initial方法obj = cls.initial() # 执行类方法initial 并把返回值赋给objelse:obj = cls() # 否则直接实例化类对象# obj.process(self.command,self.debug) 执行插件中的process方法,传入command函数和debug信息result = obj.process(self.command,self.debug) # result = "根据v获取类,并执行其方法采集资产"ret['data'] = resultexcept Exception as e:ret['status'] = False# traceback.format_exc() 可以返回更详细的错误信息字符串ret['data'] = "[%s][%s] 采集数据出现错误 : %s" % (self.hostname if self.hostname else "AGENT", k, traceback.format_exc())response[k] = retreturn responsedef command(self,cmd):if self.mode == "AGENT":return self.__agent(cmd)elif self.mode == "SSH":return self.__ssh(cmd)elif self.mode == "SALT":return self.__salt(cmd)else:raise Exception('模式只能是 AGENT/SSH/SALT')def __agent(self,cmd): # 如果配置文件中的方法是AGENTimport subprocessoutput = subprocess.getoutput(cmd)return outputdef __ssh(self,cmd): # 如果配置文件中的方法是SSHimport paramiko# 秘钥连接# private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key)# ssh = paramiko.SSHClient()# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())# ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, pkey=private_key)# stdin, stdout, stderr = ssh.exec_command(cmd)# result = stdout.read()# ssh.close()# 用户名密码连接ssh = paramiko.SSHClient()ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())ssh.connect(hostname=self.hostname,port=self.ssh_port,username=self.ssh_user,password=self.ssh_pwd)stdin,stdout,stderr = ssh.exec_command(cmd)result = stdout.read() # 读到返回的信息ssh.close()return resultdef __salt(self,cmd): # 也是通过操作subprocess# import salt.client# local = salt.client.LocalClient()# result = local.cmd(self.hostname, 'cmd.run', [cmd])# return result[self.hostname]salt_cmd = "salt '%s' cmd.run '%s'"%(self.hostname,cmd,)import subprocessoutput = subprocess.getoutput(salt_cmd)return output # 读到返回的信息

  

四、唯一标识和线程池

1.唯一标识:主板SN(不准确)

标准化:
       - 主机名不重复(装机同时,主机名在cmdb中设置)

步骤:
    a. 装系统,初始化软件(CMDB),运行CMDB:
             - 通过命令获取主机名
             - 写入本地指定文件
    b. 将资产信息发送到API

    c. 获取资产信息:
           - 本地文件主机名 != 命令获取的主机名(按照文件中的主机名)
           - 本地文件主机名 == 命令获取的主机名

服务器资产采集(Agent):
    a. 第一次:文件不存在,或内容为空;
采集资产:
    - 主机名写入文件
    - 发送API

    b. 第N次:采集资产,主机名:文件中获取
SSH或Salt:
    中控机:获取未采集主机名列表:【c1.com 】

# 继承基类,每个客户端自己向API发送查询到的信息
class Agent(Base):def execute(self):# 获取主机名唯一标识server_info = PluginManager().exec_plugin()hostname = server_info['basic']['data']['hostname']certname = open(settings.CERT_PATH, 'r', encoding='utf-8').read().strip() # 配置文件里拿到初始主机名if not certname: # 第一次的时候,把主机名写入文件with open(settings.CERT_PATH, 'w', encoding='utf-8') as f:f.write(hostname)else: # 以配置文件的主机名为准server_info['basic']['data']['hostname'] = certnameself.post_asset(server_info) # 向API发送查询到信息

View Code
2.线程池

def run(self,host):server_info = PluginManager(host).exec_plugin() # 依次传入每个主机的host信息,并拿到返回的查询信息self.post_asset(server_info) # 向API发送查询到信息def execute(self):from concurrent.futures import ThreadPoolExecutorhost_list = self.get_host() # 拿到主机列表清单pool = ThreadPoolExecutor(10) # 创建的线程池中有10个线程for host in host_list: # 遍历拿到每个主机信息pool.submit(self.run,host) # 把run函数放入线程池

View Code

五、资产入库(API 资产入库同时对外提供数据访问接口)

1.后台API拿到发过来的主机信息

        server_info = json.loads(request.body.decode('utf-8')) # 查询到的主机信息

   hostname = server_info['basic']['data']['hostname'] # 拿到客户端自己查到的主机名

2.根据主机名查到数据库中未更新的老资产主机对象

        server_obj = models.Server.objects.filter(hostname=hostname).first() # 服务器对象

3.查不到则直接退出

        if not server_obj:

                return HttpResponse('当前主机名未在资产中录入')

4.录入资产信息

  

# -------------- 处理硬盘、网卡、内存等的信息资产入库----------------
PluginManager(server_info, server_obj, hostname).exec_plugin()
我们可以利用python的反射机制来定制插件

PluginManager类写在__init__.py里
来看一下代码

import importlib
from autoserver import settingsclass PluginManager(object):def __init__(self, server_info,server_obj,hostname):self.server_info = server_infoself.server_obj = server_objself.hostname = hostnameself.plugin_dict = settings.PLUGINS_DICT #引入配置文件中的插件路径信息# 'disk': "api.service.disk.Disk"def exec_plugin(self):"""获取所有的插件,并执行获取插件返回值:return:"""for k, v in self.plugin_dict.items():# 'disk': "api.service.disk.Disk", 数据结构ret = {'status': True, 'data': None}try: # -------借鉴Django中间件的形式------------model_path, class_name = v.rsplit('.', 1) # 从右向左按点分割一次,并赋值给相应变量model_file = importlib.import_module(model_path) # 以字符串的形式导入模块cls = getattr(model_file, class_name) # 拿到类对象if cls:if hasattr(cls, 'initial'): # 如果类中有initial方法obj = cls.initial() # 执行类方法initial 并把返回值赋给objelse:obj = cls() # 否则直接实例化类对象obj.run(self.server_info,self.server_obj,self.hostname) # 资产入库"except Exception as e:passreturn

__init__.py

 

没什么亮点 主要是 1.以字符串的形式导入模块,2.python机制来获取类对象和方法
再来看一下disk.py里面的代码

from repository import modelsclass Disk(object):def __init__(self):pass@classmethoddef initial(cls):return cls()def run(self,server_info,server_obj,hostname):# 采集信息出错时if not server_info['disk']['status']: # 采集硬盘信息时报错了的话models.ErrorLog.objects.create(content=server_obj['disk']['data'],asset_obj=server_obj.asset,title='【%s】硬盘采集错误信息'%(hostname))# 新磁盘字典信息new_disk_dict = server_info['disk']['data']"""slot 插槽位; pd_type 磁盘类型 ; capacity 磁盘容量GB; model 磁盘型号;{'5': {'slot': '5', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5NV'},'3': {'slot': '3', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5AH'},}"""# 旧磁盘列表信息old_disk_list = models.Disk.objects.filter(server_obj=server_obj)"""Queryset 列表字典类型,多个硬盘对象[Disk('slot':5,capacity:476...)Disk('slot':4,capacity:476...)]"""new_slot_list = list(new_disk_dict.keys()) # 那新磁盘字典信息中的key 即槽位号old_slot_list = []for row in old_disk_list: # 遍历拿到数据库中旧的磁盘号
old_slot_list.append(row.slot)# 交集 更新【5】 则更新数据库的磁盘信息update_list = set(new_slot_list).intersection(old_slot_list) # 交集列表# 差集 创建【3】 则创建新的磁盘信息进数据库create_list = set(new_slot_list).difference(old_slot_list) # 差集列表# 差集 删除【4】 则把数据库里的不在返回来的新磁盘字典里的删除del_list = set(old_slot_list).difference(new_slot_list)if del_list:# 删除models.Disk.objects.filter(server_obj=server_obj,slot__in=del_list).delete()# 记录日志models.AssetRecord.objects.create(asset_obj=server_obj.asset,content='移除硬盘%s'%(''.join(del_list)))# 新增record_list = []for slot in create_list:disk_dict = new_disk_dict[slot] # {'capacity': '476.939', 'slot': '4', 'model': 'S1AXNSAF303909M Samsung SSD 840 PRO Seriesdisk_dict['server_obj'] = server_obj # 多增加一个字段models.Disk.objects.create(**disk_dict) # 以字典的方式增加tmp = '新增硬盘 插槽位【{slot}】,磁盘类型【{pd_type}】,磁盘容量GB【{capacity}】,model磁盘型号【{model}】'.format(**disk_dict)record_list.append(tmp) # 依次把每次新增的信息加入一个列表中# 记录日志if record_list:con = ';'.join(record_list)models.AssetRecord.objects.create(asset_obj=server_obj.asset,content=con)# 更新cord_list = []row_map = {'capacity': '容量', 'pd_type': '类型', 'model': '型号'}for slot in update_list:new_dick_row = new_disk_dict[slot]# {'slot': '3', 'pd_type': 'SAS', 'capacity': '279.396', 'model': 'SEAGATE ST300MM0006 LS08S0K2B5AH'}old_disk_row = models.Disk.objects.filter(slot=slot,server_obj=server_obj).first()for k,v in new_dick_row.items(): # 遍历新信息的键和值# k: capacity;slot;pd_type;model# v: '476.939''xxies DXM05B0Q''SATA'value = getattr(old_disk_row,k) # 拿到数据库中旧的值if v != value: # 如果新值和旧值不一样,则说明需要更新cord_list.append('槽位%s %s由%s变更为%s'.format(slot,row_map[k],value,v))setattr(old_disk_row,k,v) # 更新数据库中的值old_disk_row.save() # 写入数据库# 记录日志if cord_list:tent = ";".join(cord_list)models.AssetRecord.objects.create(asset_obj=server_obj.asset,content=tent)

disk.py

在里边:

        新磁盘字典信息   new_disk_dict = server_info['disk']['data']

        旧磁盘列表信息   old_disk_list = models.Disk.objects.filter(server_obj=server_obj)

  拿到 新插槽号列表  new_slot_list = list(new_disk_dict.keys())

  拿到 旧插槽号列表  old_slot_list = [] 

    for row in old_disk_list:   

      old_slot_list.append(row.slot)

 

  update_list 交集 更新;create_list 差集 创建;del_list 差集 删除

  . . . . . .

  . . . . . .

 六、API验证

1.自定义API验证

客户端代码

import time,requests,hashlibctime = time.time() # 获取当前时间戳
key = 'asdfasdfasdfasdf098712sdfs'
new_key
= '%s|%s'%(key,ctime)m = hashlib.md5()
m.update(bytes(new_key,encoding
='utf-8'))
md5_key
= m.hexdigest()md5_time_key = '%s|%s'%(md5_key,ctime)
print(md5_time_key)
response
= requests.get("http://127.0.0.1:8000/api/asset.html",headers={'OpenKey':md5_time_key})
print(response.text)

View Code

服务端

  配置文件settings里加上  AUTH_KEY = "asdfasdfasdfasdf098712sdfs"

代码

api_key_record = {# "1b96b89695f52ec9de8292a5a7945e38|1501472467.4977243":1501472477.4977243
}
def asset(request):# 所有的信息放在 request.META 里client_md5_time_key = request.META.get('HTTP_OPENKEY')client_md5_key,client_ctime = client_md5_time_key.split('|')ctime = float(client_ctime)server_time = time.time()# 第一关if server_time - ctime > 10:return HttpResponse('此随机字符串已失效')# 第二关# settings.AUTH_KEYtmp = '%s|%s'%(settings.AUTH_KEY,client_ctime)m = hashlib.md5()m.update(bytes(tmp,encoding='utf-8'))server_md5_key = m.hexdigest()if server_md5_key != client_md5_key:return HttpResponse('字符串中的时间不允许被修改')# 维护 api_key_record 字典等容量for k in list(api_key_record.keys()):v = float(api_key_record[k])if server_time > v + 10 :del api_key_record[k]# 第三关if client_md5_time_key in api_key_record:return HttpResponse('此随机字符串已访问过服务器端')else:# 把随机字符串当做key,把发过来时的时间当做value 记录进维护的api_key_record字典api_key_record[client_md5_time_key] = client_ctimeif request.method == 'GET':import_info = '我是重要的数据'return HttpResponse(import_info)

View Code
 2.AES数据加密

#3.6安装 pip3 install pycryptodome
#
mac pip3 install pycrypto

安装

 

客户端

    utils.py是封装 数据加密、数据解密、API验证函数的组件

from Crypto.Cipher import AES
from lib.conf.config import settings# 数据加密
def encrypt(message):"""数据加密:param message::return:"""key = settings.DATA_KEYcipher = AES.new(key, AES.MODE_CBC, key)ba_data = bytearray(message,encoding='utf-8')v1 = len(ba_data)v2 = v1 % 16if v2 == 0:v3 = 16else:v3 = 16 - v2for i in range(v3):ba_data.append(v3)final_data = ba_data.decode('utf-8')msg = cipher.encrypt(final_data) # 要加密的字符串,必须是16个字节或16个字节的倍数return msg# 数据解密
def decrypt(msg):"""数据解密:param message::return:"""from Crypto.Cipher import AESkey = settings.DATA_KEYcipher = AES.new(key, AES.MODE_CBC, key)result = cipher.decrypt(msg) # result = b'\xe8\xa6\x81\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0sdfsd\t\t\t\t\t\t\t\t\t'data = result[0:-result[-1]]return str(data,encoding='utf-8')# API验证
def auth():"""API验证:return:"""import hashlib,timectime = time.time()key = "asdfasdfasdfasdf098712sdfs"new_key = "%s|%s" %(key,ctime,)m = hashlib.md5()m.update(bytes(new_key,encoding='utf-8')) #里面是字节数据md5_key = m.hexdigest() #返回值是字符串类型
md5_time_key = "%s|%s" %(md5_key,ctime)return md5_time_key

utils.py

在client.py中的基类里,把要发给服务端的API信息进行加密

import requests
from lib.conf.config import settings
from lib.utils import encrypt,auth
class Base(object):def post_asset(self,server_info):data = encrypt(json.dumps(server_info))requests.post(url=settings.API,data=data,headers={'OpenKey':auth(),'Content-Type':'application/json'})

client.py

 

服务端

先弄出解密函数

def decrypt(msg):from Crypto.Cipher import AESkey = b'dfdsdfsasdfdsdfs'cipher = AES.new(key, AES.MODE_CBC, key)result = cipher.decrypt(msg) # result = b'\xe8\xa6\x81\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0sdfsd\t\t\t\t\t\t\t\t\t'data = result[0:-result[-1]]return str(data,encoding='utf-8')

解密函数

客户端通过post请求传过来server_info信息,然后直接调用解密函数解密即可

elif request.method == 'POST':server_info = decrypt(request.body)server_info = json.loads(server_info)

请求为POST时

 

转:https://www.cnblogs.com/guotianbao/p/7703921.html



推荐阅读
  • 本文介绍了django中视图函数的使用方法,包括如何接收Web请求并返回Web响应,以及如何处理GET请求和POST请求。同时还介绍了urls.py和views.py文件的配置方式。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
author-avatar
天王2502871933
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有