1.命令插件异常处理 + 日志采集

  1)为了更清楚发送客户端收集信息的状态 ,优化返回api的数据 ,变为字典存储 {状态 错误信息 数据} ,因为每个插件的每种系统下都要这个返回值我们将他单独做成类

  /lib/BaseReponse.py

class BaseReponese:
def __init__(self):
self.status = True
self.error = None
self.data = None @property
def dict(self):
return self.__dict__

  2)在执行命令的时候 ,debug模式下有打开文件的操作 ,打开文件可能出现目录不存在而导致程序终止 ,且这个错误信息仅能在服务端看到 ,这是不合理的所以同时引用日志

  /lib/log.py  日志做一个单例对象到处使用导入使用即可

import logging
from conf import settings class Logger:
def __init__(self, file_name, log_name):
file_handler = logging.FileHandler(file_name, encoding='utf-8')
fmt = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s")
file_handler.setFormatter(fmt) self.logger = logging.Logger(log_name)
self.logger.addHandler(file_handler) def info(self, msg):
return self.logger.info(msg) def error(self, msg):
return self.logger.error(msg) logger = Logger(settings.LOGGER_PATH, settings.LOGGER_NAME)

  所有的插件的linux'与win模式下都肯能出现这个问题 ,所以所有的都要加错误处理 + 客户端日志

    traceback可以直接捕获发生异常的文件 ,以及哪行出错

import traceback
from lib.BaseReponse import BaseResponese
def linux or win(self, handler, hostname=None):
response = BaseReponese()
try:
if self.debug:
with open(os.path.join(self.base_dir, 'files1', 'memory.out')) as f:
ret = f.read() else:
ret = handler.cmd('lsblk', hostname)
response.data = self.parse(ret)
except Exception as e:
logger.error(e)
response.error = traceback.format_exc()
response.status = False return response.dict

2.完善资产采集插件

  增加cpu采集 ,主板信息采集 ,基本信息采集

##settings.py
PLUGINS_DICT = {
'disk': 'src.plugins.disk.Disk',
'memory': 'src.plugins.memory.Memory',
'nic': 'src.plugins.nic.NIC',
'basic': 'src.plugins.basic.Basic',
'cpu': 'src.plugins.cpu.Cpu',
'board': 'src.plugins.main_board.MainBoard',
}

  cpu命令插件代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import traceback
from .base import BasePlugin
from lib.BaseReponse import BaseReponese
from lib.log import logger
from conf.settings import BASE_DIR class Cpu(BasePlugin):
def win(self, handler, hostname=None):
raise NotImplementedError('win must be implemented ') def linux(self, handler, hostname=None):
response = BaseReponese()
try:
if self.debug:
output = open(os.path.join(BASE_DIR, 'files', 'cpuinfo.out'), 'r').read()
else:
shell_command = "cat /proc/cpuinfo"
output = handler.cmd(shell_command, hostname)
response.data = self.parse(output)
except Exception as e:
msg = traceback.format_exc()
response.status = False
response.error = msg
logger.error(msg)
return response.dict @staticmethod
def parse(content):
"""
解析shell命令返回结果
:param content: shell 命令结果
:return:解析后的结果
"""
response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''} cpu_physical_set = set() content = content.strip()
for item in content.split('\n\n'):
for row_line in item.split('\n'):
key, value = row_line.split(':')
key = key.strip()
if key == 'processor':
response['cpu_count'] += 1
elif key == 'physical id':
cpu_physical_set.add(value)
elif key == 'model name':
if not response['cpu_model']:
response['cpu_model'] = value
response['cpu_physical_count'] = len(cpu_physical_set) return response

  basic命令插件代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import traceback
from .base import BasePlugin
from lib.response import BaseReponese
from lib.log import logger class Basic(BasePlugin):
def os_platform(self, handler, hostname):
"""
获取系统平台
:return:
"""
output = handler.cmd('uname', hostname)
return output.strip() def os_version(self, handler, hostname):
"""
获取系统版本
:return:
"""
output = handler.cmd('cat /etc/issue', hostname)
result = output.strip().split('\n')[0]
return result def os_hostname(self, handler, hostname):
"""
获取主机名
:return:
"""
output = handler.cmd('hostname', hostname)
return output.strip() def win(self, handler, hostname=None):
raise NotImplementedError('win must be implemented ') def linux(self, handler, hostname=None):
response = BaseReponese()
try:
if self.debug:
ret = {
'os_platform': 'linux',
'os_version': '6.5',
'hostname': 'c2.com'
}
else:
ret = {
'os_platform': self.os_platform(handler, hostname),
'os_version': self.os_version(handler, hostname),
'hostname': self.os_hostname(handler, hostname),
}
response.data = ret
except Exception as e:
msg = traceback.format_exc()
response.status = False
response.error = msg
logger.error(msg) return response.dict

  board命令插件代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import traceback
from .base import BasePlugin
from lib.BaseReponse import BaseReponese
from lib.log import logger
from conf.settings import BASE_DIR class MainBoard(BasePlugin):
def win(self, handler, hostname=None):
raise NotImplementedError('win must be implemented ') def linux(self, handler, hostname=None):
response = BaseReponese()
try:
if self.debug: output = open(os.path.join(BASE_DIR, 'files', 'board.out'), 'r').read()
else:
shell_command = "sudo dmidecode -t1"
output = handler.cmd(shell_command, hostname)
response.data = self.parse(output)
except Exception as e:
msg = traceback.format_exc()
response.status = False
response.error = msg
logger.error(msg)
return response.dict def parse(self, content): result = {}
key_map = {
'Manufacturer': 'manufacturer',
'Product Name': 'model',
'Serial Number': 'sn',
} for item in content.split('\n'):
row_data = item.strip().split(':')
if len(row_data) == 2:
if row_data[0] in key_map:
result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1] return result

3.完善资产上报

  1)需要考虑的问题 : 资产上报如何确定该这是对应的资源 ? 本次提交数据是新增还是修改呢 ?

    主机唯一标识使用主机名 ,主机名存在系统的一个文件A中 ,每次提交数据的时候客户端比对一下就能完美解决唯一标识的问题 (唯一标识仅agent模式需要 ,ssh的模式中都是从api获取主机列表)

    客户端提交数据的操作 ,我们可以在上报的字典中加一个状态

      情况1:如果没有文件A ,新增主机

      情况2:如果有文件A 且文件中记录主机名与提交主机名一致 ,唯一标识未被修改修改数据

      情况3:如果有文件A 且文件中记录主机名与提交主机名不一致 ,唯一标识被修改的修改数据

    服务端收到字典

      针对情况1:新增记录到数据库

      针对情况2:根据主机名找到资源更新该资源信息

      针对情况3:根据文件A中的主机名找到字段更新信息 ,将返回唯一标识返回给客户端 ,客户端改动文件A

  

  2)客户端情况进行代码实现

    考虑三种情况 + 保存唯一标识文件(文件位置由配置定义)

from src.engine.base import BaseHandler
from ..plugins import get_server_info
import requests
import json
import os
from conf import settings class AgentHandler(BaseHandler):
"""定义cmd窗口 ,操控资产采集 + 上报""" def cmd(self, command, hostname=None):
import subprocess
return subprocess.getoutput(command) def handler(self):
info = get_server_info(self) # 新增
if not os.path.exists(settings.CERT_PATH):
info['type'] = 'create' else:
with open(settings.CERT_PATH, 'r', encoding='utf-8') as f1:
cert_hostname = f1.read()
hostname = info['basic']['data']['hostname']
# 修改
if cert_hostname == hostname:
info['type'] = 'update'
# 主机名被修改
else:
info['type'] = 'host_change'
info['cert_hostname'] = cert_hostname r1 = requests.post(
url='http://127.0.0.1:8000/api/asset/',
data=json.dumps(info).encode('gbk'),
headers={
'content-type': 'application/json'
}
) response_data = r1.json()
with open(settings.CERT_PATH,'w',encoding='utf-8') as f1:
f1.write(response_data['hostname'])

  3)api服务端根据三种情况 ,使用django提供数据增删改查

    建立models表结构 ,数据迁移

      外键遵循多对一 :IDC对于Server来说是一 ,那么Server设置外键字段 ,Server对于内存网卡来说是一 ....

from django.db import models

# 业务线
class BusinessUnit(models.Model):
"""
业务名
""" # 机房信息
class IDC(models.Model):
"""
机房名 楼层
""" # 服务器信息
class Server(models.Model):
"""
服务器状态 IDC外键 机柜号 机柜序号 业务线外键 主机名 系统 系统版本 SN 厂商 型号 CPU个数 CPU物理个数 CPU型号 最后一次更新日期时间 创建时间
""" # 硬盘信息
class Disk(models.Model):
"""
硬盘所在槽位 硬盘型号 硬盘容量 硬盘类型 服务器外键
""" # 网卡信息
class NIC(models.Model):
"""
网卡名 网卡mac地址 网卡的掩码 网卡的ip 启用状态 服务器外键
""" # 内存信息
class Memory(models.Model):
"""
内存所在槽位 内存型号 内存容量 内存厂商 SN 内存读写速率 服务器外键
""" # 变更审计
class AssetRecord(models.Model):
"""
审计内容 服务器外键 创建时间
""" # 错误日志
class ErrorLog(models.Model):
"""
日志内容 日志标题 创建时间 服务器外键信息
"""

  4)API处理3种情况的方式

    create完全新增 ,update与host_change两种情况是重合的 ,host_change需要先变更一下主机名而已 ,修改部分的重复代码放入service.py

    service.py

from api import models

def process_basic(info, server_list):
server_info = {}
server_info.update(info['basic']['data'])
server_info.update(info['cpu']['data'])
server_info.update(info['board']['data'])
# 将新字典内容打散用于更新数据库数据
server_list.update(**server_info) def process_disk(info, server):
disk_info = info['disk']['data']
disk_query = models.Disk.objects.filter(server=server) disk_info_set = set(disk_info) # 针对字典的key,也就是slot
disk_query_set = {str(i.slot) for i in disk_query} # 针对对象的slot属性 add_slot_set = disk_info_set - disk_query_set # 差集(新增硬盘槽位)
del_slot_set = disk_query_set - disk_info_set # 差集(移除的硬盘槽位)
update_slot_set = disk_info_set & disk_query_set # 交集(槽位硬盘更新) # add_slot_set
disk_list = []
for slot in add_slot_set:
disk_info[slot]['server'] = server
disk_list.append(**disk_list[slot])
if disk_list:
models.Disk.objects.bulk_create(disk_list) # del_slot_set(拿出所有被删除的对象列表)
if del_slot_set:
models.Disk.objects.filter(server=server, slot__in=del_slot_set).delete() # update_slot_set
if update_slot_set:
for slot in update_slot_set:
models.Disk.objects.filter(server=server, slot=slot).update(**disk_info[slot]) def process_memory(info,server):
memory_info = info['memory']['data']
memory_query = models.Memory.objects.filter(server=server) memory_info_set = set(memory_info) # 针对字典的key,也就是slot
memory_query_set = {str(i.slot) for i in memory_query} # 针对对象的slot属性 add_slot_set = memory_info_set - memory_query_set # 差集(新增内存槽位)
del_slot_set = memory_query_set - memory_info_set # 差集(移除的内存槽位)
update_slot_set = memory_info_set & memory_query_set # 交集(槽位内存更新) # add_slot_set
memory_list = []
for slot in add_slot_set:
memory_info[slot]['server'] = server
memory_list.append(**memory_list[slot])
if memory_list:
models.Memory.objects.bulk_create(memory_list) # del_slot_set(拿出所有被删除的对象列表)
if del_slot_set:
models.Memory.objects.filter(server=server, slot__in=del_slot_set).delete() # update_slot_set
if update_slot_set:
for slot in update_slot_set:
models.Memory.objects.filter(server=server, slot=slot).update(**memory_info[slot]) def process_nic(info,server):
nic_info = info['nic']['data']
nic_query = models.NIC.objects.filter(server=server) nic_info_set = set(nic_info)
nic_query_set = {str(i.name) for i in nic_query} create_name_set = nic_info_set - nic_query_set
del_name_set = nic_query_set - nic_info_set
update_name_set = nic_info_set & nic_query_set if create_name_set:
nic_list = []
for name in create_name_set:
nic_info[name]['server'] = server
nic_info[name]['name'] = name
nic_list.append(models.NIC(**nic_info[name]))
models.NIC.objects.bulk_create(nic_list) if del_name_set:
models.NIC.objects.filter(server=server, name__in=del_name_set).delete() if update_name_set:
for name in update_name_set:
models.NIC.objects.filter(server=server, name=name).update(**nic_info[name])

    view.py

      数据打散时注意一下model的字段名要与info中的key一致

      在对硬盘网卡内存等一种多资源的操作 ,需要先判断新增多少 ,删除多少 ,变更多少三种情况再具体操作

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from api import models
from api import service class Asset(APIView):
def get(self, request):
server_info = ['master1', 'master2']
return Response(server_info) def post(self, request):
info = request.data
hostname = info['basic']['data']['hostname']
ret = {'status': True, 'hostname': hostname}
action_type = info.get('type')
print(action_type)
# 完全新增主机
if action_type == 'create':
# 1.新增主机记录 将info中有关Server表字段的字典数据拿出来放入server_info中
server_info = {}
server_info.update(info['basic']['data'])
server_info.update(info['cpu']['data'])
server_info.update(info['board']['data'])
# 创建Server记录 ,保存在数据库 ,并拿到该对象 ,其他外键与之关联
server = models.Server.objects.create(**server_info) # 2.新增该主机的硬盘信息
disk_list = []
for i in info['disk']['data'].values():
i['server'] = server
disk_list.append(models.Disk(**i))
models.Disk.objects.bulk_create(disk_list) # 3.新增该主机内存信息
memory_list = []
for i in info['memory']['data'].values():
i['server'] = server
memory_list.append(models.Memory(**i))
models.Memory.objects.bulk_create(memory_list) # 4.新增该主机的网卡信息
nic_list = []
for n, m in info['nic']['data'].items():
m['server'] = server
m['name'] = n
nic_list.append(models.NIC(**m))
models.NIC.objects.bulk_create(nic_list) # 唯一标识不变,更新数据
elif action_type == 'update': # 取出旧的信息
server_list = models.Server.objects.filter(hostname=hostname)
server = server_list.first() ##1. 修改主机信息
service.process_basic(info, server_list) ##2. 修改硬盘信息
service.process_disk(info, server) ##3. 内存信息变更
service.process_memory(info, server) ##4. 网卡信息变更
service.process_nic(info, server) # 唯一标识变更的情况下 ,更新数据
elif action_type == 'host_change':
print('host_change')
cert = info.get('cert_hostname')
server = models.Server.objects.filter(hostname=cert).first()
server.hostname = info['basic']['data']['hostname']
server.save()
server_list = models.Server.objects.filter(hostname=server.hostname) ##1 修改主机信息
service.process_basic(info,server_list) ##2 修改硬盘信息
service.process_disk(info,server) ##3 修改内存信息
service.process_memory(info,server) ##4 修改网卡信息
service.process_nic(info,server) return Response(ret)

4.api的验证

  客户端必须拥有key才能去调用服务端api ,两端存储同一个key值不在网络中传输

  原理:  客户端发送    MD5=md5(client的key+A时间)  + A时间

       服务端接收    MD5 + A时间

       服务端使用    md5(server的key+A时间) 是否等于 客户端传来的MD5     ,由此判断两边存储的key值是否相等

  合理使用: 客户端的post请求带上MD5与时间的参数 ,服务端所有api都要使用到这个校验 ,重写dispatch方法来完成

  客户端代码

    /lib/api_auth.py   所有的engine都要使用所以放入lib中

import hashlib
from conf import settings def MD5_key(ctime):
key = '{}|{}'.format(settings.AUTH_KEY, ctime)
md5 = hashlib.md5()
md5.update(key.encode('utf-8'))
return md5.hexdigest()

    /engine/agent.py  engine中使用的方式

      其中params可以让url上带参数 ,服务端通过request.GET.get('key')获取

        ctime = time.time()
r1 = requests.post(
url='http://127.0.0.1:8000/api/asset/',
data=json.dumps(info).encode('gbk'),
params={'key': MD5_key(ctime), 'ctime': ctime},
headers={
'content-type': 'application/json'
}
)

  服务端代码

    views.py

      1.定义一个类 ,重写dispatch方法进行校验 ,让所有api都继承这个类

      2.校验中考虑超时问题拒绝 ,重复伪造的url拒绝 ,md5值无法对应的拒绝

      3.如果失败直接返回json ,如果成功就走父类的dispatch ,执行api流程

def MD5_key(ctime):
key = '{}|{}'.format(settings.AUTH_KEY, ctime)
md5 = hashlib.md5()
md5.update(key.encode('utf-8'))
return md5.hexdigest() already_key = {} class AuthView(APIView):
def dispatch(self, request, *args, **kwargs):
ret = {'status': True, 'msg': 'ok'}
key = request.GET.get('key')
ctime = request.GET.get('ctime')
now = time.time() if now - float(ctime) > 3:
ret['status'] = False
ret['msg'] = '请求超时'
return JsonResponse(ret)
if key in already_key:
ret['status'] = False
ret['msg'] = 'url已使用'
return JsonResponse(ret)
if key != MD5_key(ctime):
ret['status'] = False
ret['msg'] = '验证失败'
already_key[key] = None
if ret['status']:
return super().dispatch(request, *args, **kwargs)
else:
return JsonResponse(ret)

5.数据传输加密

  提高安全性 ,客户端服务端传输的数据使用非对称加密

  1)生成秘钥对

    生成最多加密1024位 ,那就是128个字节 ,其中有11个是加密自己用 ,所以每次能加密最大长度为117

import rsa
import base64 ####生成一组秘钥对####
pub_key_obj, priv_key_obj = rsa.newkeys(1024) # 最大加密value的字节数是 1024/8 - 11(自己用) = 117(可用加密字节) # 将公私钥的str类型拿出
pub_key_str = pub_key_obj.save_pkcs1()
priv_key_str = priv_key_obj.save_pkcs1() # 再将str的类型用base64编码
pub_key_code = base64.standard_b64encode(pub_key_str)
priv_key_code = base64.standard_b64encode(priv_key_str) print(pub_key_code)
print(priv_key_code)

  2)客户端加密

    使用配置好的公钥 ,对数据进行分段加密再拼接在一起 ,发送到服务端

    数据编码加密格式化顺序 :  加密(编码(json(原始数据)))

    lib/encrypt.py  将加密函数单独放入公共功能文件夹

import base64
import rsa
from conf import settings def encrypt(bytes_value):
values_list = []
key_str = base64.standard_b64decode(settings.PUB_KEY)
pk = rsa.PublicKey.load_pkcs1(key_str)
length = len(bytes_value) for i in range(0, length, 117):
val = rsa.encrypt(bytes_value[i: i + 117], pk)
values_list.append(val)
val = b''.join(values_list) return val

    所有的engine中的handler方法数据上报都加密

r1 = requests.post(
...
data=encrypt(json.dumps(info).encode('utf-8')),
...
}
)

  3)服务端解密

    当post请求中数据过长 ,request的数据放在body中

    数据编码解密格式化顺序 : json(解码(解密(原始数据) ))

    utils.py/decrypt.py 将数据解码文件函数放入工具文件夹

import base64
import rsa
from django.conf import settings def decrypt(value):
values_list = []
key_str = base64.standard_b64decode(settings.PRI_KEY)
pk = rsa.PrivateKey.load_pkcs1(key_str)
length = len(value) for i in range(0, length, 128):
val = rsa.decrypt(value[i:i + 128], pk)
values_list.append(val)
val = b''.join(values_list)
return val

    视图函数中接收的info数据进行解密

    def post(self, request):
info = json.loads(decrypt(request.body).decode('utf-8'))

    

   

cmdb项目-2的更多相关文章

  1. Python Django CMDB项目实战之-3创建form表单,并在前端页面上展示

    基于之前的项目代码 Python Django CMDB项目实战之-1如何开启一个Django-并设置base页.index页.文章页面 Python Django CMDB项目实战之-2创建APP. ...

  2. Python Django CMDB项目实战之-2创建APP、建模(models.py)、数据库同步、高级URL、前端页面展示数据库中数据

    基于之前的项目代码来编写 Python Django CMDB项目实战之-1如何开启一个Django-并设置base页index页文章页面 现在我们修改一个文章列表是从数据库中获取数据, 下面我们就需 ...

  3. Python Django CMDB项目实战之-1如何开启一个Django-并设置base页、index页、文章页面

    1.环境 win10 python 2.7.14 django 1.8.2 需要用到的依赖包:MySQLdb(数据库的接口包).PIL/pillow(处理图片的包) 安装命令: pip install ...

  4. CMDB项目要点之技术点(面试题)

    1.单例模式 日志对象用单例模式 django admin中注册类是,用到单例模式 为什么要用单例模式 同一个对象操作 维护全局变量 + 对全局变量做一些操作 # __new__ import thr ...

  5. Python之路【第二十二篇】CMDB项目

    浅谈ITIL TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部门CCTA(Central ...

  6. CMDB项目开发

    CMDB介绍 CMDB --Configuration Management Database 配置管理数据库, CMDB存储与管理企业IT架构中设备的各种配置信息,它与所有服务支持和服务交付流程都紧 ...

  7. CMDB项目实战

    01-CMDB项目介绍 02-CMDB开发背景 03-CMDB开发目的 04-CMDB资产采集方式之agent 05-CMDB资产采集方式之ssh 06-CMDB资产采集方式之saltstack 07 ...

  8. cmdb项目-1

    1.什么是cmdb 配置管理数据库 ,存储基础设备的各种信息配置等 CMDB可以存储并自动发现整个IT网络上的各种信息,比如一个IT网络上有多少台服务器.多少存储.设备的品牌.资产编号.维护人员.所属 ...

  9. cmdb项目-3

    1. cmdb资产审计 2.stark组件使用 快速完成网站的一个组件,使用方式与django的admin系统类似 ,仅仅将model注册 ,就可以生成对model增删改查的页面 ,当然这里还包括了模 ...

  10. CMDB项目要点总结之中控机

    1.基于paramiko对远程主机执行命令操作 秘钥形式 private_key = paramiko.RSAKey.from_private_key_file('c:/Users/用户名/.ssh/ ...

随机推荐

  1. 基于windows server 2016和sqlserver 2016 AlwaysOn的群集配置

    文档:基于windows server 2016和sqlserver 2...链接:http://note.youdao.com/noteshare?id=4f07c1c3f7d0e32b7631d7 ...

  2. jQuery模仿ToDoList实现简单的待办事项列表

    功能:在文本框中输入待办事项按下回车后,事项会出现在未完成列表中:点击未完成事项前边的复选框后,该事项会出现在已完成列表中,反之亦然:点击删除按钮会删除该事项:双击事项可以修改事项的内容.待办事项的数 ...

  3. 渗透测试初学者的靶场实战 2--墨者学院SQL注入—报错盲注

    墨者SQL注入-MYSQL数据库实战环境 实践步骤 1. 决断注入点 输入单引号,提示错误信息: 输入and 1=1 返回页面正常: 输入 and 1=2 返回正常 输入-1,返回异常: 2. 带入s ...

  4. 关于iOS引导页图层的相关属性类

    关键字:CABasicAnimation 参考链接:https://blog.csdn.net/Dwarven/article/details/42492199 Layer.mask属性用法: htt ...

  5. JMeter多脚本间的启动延时

    JMeter做压测时,当需要多个jmx脚本依次执行时,需要用到“启动延时”,即间隔可设置的时间后启动运行下一个jmx脚本. 实现“启动延时”的方法有2个. 方法一.利用JMeter线程组中的" ...

  6. C++之typename和typedef关键字

    1. typename的作用 template <typename Distance> class KDTreeIndex : public NNIndex<Distance> ...

  7. React Native-ios环境的搭建

    要在mac系统上搭建RN环境: 一.安装Homebrew.它是一款Mac OS平台下的软件包管理工具. 详见Homebrew的安装这篇文章. 二.安装node和watchman,用Homebrew在命 ...

  8. 使用 ASP.NET Core MVC 创建 Web API(六)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 使 ...

  9. js 实现ReplaceAll 的方法

    JS  字符串有replace() 方法.但这个方法只会对匹配到的第一个字串替换. 如下例: <HTML> <HEAD> <TITLE> New Document ...

  10. C# 调用OpenCVSharp报错“尝试读取或写入受保护的内存。这通常指示其他内存已损坏”

    一.描述问题 当托管代码调用非托管代码的时候,经常会出现如下报错:“尝试读取或写入受保护的内存.这通常指示其他内存已损坏”. 二.原因分析 由于非托管代码的内存指针的回收是由非托管代码自身手动完成的, ...