2019-06-20 11:49:12    2019-06-24 14:49:10   

django ztree
### ztree文档 http://www.treejs.cn/v3/api.php ### django配置 #### 创建虚拟环境 ```bash virtualenv venv27 source venv27/bin/activate ``` #### 安装django ```bash pip install django binaryornot ``` #### 创建项目 ```bash django-admin startproject myproject ``` #### 创建app ```bash cd myproject python manage.py startapp blog ``` #### 配置项目settings.py ```bash vim myproject/settings.py ``` ```ini # -*- coding:utf-8 -*- BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ ... 'blog',#安装app ] MEDIA_URL = "/upload/" #配置上传目录 MEDIA_ROOT = os.path.join(BASE_DIR, 'upload') STATIC_URL = '/static/' #配置静态文件目录 STATIC_DIR = os.path.join(BASE_DIR, 'static') STATICFILES_DIRS = [STATIC_DIR, ] ``` #### 创建上传和静态文件目录 ```bash mkdir myproject/{upload,static} ``` #### 配置项目urls.py ```bash vim myproject/urls.py ``` ```python # -*- coding:utf-8 -*- from django.conf.urls import url,include from django.conf.urls.static import static from . import settings urlpatterns = [ url(r'^blog/', include('blog.urls', namespace='blog')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` #### 配置应用urls.py ```bash vim blog/urls.py ``` ```python # -*- coding:utf-8 -*- from django.conf.urls import url from blog import views,filetree_views urlpatterns = [ url(r'^demo/$', views.demo, name='demo'),#展示视图 url(r'^async_get_nodes/', filetree_views.async_get_nodes, name='async_get_nodes'), #异步加载节点 url(r'^rename_node/', filetree_views.rename_node, name='rename_node'), #重命名节点 url(r'^add_node/', filetree_views.add_node, name='add_node'), #添加节点 url(r'^remove_node/', filetree_views.remove_node, name='remove_node'), #移除节点 url(r'^move_node/', filetree_views.move_node, name='move_node'), #移动节点 url(r'^read_node/', filetree_views.read_node, name='read_node'), #读取节点 url(r'^save_node/', filetree_views.save_node, name='save_node'), #保存节点 url(r'^upload_file/', filetree_views.upload_file, name='upload_file'), #节点上传文件 url(r'^upload_folder/', filetree_views.upload_folder, name='upload_folder'), #节点上传文件夹 url(r'^download_node/', filetree_views.download_node, name='download_node'), #下载节点 ] ``` #### 配置项目views.py ```bash vim blog/views.py ``` ```python # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.shortcuts import render from django.conf import settings from datetime import datetime import random import base64 import os code_root_path=os.path.join(settings.BASE_DIR,'upload/pygame/code_folder_path') def demo(request): #获取加密的参数 code_folder_enc=request.get_signed_cookie("code_folder_enc",default='',salt='ynotes') if not code_folder_enc: #创建随机目录 datetime_str=datetime.now().strftime('%Y%m%d%H%M%S%f') random_str=random.random() code_folder="{0}__{1}".format(datetime_str,random_str) code_folder_enc=base64.b64encode(code_folder) code_folder_path=code_folder code_folder_abspath=os.path.join(code_root_path,code_folder_path) os.makedirs(code_folder_abspath) else: #解密参数值 code_folder=base64.b64decode(code_folder_enc) code_folder_path=code_folder response = render(request, 'blog/demo.html',{'code_folder_path': code_folder_path}) response.set_signed_cookie('code_folder_enc',code_folder_enc,salt='ynotes',max_age=864000) return response ``` #### 配置项目filetree_views.py ```bash vim blog/filetree_views.py ``` ```python # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.http import HttpResponse from django.shortcuts import render from django.conf import settings import os import json import shutil import base64 import zipfile import random from datetime import datetime from binaryornot.check import is_binary code_relative_path='upload/pygame/code_folder_path' code_root_path=os.path.join(settings.BASE_DIR,code_relative_path) #zip压缩目录 def zipdir(path, ziph): for root, dirs, files in os.walk(path): for file in files: ziph.write(os.path.join(root, file)) def async_get_nodes(request): """异步加载节点""" code_folder_path=request.POST.get('codeFolderPath','')#获取代码的临时目录 code_folder_abspath=os.path.join(code_root_path,code_folder_path)#拼接临时目录绝对路径 treenode_path=request.POST.get('treeNodePath','') #获取选中节点的相对路径 get_nodes_dir=os.path.join(code_folder_abspath,treenode_path) #拼接选中节点的绝对路径 nodes_list=[] #如果code_folder_path参数为空或者绝对路径不存在返回空列表json if not code_folder_path or not os.path.exists(code_folder_abspath): return HttpResponse(json.dumps([])) #循环拼接ztree的数据 for i in os.listdir(get_nodes_dir): node_dict={} get_file=os.path.join(get_nodes_dir,i) inode=os.stat(get_file).st_ino node_dict['id']=inode node_dict['name']=i #通过文件后缀设置不同的zTree图标 if os.path.isdir(get_file): node_dict['isParent']='true' else: node_dict['isParent']='false' nodes_list.append(node_dict) return HttpResponse(json.dumps(nodes_list,ensure_ascii=False)) def rename_node(request): """重命名节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') old_path_name=os.path.join(code_folder_abspath,treenode_path) new_name=request.POST.get('newName','') if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not new_name: return HttpResponse('{"result":"error","msg":"请输入文件名"}') new_path_name=os.path.join(os.path.dirname(os.path.join(code_folder_abspath,treenode_path)),new_name) try: if os.path.exists(new_path_name): return HttpResponse('{"result":"error","msg":"文件名已存在"}') os.rename(old_path_name,new_path_name) except: return HttpResponse('{"result":"error","msg":"重命名异常"}') return HttpResponse('{"result":"ok","msg":"重命名完成"}') def add_node(request): """添加节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') file_type=request.POST.get('fileType','file') new_name=request.POST.get('newName','') if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not new_name: return HttpResponse('{"result":"error","msg":"请输入文件名"}') new_path_name=os.path.join(code_folder_abspath,treenode_path,new_name) try: if os.path.exists(new_path_name): return HttpResponse('{"result":"error","msg":"文件名已存在"}') if file_type == 'file': os.mknod(new_path_name,0644) else: os.mkdir(new_path_name) except Exception as e: return HttpResponse('{"result":"error","msg":"新建节点异常"}') return HttpResponse('{"result":"ok","msg":"新建节点完成"}') def remove_node(request): """移除节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') name=request.POST.get('name','') if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not name: return HttpResponse('{"result":"error","msg":"节点名为空"}') path_name=os.path.join(code_folder_abspath,treenode_path,name) try: if not os.path.exists(path_name): return HttpResponse('{"result":"error","msg":"节点不存在"}') else: if os.path.isdir(path_name): shutil.rmtree(path_name) else: os.remove(path_name) except Exception as e: return HttpResponse('{"result":"error","msg":"删除节点异常"}') return HttpResponse('{"result":"ok","msg":"删除成功"}') def move_node(request): """移动节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) src_treenode_path=request.POST.get('srcTreeNodePath','') dst_treenode_path=request.POST.get('dstTreeNodePath','') src_path_name=os.path.join(code_folder_abspath,src_treenode_path) dst_path_name=os.path.join(code_folder_abspath,dst_treenode_path) if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') try: if not os.path.exists(src_path_name): return HttpResponse('{"result":"error","msg":"原节点不存在"}') if not os.path.exists(dst_path_name): return HttpResponse('{"result":"error","msg":"目的节点不存在"}') shutil.move(src_path_name,dst_path_name) except Exception as e: return HttpResponse('{"result":"error","msg":"移动节点异常"}') return HttpResponse('{"result":"ok","msg":"移动成功"}') def read_node(request): """读取节点""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') path_name=os.path.join(code_folder_abspath,treenode_path) if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not os.path.exists(path_name): return HttpResponse('{"result":"error","msg":"文件不存在"}') print(path_name+"是否是二进制文件",is_binary(path_name)) if not is_binary(path_name): with open(path_name) as file_object: content=file_object.read() data={"result":"ok","msg":"读取完成","data":base64.b64encode(content)} else: data={"result":"error","msg":"非文本文件不能读取"} return HttpResponse(json.dumps(data)) def save_node(request): """保存节点,对非二进制文件节点进行保存""" code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') encode_file_content=request.POST.get('encodedFileContent','') #获取base64编码的文本内容 path_name=os.path.join(code_folder_abspath,treenode_path) if not code_folder_abspath: return HttpResponse('{"result":"error","msg":"参数异常"}') if not os.path.exists(path_name): return HttpResponse('{"result":"error","msg":"文件不存在"}') if not is_binary(path_name): with open(path_name,'w') as file_object: if len(encode_file_content) > 0: file_object.write(base64.b64decode(encode_file_content)) else: file_object.write('') data={"result":"ok","msg":"保存完成"} else: data={"result":"error","msg":"非文本文件不能保存"} return HttpResponse(json.dumps(data)) def upload_file(request): """节点上传文件""" if request.method == 'POST': code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') upload_file=request.FILES.get('uploadFile','') #上传的文件 if code_folder_path and upload_file: upload_file_abspath=os.path.join(code_folder_abspath,treenode_path,upload_file.name) #拼接上传文件后的绝对路径 if os.path.exists(upload_file_abspath) and os.path.isdir(upload_file_abspath): dict_data={"result":"error","msg":"上传失败,存在同名的目录"} else: with open(upload_file_abspath,'w') as f: f.write(upload_file.read()) if os.path.exists(upload_file_abspath): inode=os.stat(upload_file_abspath).st_ino dict_data={"result":"ok","msg":"上传成功","id":inode} else: dict_data={"result":"error","msg":"上传文件异常"} return HttpResponse(json.dumps(dict_data)) return HttpResponse(json.dumps({"result":"error","msg":"请求参数错误"})) return HttpResponse(json.dumps({"result":"error","msg":"请求方法不支持"})) def upload_folder(request): """节点上传文件夹""" if request.method == 'POST': code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') upload_files=request.FILES.getlist('uploadFiles',[]) #获取上传文件列表 upload_file_paths=request.POST.getlist('uploadFilePaths',[]) #获取上传文件的相对路径列表 is_exist_upload_success=False dict_data={} #判断上传文件列表和上传文件的相对路径列表是否相等 if code_folder_path and upload_files and len(upload_files) == len(upload_file_paths): upload_files_dirpath=os.path.join(code_folder_abspath,treenode_path) upload_fail_files_dict={} for idx in range(0,len(upload_files)): #逐级判断是否存在同名的文件 dir_temp_path='' is_exist_same_file=False for d in os.path.dirname(upload_file_paths[idx]).split('/'): dir_temp_path=os.path.join(dir_temp_path,d) dir_temp_abspath=os.path.join(upload_files_dirpath,dir_temp_path) if os.path.exists(dir_temp_abspath) and not os.path.isdir(dir_temp_abspath): is_exist_same_file=True #需要创建的目录存在同名的文件的字典key是否存在 if dir_temp_path in upload_fail_files_dict: upload_fail_files_dict[dir_temp_path].append(upload_files[idx].name) else: upload_fail_files_dict[dir_temp_path]=[] upload_fail_files_dict[dir_temp_path].append(upload_files[idx].name) #如果存在同名的文件没必要往下判断 break #如果存在同名的文件则直接跳到下一个文件 if is_exist_same_file: continue #判断是否存在目录,不存在则创建 upload_file_dir_abspath=os.path.join(upload_files_dirpath,os.path.dirname(upload_file_paths[idx])) if not os.path.exists(upload_file_dir_abspath): os.makedirs(upload_file_dir_abspath) #把文件保存到相应的路径下 #判断上传的文件名是否存在同名的目录 upload_file_abspath=os.path.join(upload_file_dir_abspath,upload_files[idx].name) if os.path.exists(upload_file_abspath) and os.path.isdir(upload_file_abspath): if upload_file_abspath in upload_fail_files_dict: upload_fail_files_dict[upload_file_paths[idx]].append(upload_file_paths[idx]) else: upload_fail_files_dict[upload_file_paths[idx]]=[] upload_fail_files_dict[upload_file_paths[idx]].append(upload_file_paths[idx]) continue with open(upload_file_abspath,'w') as f: f.write(upload_files[idx].read()) is_exist_upload_success=True fail_msg="" if upload_fail_files_dict: for d in upload_fail_files_dict.keys(): for f in upload_fail_files_dict[d]: if f == d: fail_msg+="已存在同名的目录{0},导致文件{1}上传失败.\n".format(d,f) else: fail_msg+="已存在同名的文件{0},创建目录{0}失败,导致该目录下的文件{1}上传失败.\n".format(d,f) if fail_msg: if is_exist_upload_success: dict_data["msg"]="警告:\n"+fail_msg dict_data["result"]="error" else: dict_data["msg"]="警告:\n"+fail_msg dict_data["result"]="error" else: dict_data["msg"]="上传文件夹成功" dict_data["result"]="ok" ##获取父目录的id if is_exist_upload_success: inode=os.stat(os.path.join(upload_files_dirpath,upload_file_paths[0].split('/')[0])).st_ino dict_data["id"]=inode return HttpResponse(json.dumps(dict_data)) return HttpResponse(json.dumps({"result":"error","msg":"请求参数错误"})) return HttpResponse(json.dumps({"result":"error","msg":"请求方法不支持"})) def download_node(request): """下载节点""" if request.method == 'POST': code_folder_path=request.POST.get('codeFolderPath','') code_folder_abspath=os.path.join(code_root_path,code_folder_path) treenode_path=request.POST.get('treeNodePath','') if code_folder_path: download_file=os.path.join(code_folder_abspath,treenode_path) dict_data={} if os.path.exists(download_file): if os.path.isdir(download_file): #生成一个压缩包目录 datetime_str=datetime.now().strftime('%Y%m%d%H%M%S%f') random_str=random.random() zip_folder="{0}__{1}".format(datetime_str,random_str) zip_folder_abspath=os.path.join(code_root_path,'zip',zip_folder) os.makedirs(zip_folder_abspath) #生成压缩包 cwd=os.getcwd() try: zip_filename = "%s" % os.path.basename(download_file) if treenode_path else "All" workspace_dir=os.path.dirname(download_file) if treenode_path else code_root_path os.chdir(workspace_dir) zip_dirname=os.path.basename(download_file) if treenode_path else code_folder_path shutil.make_archive(os.path.join(zip_folder_abspath,zip_filename), 'zip',zip_dirname) os.chdir(cwd) except Exception as e: os.chdir(cwd) #返回zip的下载链接 url=os.path.join(code_relative_path,'zip',zip_folder,zip_filename+'.zip') filename=zip_filename dict_data={"result":"ok","msg":"文件压缩包下载链接获取成功","url":url,"filename":filename} else: url=os.path.join(code_relative_path,code_folder_path,treenode_path) filename=os.path.basename(treenode_path) dict_data={"result":"ok","msg":"文件下载链接获取成功","url":url,"filename":filename} return HttpResponse(json.dumps(dict_data)) return HttpResponse(json.dumps({"result":"error","msg":"文件不存在"})) return HttpResponse(json.dumps({"result":"error","msg":"请求参数错误"})) return HttpResponse(json.dumps({"result":"error","msg":"请求方法不支持"})) ``` #### 配置django模板 创建模板目录 ```bash mkdir blog/templates/blog -p ``` 创建基础模板 ```bash vim blog/templates/blog/base.html ``` ##### 配置base.html ```html <!DOCTYPE html> {% load staticfiles %} <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!--<meta name="viewport" content="width=device-width, initial-scale=1.0">--> <meta name='viewport' content='width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0' /> <meta name='apple-mobile-web-app-capable' content='yes' /> <title>Ztree demo</title> {% block css %} <link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> {% endblock %} {% block javascript %} <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script> {% endblock %} </head> <body> {% block body %} {% endblock %} {% block extra_javascript %} {% endblock %} </body> </html> ``` ```bash vim blog/templates/blog/demo.html ``` ```html {% extends 'blog/base.html' %} {% load staticfiles %} {% block css %} {{ block.super }} <link rel="stylesheet" href="{% static 'css/ztree/zTreeStyle.css' %}" type="text/css"> <style> div#rMenu {position:absolute; visibility:hidden; top:0; text-align: left;padding: 1px;} div#rMenu ul li{ margin: 0px 0; padding: 0 5px; cursor: pointer; list-style: none outside none; font-weight:bold; background-color: #90CAF9; font-family:"Microsoft YaHei"; } //设置ztree显示样式 .highlight_red {color:#A60000;} .highlight_green {color:#A7F43D;} //li {list-style: circle;font-size: 12px;} li.title {list-style: none;} ul.list {margin-left: 17px;} div.content_wrap {width: 600px;height:380px;} div.content_wrap div.left{float: left;width: 250px;} div.content_wrap div.right{float: right;width: 340px;} div.zTreeDemoBackground {width:250px;height:362px;text-align:left;} ul.ztree {margin-top: 0px;border: 1px solid #617775;background: #f0f6e4;width:220px;height:360px;overflow-y:scroll;overflow-x:auto;} ul.ztree {margin-top: 0px;border: 1px solid #617775;background: #f0f6e4;overflow-y:scroll;overflow-x:auto;} ul.log {border: 1px solid #617775;background: #f0f6e4;width:300px;height:170px;overflow: hidden;} ul.log.small {height:45px;} ul.log li {color: #666666;list-style: none;padding-left: 10px;} ul.log li.dark {background-color: #E3E3E3;} /* ruler */ div.ruler {height:20px; width:220px; background-color:#f0f6e4;border: 1px solid #333; margin-bottom: 5px; cursor: pointer} div.ruler div.cursor {height:20px; width:30px; background-color:#3C6E31; color:white; text-align: right; padding-right: 5px; cursor: pointer} .ztree li span.button.add {margin-left:2px; margin-right: -1px; background-position:-144px 0; vertical-align:top; *vertical-align:middle} [hidden] { display: none !important; } //配置图片div的显示区域 #yourimg_div {width:800px;height:600px;} </style> {% endblock %} {% block javascript %} {{ block.super }} <script type="text/javascript" src="{% static 'js/ztree/jquery.ztree.core.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/ztree/jquery.ztree.exedit.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/ztree/custom-ztree.js' %}"></script> {% endblock %} {% block body %} <div class="container-fluid"> <div class="row"> <div class="col-md-2"> <label class="btn btn-success btn-sm"> 上传文件<input type="file" id="uploadFile" value="uploadFile" hidden> </label> <label class="btn btn-success btn-sm"> 上传目录<input type="file" id="uploadFolder" value="uploadFolder" webkitdirectory directory multiple hidden> </label> <label class="btn btn-success btn-sm" id="downloadFile"> 下载 </label> </div> <div class="col-md-2"> <div id="notice"> </div> </div> </div> <div class="row"> <div class="col-md-2" id="treeDemo-parent-col"> <div id="filetree" class="content_wrap" style="width:100%;"> <div class="zTreeDemoBackground left" style="width:100%;"> <ul id="treeDemo" class="ztree" style="width:100%;"></ul> </div> </div> </div> <div class="col-md-5"> <div id="yourcode_div"> </div> <div id="yourimg_div" style="display:none;width:800px;height:600px;" > </div> <button class="btn btn-primary" id="saveCode" type="button">保存</button> </div> </div> </div> {% csrf_token %} <input id="codeFolderPath" value="{{code_folder_path}}" type="hidden"> <div id="rMenu"> <ul style="padding-left:0px;"> <li id="m_add_file" onclick="addTreeNode('file');">增加文件</li> <li id="m_add_folder" onclick="addTreeNode('folder');">增加目录</li> <li id="m_rename" onclick="renameTreeNode();">重命名</li> <li id="m_del" onclick="removeTreeNode();">删除</li> </ul> </div> {% endblock %} {% block extra_javascript %} <script type="text/javascript"> $(document).ready(function(){ //ztree $.fn.zTree.init($("#treeDemo"), setting); zTree = $.fn.zTree.getZTreeObj("treeDemo"); rMenu = $("#rMenu"); $("#uploadFile").bind("change",uploadFile); $("#uploadFile").bind("click",beforeUploadFile); $("#uploadFolder").bind("change",uploadFolder); $("#uploadFolder").bind("click",beforeUploadFolder); $("#downloadFile").bind("click",downloadFile); $("#saveCode").bind("click",saveNode); }); </script> {% endblock %} ``` #### 创建ztree目录 ```bash mkdir myproject/static/{js,css}/ztree/ -p ``` #### 下载zTree ztree.core(核心模块):https://gitee.com/zTree/zTree_v3/blob/master/js/jquery.ztree.core.min.js ztree.exedit(exedit扩展模块):https://gitee.com/zTree/zTree_v3/blob/master/js/jquery.ztree.exedit.min.js ztree css:https://gitee.com/zTree/zTree_v3/blob/master/css/zTreeStyle/zTreeStyle.css ztree 图片:https://gitee.com/zTree/zTree_v3/tree/master/css/zTreeStyle/img js文件放到 `myproject/static/js/ztree`目录 css文件放到 `myproject/static/css/ztree`目录 img文件夹放到 `myproject/static/css/ztree`目录 #### 创建自定义js文件 ```bash vim myproject/static/js/ztree/custom-ztree.js ``` ```javascript var setting = { async: { enable: true, url: '/blog/async_get_nodes/', otherParam: getOtherParam, }, view: { selectedMulti: false, showLine: false, expandSpeed: "slow" }, edit: { drag:{ isCopy: false, isMove: true, prev: true, next: true, inner: true }, enable: true, editNameSelectAll: true, showRemoveBtn: false, showRenameBtn: false, }, data: { keep: { leaf: true, parent: true }, simpleData: { enable: true } }, callback: { beforeExpand: beforeExpand, beforeDrag: beforeDrag, beforeDrop: beforeDrop, beforeClick: beforeClick, beforeRename: beforeRename, onRightClick: OnRightClick, onDblClick: OnDblClick, onClick: OnClick } }; function showNotice(type,msg) { if(type=="ok"){ $('#notice').html('<span class="glyphicon glyphicon-ok-sign"></span> '+msg); $('#notice').css('color', 'green'); }else if(type=="error"){ $('#notice').html('<span class="glyphicon glyphicon-remove-sign"></span> '+msg); $('#notice').css('color', 'red'); }else if(type=="hidden"){ $('#notice').html(''); } } function getOtherParam(treeId, treeNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); return {'codeFolderPath':codeFolderPath,'treeNodePath':treeNodePath,'csrfmiddlewaretoken':token} } function getTreeNodePath(treeNode){ if(treeNode == null){ return ''; } var ParentNode=treeNode.getParentNode(); if(ParentNode==null){ return treeNode.name; } return getTreeNodePath(ParentNode)+"/"+treeNode.name; } function beforeExpand(treeId, treeNode) { if (!treeNode.isAjaxing) { startTime = new Date(); treeNode.times = 1; ajaxGetNodes(treeNode, "refresh"); return true; } else { showNotice("ok","加载数据中,请稍后..."); return false; } } function ajaxGetNodes(treeNode, reloadType) { var zTree = $.fn.zTree.getZTreeObj("treeDemo"); showNotice("ok","数据加载中..."); if (reloadType == "refresh") { zTree.updateNode(treeNode); } zTree.reAsyncChildNodes(treeNode, reloadType, true,function(){ showNotice("ok","数据加载完成"); }); } function beforeDrag(treeId, treeNodes) { for (var i=0,l=treeNodes.length; i<l; i++) { if (treeNodes[i].drag === false) { return false; } } return true; } function beforeDrop(treeId, treeNodes, targetNode, moveType) { ajaxDragNode(treeNodes[0],targetNode); setTimeout(function(){ zTree.reAsyncChildNodes(targetNode, "refresh", false); },100); return targetNode ? targetNode.drop !== false : true; } function ajaxDragNode(treeNode,targetNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var srcTreeNodePath=getTreeNodePath(treeNode); var dstTreeNodePath=getTreeNodePath(targetNode); $.post('/blog/move_node/', { codeFolderPath: codeFolderPath, srcTreeNodePath: srcTreeNodePath, dstTreeNodePath: dstTreeNodePath, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); console.log(obj.msg); if(obj.result == "ok"){ showNotice("ok",'节点移动完成'); return true; } showNotice('error','节点移动失败'); return false; } }).fail(function(){ showNotice('error','节点移动异常,请联系管理员'); return false; }); } function OnDblClick(event, treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("treeDemo"); zTree.cancelSelectedNode(); showNotice("ok",'已选中根目录'); } function OnClick(event, treeId, treeNode) { var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; if(SelectedTreeNode.isParent){ showNotice("ok",'已选中目录'); } } function OnRightClick(event, treeId, treeNode) { if (!treeNode && event.target.tagName.toLowerCase() != "button" && $(event.target).parents("a").length == 0) { zTree.cancelSelectedNode(); showRMenu("root", event.clientX, event.clientY); } else if (treeNode && !treeNode.noR) { zTree.selectNode(treeNode); if(treeNode.isParent){ showRMenu("folder", event.clientX, event.clientY); }else{ showRMenu("file", event.clientX, event.clientY); } } } function showRMenu(type, x, y) { $("#rMenu ul").show(); if (type=="file") { $("#m_del").show(); $("#m_edit").show(); $("#m_rename").show(); $("#m_add_folder").hide(); $("#m_add_file").hide(); } else if (type=="folder") { $("#m_del").show(); $("#m_edit").hide(); $("#m_rename").show(); $("#m_add_file").show(); $("#m_add_folder").show(); }else{ $("#m_del").hide(); $("#m_edit").hide(); $("#m_rename").hide(); $("#m_add_file").show(); $("#m_add_folder").show(); } y += document.body.scrollTop; x += document.body.scrollLeft; rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"}); $("body").bind("mousedown", onBodyMouseDown); } function hideRMenu() { if (rMenu) rMenu.css({"visibility": "hidden"}); $("body").unbind("mousedown", onBodyMouseDown); } function onBodyMouseDown(event){ if (!(event.target.id == "rMenu" || $(event.target).parents("#rMenu").length>0)) { rMenu.css({"visibility" : "hidden"}); } } function addTreeNode(fileType) { hideRMenu(); var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; zTree.reAsyncChildNodes(SelectedTreeNode, "refresh", false,function(){ handlerAddTreeNode(fileType); }); } function handlerAddTreeNode(fileType){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; var isParent = false; var tempName = "NewFile"; if(fileType == "folder"){ isParent = true; tempName = "NewFolder"; } tempName=MakeUniqueNodeName(SelectedTreeNode,fileType); if (SelectedTreeNode) { NewTreeNode = zTree.addNodes(SelectedTreeNode, {name: tempName, isParent: isParent}); } else { NewTreeNode = zTree.addNodes(null, {name: tempName, isParent: isParent} ); } ajaxAddNode(SelectedTreeNode,fileType,NewTreeNode[0].name); zTree.editName(NewTreeNode[0]); } function filter(Node){ return Node.level == 0; } function filterChildNode(Node){ return true; } function nameExistNode(NodeList,Name){ for(i=0;i<NodeList.length;i++){ if(NodeList[i].name == Name){ return true; } } return false; } function MakeUniqueNodeName(TreeNode,fileType){ var tempName = "NewFile"; if(fileType == "folder"){ tempName = "NewFolder"; } var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(TreeNode){ var NodesList=zTree.getNodesByFilter(filterChildNode,false,TreeNode); }else{ var NodesList=zTree.getNodesByFilter(filter); } var counter=1; var NewTempName=tempName; while(nameExistNode(NodesList,NewTempName)){ NewTempName=tempName+counter; counter++; } return NewTempName; } function ajaxAddNode(treeNode,fileType,newName){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); $.post('/blog/add_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, newName: newName ,fileType: fileType, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); console.log(obj.msg); if(obj.result == "ok"){ showNotice("ok","新建节点完成"); return true; } showNotice("error","新建节点请求失败"); return false; } }).fail(function(){ showNotice("error","新建节点请求异常,请联系管理员"); return false; }); } function removeTreeNode() { hideRMenu(); var nodes = zTree.getSelectedNodes(); if (nodes && nodes.length>0) { if (nodes[0].children && nodes[0].children.length > 0) { var msg = "要删除的节点是父节点,如果删除将连同子节点一起删掉。\n\n请确认!"; if (confirm(msg)==true){ ajaxRemoveNode(nodes[0].getParentNode(),nodes[0].name); zTree.reAsyncChildNodes(nodes[0],"refresh",false); } } else { ajaxRemoveNode(nodes[0].getParentNode(),nodes[0].name); zTree.reAsyncChildNodes(nodes[0],"refresh",false); } } } function ajaxRemoveNode(treeNode,name){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); $.post('/blog/remove_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, name: name , csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); zTree.reAsyncChildNodes(treeNode,"refresh",false); console.log(obj.msg); if(obj.result == "ok"){ showNotice("ok","删除节点完成"); return true; } showNotice("error","删除节点失败"); return false; } }).fail(function(){ showNotice("error","删除异常异常,请联系管理员"); return false; }); } function beforeRename(treeId, treeNode, newName) { if (newName.length == 0) { showNotice("error","节点名称不能为空"); var zTree = $.fn.zTree.getZTreeObj("treeDemo"); setTimeout(function(){zTree.editName(treeNode)}, 10); return false; } ajaxRenameNode(treeNode,newName); return true; } function ajaxRenameNode(treeNode,newName){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); if(treeNode.name == newName ){ return true; } $.post('/blog/rename_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, newName: newName , csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); zTree.reAsyncChildNodes(treeNode.getParentNode(),"refresh",false); if(obj.result == "ok"){ showNotice("ok",'重命名节点完成'); return true; } showNotice('error','重命名节点失败'); return false; } }).fail(function(){ showNotice('error','重命名节点异常,请联系管理员'); return false; }); } function renameTreeNode() { hideRMenu(); var zTree = $.fn.zTree.getZTreeObj("treeDemo"), nodes = zTree.getSelectedNodes(), treeNode = nodes[0]; if (nodes.length == 0) { showNotice("error","请先选择一个节点"); return; } zTree.editName(treeNode); }; function utf8_to_b64(str) { return window.btoa(unescape(encodeURIComponent( str ))); } function b64_to_utf8(str) { return decodeURIComponent(escape(window.atob( str ))); } function isImage(filename) { var flag = false; var arr = ["jpg","png","gif"]; var index = filename.lastIndexOf("."); var ext = filename.substr(index+1); for(var i=0;i<arr.length;i++) { if(ext == arr[i]) { flag = true; break; } } return flag; } function beforeClick(treeId, treeNode,clickFlag) { if(!treeNode.isParent){ if(isImage(treeNode.name)){ var codeFolderPath=$("#codeFolderPath").val(); var treeNodePath=getTreeNodePath(treeNode); var codeRootPath='upload/pygame/code_folder_path'; var image_url='/'+codeRootPath+'/'+codeFolderPath+'/'+treeNodePath; var elem = document.createElement("img"); elem.setAttribute("src", image_url); elem.setAttribute("style", "margin:auto;"); elem.setAttribute("alt", "Flower"); document.getElementById("yourcode_div").style.display="none"; document.getElementById("yourimg_div").innerHTML = ""; document.getElementById("yourimg_div").style.background="url("+image_url+") center no-repeat"; document.getElementById("yourimg_div").style.display=""; showNotice("ok","图片加载完成"); }else{ document.getElementById("yourcode_div").style.display=""; document.getElementById("yourimg_div").style.display="none"; ajaxReadNode(treeNode); } } return true; } function ajaxReadNode(treeNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); $.post('/blog/read_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(obj.result == "ok"){ var base64_enc_content=obj.data; if(base64_enc_content == ""){ $('#yourcode_div').text(""); }else{ $('#yourcode_div').text(b64_to_utf8(base64_enc_content)); } showNotice("ok","读取节点完成"); return true; } $('#yourcode_div').text(""); showNotice("error","读取节点失败,二进制格式无法读取"); return false; } }).fail(function(){ showNotice("error","读取节点异常,请联系管理员"); return false; }); } function saveNode(){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var SelectedTreeNode = nodes[0]; if(nodes[0].isParent){ return; } ajaxsaveNode(SelectedTreeNode); } function ajaxsaveNode(treeNode){ var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var treeNodePath=getTreeNodePath(treeNode); var yourcode=$('#yourcode_div').text(); var encodedFileContent=utf8_to_b64(yourcode); $.post('/blog/save_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath,encodedFileContent:encodedFileContent, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(obj.result == "ok"){ showNotice("ok","节点保存完成"); return true; } showNotice("error","节点保存失败"); return false; } }).fail(function(){ showNotice("error","节点保存异常,请联系管理员"); return false; }); } function uploadFile(){ console.log('uploadFile'); var FileMaxSize=10*1024*1024; var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); zTree.reAsyncChildNodes(nodes[0], "refresh", false); if(nodes.length != 0 && !nodes[0].isParent){ showNotice("error","请选择目录"); return; } var treeNodePath=getTreeNodePath(nodes[0]); var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var uploadFile=$('#uploadFile')[0].files[0]; if ( uploadFile.size > FileMaxSize){ showNotice('error','上传文件最大限制为'+(FileMaxSize/1024/1024).toFixed(2)+'M,'+uploadFile.name+':'+(uploadFile.size/1024/1024).toFixed(2)+'M'); return false; } var formData=new FormData(); formData.append("codeFolderPath",codeFolderPath); formData.append("treeNodePath",treeNodePath); formData.append("csrfmiddlewaretoken",token); formData.append("uploadFile",uploadFile); $.ajax({ url: '/blog/upload_file/', data: formData, type: 'POST', contentType: false, processData: false, success(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.result == "ok"){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); zTree.reAsyncChildNodes(nodes[0], "refresh", false,function(){ showNotice("ok",'文件上传完成'); var upload_node = zTree.getNodeByParam("id",obj.id,nodes[0]); zTree.selectNode(upload_node); ajaxReadNode(upload_node); }); }else{ showNotice('error',obj.msg); } document.getElementById("uploadFile").value = ""; }, error(err){ showNotice('error','文件上传失败'); console.log(err); } }); } function downloadFile(){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); var treeNodePath=getTreeNodePath(nodes[0]); var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); $.post('/blog/download_node/', { codeFolderPath: codeFolderPath, treeNodePath: treeNodePath, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('result')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); if(obj.result == "ok"){ showNotice("ok","下载链接获取成功"); var a = document.createElement('a'); var url = obj.url; a.href = window.location.protocol+'//'+window.location.host+'/'+url; a.download = obj.filename; a.click(); return true; } showNotice("error","下载节点失败"); return false; } }).fail(function(){ showNotice("error","下载节点异常,请联系管理员"); return false; }); } function uploadFolder(){ var FolderMaxNumber=1000; var FolderMaxSize=50*1024*1024; var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); zTree.reAsyncChildNodes(nodes[0], "refresh", false); if(nodes.length != 0 && !nodes[0].isParent){ showNotice("error","请选择目录"); return; } var treeNodePath=getTreeNodePath(nodes[0]); var codeFolderPath=$("#codeFolderPath").val(); var token=$('input[name=csrfmiddlewaretoken]').val(); var uploadFiles=$('#uploadFolder')[0].files; if(uploadFiles.length>FolderMaxNumber){ showNotice('error','上传文件夹包含的文件数限制为'+FolderMaxNumber+',当前文件数:'+uploadFiles.length); return; } var uploadFilesTotalSize=0; for(var i=0;i<uploadFiles.length;i++){ uploadFilesTotalSize+=uploadFiles[i].size } if (uploadFilesTotalSize>FolderMaxSize){ showNotice('error','上传文件夹包含的文件总大小为'+(FolderMaxSize/1024/1024).toFixed(2)+'M,当前文件总大小:'+(uploadFilesTotalSize/1024/1024).toFixed(2)+'M'); return; } var formData=new FormData(); for(var i=0;i<uploadFiles.length;i++){ formData.append("uploadFiles",uploadFiles[i]); formData.append("uploadFilePaths",uploadFiles[i]['webkitRelativePath']); } formData.append("codeFolderPath",codeFolderPath); formData.append("treeNodePath",treeNodePath); formData.append("csrfmiddlewaretoken",token); $.ajax({ url: '/blog/upload_folder/', data: formData, type: 'POST', contentType: false, processData: false, success(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('id')){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var upload_node = zTree.getNodeByParam("id",obj.id,nodes[0]); if(obj.result == "ok"){ setTimeout(function(){ zTree.reAsyncChildNodes(upload_node, "refresh", false,function(){ zTree.selectNode(upload_node); showNotice("ok",obj.msg); }); },100); }else{ setTimeout(function(){ zTree.reAsyncChildNodes(upload_node, "refresh", false,function(){ zTree.selectNode(upload_node); showNotice('error',"警告,部分文件上传失败!"); setTimeout(function(){ alert(obj.msg); },300); }); },100); } }else{ showNotice('error',"警告,所有文件上传失败!"); setTimeout(function(){ alert(obj.msg); },300); } document.getElementById("uploadFolder").value = ""; }, error(err){ showNotice('error','警告,文件夹上传失败'); document.getElementById("uploadFolder").value = ""; } }); } function beforeUploadIsReady(){ var zTree = $.fn.zTree.getZTreeObj("treeDemo"); var nodes = zTree.getSelectedNodes(); if(nodes.length != 0 && !nodes[0].isParent){ showNotice("error","请选择目录上传,选择根目录请双击空白处"); return false; } return true; } function beforeUploadFile(){ if(!beforeUploadIsReady()){ event.preventDefault(); } } function beforeUploadFolder(){ if(!beforeUploadIsReady()){ event.preventDefault(); } } ``` ### demo ![avatar](https://image.ynotes.cn/ztree_demo.png) ### 实例 https://ynotes.cn/blog/pygame_v2/
阅读 69 评论 0 收藏 0
阅读 69
评论 0
收藏 0

   2019-06-18 18:04:20    2019-06-21 11:44:40   

vpn openvpn
### OpenVPN服务器 #### 开启epel-release ```bash yum install epel-release -y ``` #### 安装openvpn、easy-rsa和iptables-services ```bash yum install openvpn easy-rsa iptables-services -y ``` #### 配置easy-rsa ```bash cd /etc/openvpn/ cp -r /usr/share/easy-rsa /etc/openvpn/ ``` ```bash cd /etc/openvpn/easy-rsa/3/ vim vars ``` ``` set_var EASYRSA "$PWD" set_var EASYRSA_PKI "$EASYRSA/pki" set_var EASYRSA_DN "cn_only" set_var EASYRSA_REQ_COUNTRY "ID" set_var EASYRSA_REQ_PROVINCE "GuangDong" set_var EASYRSA_REQ_CITY "guangzhou" set_var EASYRSA_REQ_ORG "test CERTIFICATE AUTHORITY" set_var EASYRSA_REQ_EMAIL "sheyinsong@untes.co" set_var EASYRSA_REQ_OU "SHEYINSONG EASY CA" set_var EASYRSA_KEY_SIZE 2048 set_var EASYRSA_ALGO rsa set_var EASYRSA_CA_EXPIRE 7500 set_var EASYRSA_CERT_EXPIRE 3650 set_var EASYRSA_NS_SUPPORT "no" set_var EASYRSA_NS_COMMENT "SHEYINSONG CERTIFICATE AUTHORITY" set_var EASYRSA_EXT_DIR "$EASYRSA/x509-types" set_var EASYRSA_SSL_CONF "$EASYRSA/openssl-1.0.cnf" set_var EASYRSA_DIGEST "sha256" ``` ```bash chmod +x vars ``` #### 创建CA ```bash cd /etc/openvpn/easy-rsa/3/ ./easyrsa init-pki ./easyrsa build-ca #提示输入CA私钥的密码 ``` #### 创建OpenVPN服务器的证书和私钥 ```bash ./easyrsa gen-req openvpn-server nopass ``` #### CA签名OpenVPN服务器证书 ```bash ./easyrsa sign-req server openvpn-server ``` #### 验证签名的证书 ```bash openssl verify -CAfile pki/ca.crt pki/issued/openvpn-server.crt ``` #### 创建OpenVPN客户端的证书和私钥 ```bash cd /etc/openvpn/easy-rsa/3/ ./easyrsa gen-req client01 nopass ``` #### CA签名OpenVPN客户端证书 ```bash ./easyrsa sign-req client client01 ``` #### 验证签名的证书 ```bash openssl verify -CAfile pki/ca.crt pki/issued/client01.crt ``` #### 创建DH Key ```bash ./easyrsa gen-dh ``` #### 创建CRL key ```bash ./easyrsa gen-crl ``` #### 销毁拨号客户端(`注意:销毁客户端私钥,需要销毁才执行`) ```bash ./easyrsa revoke client01 ``` #### 拷贝证书和私钥到openvpn的目录 ```bash cp pki/ca.crt /etc/openvpn/server/ cp pki/issued/openvpn-server.crt /etc/openvpn/server/ cp pki/private/openvpn-server.key /etc/openvpn/server/ ``` ```bash cp pki/ca.crt /etc/openvpn/client/ cp pki/issued/client01.crt /etc/openvpn/client/ cp pki/private/client01.key /etc/openvpn/client/ ``` ```bash cp pki/dh.pem /etc/openvpn/server/ cp pki/crl.pem /etc/openvpn/server/ ``` #### 配置OpenVPN服务器 ```bash vim /etc/openvpn/server.conf ``` ``` # OpenVPN Port, Protocol and the Tun port 1194 proto udp dev tun # OpenVPN Server Certificate - CA, server key and certificate ca /etc/openvpn/server/ca.crt cert /etc/openvpn/server/openvpn-server.crt key /etc/openvpn/server/openvpn-server.key #DH and CRL key dh /etc/openvpn/server/dh.pem crl-verify /etc/openvpn/server/crl.pem # Network Configuration - Internal network # Redirect all Connection through OpenVPN Server server 10.10.1.0 255.255.255.0 push "redirect-gateway def1" # Using the DNS from https://dns.watch push "dhcp-option DNS 114.114.114.114" push "dhcp-option DNS 8.8.8.8" #Enable multiple client to connect with same Certificate key duplicate-cn # TLS Security cipher AES-256-CBC tls-version-min 1.2 tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 auth SHA512 auth-nocache # Other Configuration keepalive 20 60 persist-key persist-tun comp-lzo yes daemon # OpenVPN Log log-append /var/log/openvpn.log verb 3 ``` #### 开启路由转发功能 ```bash echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf sysctl -p ``` #### iptables防火墙配置 ```bash iptables -A INPUT -p udp --dport=1194 -j ACCEPT #开放openvpn端口 internet_dev=$(ip route get 84.200.69.80 | awk 'NR==1 {print $(NF-2)}') #获取访问外网的网卡名 iptables -t nat -A POSTROUTING -s 10.10.1.0/24 -o $internet_dev -j MASQUERADE #enp0s3为局域网通信的网卡接口,这里会把拨号成功的客户端对访问公网的流量进行IP伪装,修改成enp0s3的接口IP,然后经由内网网关传到路由器,再次通过路由器的NAT转换成路由器的公网ip. ``` iptables允许拨号网段的流量转发(`针对FORWARD默认拒绝的情况`) ```bash iptables -A FORWARD -s 10.10.1.0/24 -j ACCEPT iptables -A FORWARD -d 10.10.1.0/24 -j ACCEPT ``` #### firewalled防火墙配置 ```bash firewall-cmd --permanent --add-service=openvpn #开放openvpn服务端口 firewall-cmd --permanent --zone=trusted --add-interface=tun0 #把tun0加入trusted firewall-cmd --permanent --zone=trusted --add-masquerade #trusted开启masquerade internet_dev=$(ip route get 84.200.69.80 | awk 'NR==1 {print $(NF-2)}') firewall-cmd --permanent --direct --passthrough ipv4 -t nat -A POSTROUTING -s 10.10.1.0/24 -o $internet_dev -j MASQUERADE firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -s 10.10.1.0/24 -j ACCEPT firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -d 10.10.1.0/24 -j ACCEPT firewall-cmd --reload ``` #### 启动OpenVPN服务器 ```bash systemctl start openvpn@server systemctl enable openvpn@server ``` #### 创建openVPN客户端的配置 ```bash vim /etc/openvpn/client/client01.ovpn ``` ``` client dev tun proto udp remote xx.xx.xx.xx 1194 #xx.xx.xx.xx配置为OpenVPN服务器所在网络的路由器公网IP,该地址为路由器的公网IP地址.端口可以自行修改,同路由器映射端口匹配即可 ca ca.crt cert client01.crt key client01.key cipher AES-256-CBC auth SHA512 auth-nocache tls-version-min 1.2 tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 resolv-retry infinite compress lzo nobind persist-key persist-tun mute-replay-warnings verb 3 ``` #### 打包OpenVPN客户端配置 ```bash cd /etc/openvpn/ tar -czvf client01.tar.gz client/* scp root@xx.xx.xx.xx:/etc/openvpn/client01.tar.gz . ``` &nbsp; ### 配置路由器 ```bash 路由器添加端口映射:公网ip地址:1194---->OpenVPN的内网IP地址:1194 ``` &nbsp; ### OpenVPN客户端 #### 开启epel-release ```bash yum install epel-release -y ``` #### 安装OpenVPN ```bash yum install openvpn -y ``` #### 解压OpenVPN的配置 ```bash cd /etc/openvpn/ tar xvf client01.tar.gz ``` #### 拨号OpenVPN服务器 ```bash openvpn --config /etc/openvpn/client/client01.ovpn ``` ```bash Wed Jun 19 09:11:16 2019 OpenVPN 2.4.7 x86_64-redhat-linux-gnu [Fedora EPEL patched] [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Feb 20 2019 Wed Jun 19 09:11:16 2019 library versions: OpenSSL 1.0.2k-fips 26 Jan 2017, LZO 2.06 Wed Jun 19 09:11:16 2019 WARNING: No server certificate verification method has been enabled. See http://openvpn.net/howto.html#mitm for more info. Wed Jun 19 09:11:16 2019 TCP/UDP: Preserving recently used remote address: [AF_INET]xx.xx.xx.xx:1194 Wed Jun 19 09:11:16 2019 Socket Buffers: R=[212992->212992] S=[212992->212992] Wed Jun 19 09:11:16 2019 UDP link local: (not bound) Wed Jun 19 09:11:16 2019 UDP link remote: [AF_INET]xx.xx.xx.xx:1194 Wed Jun 19 09:11:16 2019 TLS: Initial packet from [AF_INET]xx.xx.xx.xx:1194, sid=a82ea46b b556e79b Wed Jun 19 09:11:16 2019 VERIFY OK: depth=1, CN=Easy-RSA CA Wed Jun 19 09:11:16 2019 VERIFY OK: depth=0, CN=openvpn-server Wed Jun 19 09:11:16 2019 Control Channel: TLSv1.2, cipher TLSv1/SSLv3 DHE-RSA-AES256-GCM-SHA384, 2048 bit RSA Wed Jun 19 09:11:16 2019 [openvpn-server] Peer Connection Initiated with [AF_INET]xx.xx.xx.xx:1194 Wed Jun 19 09:11:17 2019 SENT CONTROL [openvpn-server]: 'PUSH_REQUEST' (status=1) Wed Jun 19 09:11:17 2019 PUSH: Received control message: 'PUSH_REPLY,redirect-gateway def1,dhcp-option DNS 114.114.114.114,dhcp-option DNS 8.8.8.8,route 10.10.1.1,topology net30,ping 20,ping-restart 60,ifconfig 10.10.1.6 10.10.1.5,peer-id 0,cipher AES-256-GCM' Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: timers and/or timeouts modified Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: --ifconfig/up options modified Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: route options modified Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: peer-id set Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: adjusting link_mtu to 1625 Wed Jun 19 09:11:17 2019 OPTIONS IMPORT: data channel crypto options modified Wed Jun 19 09:11:17 2019 Data Channel: using negotiated cipher 'AES-256-GCM' Wed Jun 19 09:11:17 2019 Outgoing Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key Wed Jun 19 09:11:17 2019 Incoming Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key Wed Jun 19 09:11:17 2019 ROUTE_GATEWAY 172.19.239.253/255.255.240.0 IFACE=eth0 HWADDR=00:16:3e:02:88:cd Wed Jun 19 09:11:17 2019 TUN/TAP device tun0 opened Wed Jun 19 09:11:17 2019 TUN/TAP TX queue length set to 100 Wed Jun 19 09:11:17 2019 /sbin/ip link set dev tun0 up mtu 1500 Wed Jun 19 09:11:17 2019 /sbin/ip addr add dev tun0 local 10.10.1.6 peer 10.10.1.5 Wed Jun 19 09:11:17 2019 /sbin/ip route add xx.xx.xx.xx/32 via 172.19.239.253 Wed Jun 19 09:11:17 2019 /sbin/ip route add 0.0.0.0/1 via 10.10.1.5 Wed Jun 19 09:11:17 2019 /sbin/ip route add 128.0.0.0/1 via 10.10.1.5 Wed Jun 19 09:11:17 2019 /sbin/ip route add 10.10.1.1/32 via 10.10.1.5 Wed Jun 19 09:11:17 2019 Initialization Sequence Completed ``` #### 验证openVPN客户端拨号是否成功(`成功则显示为OpenVPN的所在网络的公网ip`) ```bash curl ifconfig.io ``` 参考: https://www.howtoforge.com/tutorial/how-to-install-openvpn-server-and-client-with-easy-rsa-3-on-centos-7/ https://www.howtoforge.com/tutorial/how-to-install-openvpn-on-centos-7/
阅读 53 评论 0 收藏 0
阅读 53
评论 0
收藏 0

   2019-06-13 13:40:49    2019-06-20 11:00:42   

docker channels xterm.js websocket
### 流程 #### `浏览器-->nginx-->静态页面-->ws请求-->nginx-->daphne(django-channels)` &nbsp; ### 环境 `centos 7` `nginx 1.12` `python 3.6` `daphne 2.3.0` `django 2.2.2` `docker 4.0.1` `channels 2.2.0` `channels-redis 2.4.0` &nbsp; ### 一、django-channels的websocket实现 #### 创建虚拟环境 ```bash pip install virtualenv cd /data/app/ virtualenv venv source venv/bin/activate ``` #### 安装相关软件包 ```bash pip install -u django channels asgi_redis channels_redis ``` #### 创建项目 ```bash django-admin startproject xtermWS ``` #### 创建app ```bash cd xtermWS python manage.py startapp dockerCmd ``` #### 项目结构如下 ```bash xtermWS ├── dockerCmd │   ├── admin.py │   ├── apps.py │   ├── consumers.py #类似view,需要手动创建 │   ├── __init__.py │   ├── models.py │   ├── routing.py #类似urls,需要手动创建 │   └── views.py ├── manage.py └── xtermWS ├── asgi.py #类似wsgi,需要手动创建 ├── __init__.py ├── routing.py #类似urls,需要手动创建 ├── settings.py ├── urls.py └── wsgi.py ``` #### 配置项目settings.py ```bash vim xtermWS/settings.py ``` ```bash ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ 'channels', # channels 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'dockerCmd.apps.DockercmdConfig', #新创建的dockerCmd ] ... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], #项目模板路径 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] #asgi配置 ASGI_APPLICATION = 'xtermWS.routing.application' #日志配置 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'normal': { 'format': '%(levelname)s | %(asctime)s | app: %(module)s pid: %(process)d th: %(thread)d | %(message)s', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'normal', } }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'INFO', 'propagate': True, }, }, } #channel_redis配置 CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { 'hosts': [('127.0.0.1', 6379)], }, }, } ``` #### 创建xtermWS/routing.py ```bash vim xtermWS/routing.py ``` ```python from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import dockerCmd.routing application = ProtocolTypeRouter({ 'websocket': AuthMiddlewareStack( URLRouter( dockerCmd.routing.websocket_urlpatterns ) ), }) ``` #### 创建dockerCmd/routing.py ```bash vim dockerCmd/routing.py ``` ```python from django.urls import re_path from dockerCmd import consumers websocket_urlpatterns = [ re_path(r'^ws/(?P<cid>[^/]+)$', consumers.CommandConsumer), ] ``` #### 创建dockerCmd/consumers.py ```bash vim dockerCmd/consumers.py ``` ```python from channels.generic.websocket import WebsocketConsumer import docker import threading import logging logger = logging.getLogger('django') class CommandConsumer(WebsocketConsumer): def connect(self): self.container_id = self.scope['url_route']['kwargs']['cid'] self.accept() self.client=docker.APIClient() #推送logs self.send(text_data=self.client.logs(self.container_id,stdout=True, stderr=True).decode('utf-8')) #self.send(text_data=self.client.attach(container_id,stderr=True,stdout=True,demux=True)) self.socket=self.client.attach_socket(self.container_id, params={'stdin': 1, 'stream': 1}) #开启线程获取stdout,stdin,logs数据stream数据 self.stop_thread=False self.t = threading.Thread(target=self.send_stream_log) self.t.start() def disconnect(self, close_code): #关闭线程 #关闭socket self.stop_thread=True self.socket._sock.send('stop\r\n'.encode('utf-8')) #socket关闭 self.socket.close() #容器关闭删除 self.client.stop(self.container_id) self.client.wait(self.container_id) self.client.remove_container(self.container_id) #client关闭 self.client.close() def receive(self, text_data): self.socket._sock.send(text_data.encode('utf-8')) logger.info('CommandConsumer:receive') def send_stream_log(self): for b in self.client.attach(self.container_id,stderr=True,stdout=True,stream=True,demux=True): logger.info(b) if self.stop_thread: break if b[0]: self.send(text_data=b[0].decode('utf-8')) if b[1]: self.send(text_data=b[1].decode('utf-8')) logger.info('退出线程') ``` #### 创建asgi.py ```bash vim xtermWS/asgi.py ``` ```python import os import django from channels.routing import get_default_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xtermWS.settings") django.setup() application = get_default_application() ``` &nbsp; #### 二、docker安装redis ```bash docker run -d -p 6379:6379 --name redis redis ``` &nbsp; ### 三、supervisor管理daphne #### 安装supervisor ```bash yum install -y supervisor ``` #### 创建配置文件 ```bash vim /etc/supervisord.d/daphne.ini ``` ```bash [program:daphne] directory=/data/app/xtermWS/ environment=PATH="/data/app/venv/bin" command=/data/app/venv/bin/daphne xtermWS.asgi:application -b 0.0.0.0 -p 9000 autostart=true autorestart=true stdout_logfile=/var/daphne/daphne_ws.log redirect_stderr=true ``` #### 创建日志目录 ```bash mkdir /var/daphne ``` #### 启动supervisord ```bash supervisord -c /etc/supervisord.conf ``` #### 启动/停止daphne ```bash supervisorctl start daphne supervisorctl stop daphne ``` &nbsp; ### 四、nginx安装配置 #### 安装nginx ```bash yum install nginx -y ``` #### 配置nginx ```bash vim /etc/nginx/nginx.conf ``` ```ini ... upstream channels-backend-prod { server 127.0.0.1:9000; } server{ listen 80; server_name domain; location /ws { proxy_pass http://channels-backend-prod; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; } } ... ``` #### 启动nginx ```bash systemctl start nginx ``` &nbsp; ### 五、xterm.js安装配置 #### 安装xterm.js ```bash npm install xterm ``` #### 拷贝到项目静态文件目录 ```bash cp -r node_modules/xterm xtermWS/xtermWS/static/ ``` &nbsp; ### 六、前端配置 ```html ... //引入xtrem的css和js文件 <link rel="stylesheet" href="{% static 'xterm/xterm.css' %}" /> <script src="{% static 'xterm/xterm.js' %}" ></script> <script src="{% static 'xterm/addons/attach/attach.js' %}" ></script> ... //显示终端窗口 <div id="terminal-container"> </div> ... <script type="text/javascript"> ... $(document).ready(function(){ // 声明一个websocket变量 var term_websocket; Terminal.applyAddon(attach); const term = new Terminal({ windowsMode:true, rows:10 }); //关闭之前的websocket if (typeof(term_websocket) != "undefined"){ term_websocket.close(); } const container = document.getElementById('terminal-container'); term.open(container); const protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://'; const port = location.port ? `:${location.port}` : ''; const socketUrl = `${protocol}${location.hostname}${port}/ws/容器id`; term_websocket = new WebSocket(socketUrl); term_websocket.onopen = (ev) => { term.attach(term_websocket); }; term_websocket.onclose = function(ev){ console.log('Connection closed.'); }; }); ... </script> ```
阅读 64 评论 0 收藏 0
阅读 64
评论 0
收藏 0

   2019-03-30 00:53:42    2019-03-30 00:53:42   

python asyncio 协程 coroutine
### 介绍 终端使用的是asyncio,单线程支持并发操作。 聊天终端,暂时仅提供登陆/注销/发送在线消息/发送离线消息/帮助功能(未完待续) 登陆命令:auth userid password (userid为用户账号,用户数据存储在mysql中) 注销命令:logout 发送消息:msg userid message (支持在线和离线消息,离线消息,当对方不在线时,存储到 redis中,等用户上线之后再推送给该用户) 帮助命令:help/? ### 环境 系统:`CentOS7` 编程语言:`Python` 语言解释器:`Cpython-3.7.2` 数据库:`mysql-5.7` `redis-5.0.3` 缓存:`redis-5.0.3` ### 表结构 #### user表 ```sql CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8; ``` ### 服务器端代码 chat_server.py ```py #coding:utf-8 ###################### # 作者: SheYinsong # # 时间: 2019-03-30 # ###################### import asyncio import aiomysql import aioredis import sys import struct import ssl import time import logging SERVER_ADDRESS = ('0.0.0.0',10000) logging.basicConfig( level=logging.DEBUG, format='%(name)s: %(message)s', stream=sys.stderr, ) log=logging.getLogger('main') #LOOP loop=asyncio.get_event_loop() #TLS配置 CERTFILE='ssl/server.crt' KEYFILE='ssl/server.key' #在线用户userid到读写Stream映射 auth_userid_map_wstream={} auth_wstream_map_userid={} #mysql 配置 db_config={ 'host':'localhost', 'port':3306, 'user':'root', 'password':'xxxxxxxx', 'db':'chat', 'charset':'utf8' } #SQL 模板 QUERY_AUTH_SQL='select password from user where id=%s' QUERY_USERID_IS_EXISTS_SQL='select id from user where id=%s' QUERY_USERNAME_OF_USERID_SQL='select username from user where id=%s' #redis配置 #redis_host='localhost' #redis_port=6379 redis_unix_socket='/var/run/redis/redis.sock' #redis KEY模板 KV_EXISTS_USERID=u'kv_exists_userid:userid:%s' KV_USERID_GET_USERNAME=u'kv_userid_get_username:userid:%s' LIST_USERMESSAGES_OF_USERID=u'list_usermessages_of_userid:userid:%s' #支持的命令列表 cmd_list=['auth','msg','logout'] #命令帮助 cmd_help=""" =============================================== | | | 命令使用说明: | | | =============================================== |命令 参数 参数 (说明) | =============================================== |auth UID PASSWORD (登录系统) | |msg UID MESSAGE (给用户发送消息) | =============================================== |命令 (说明) | =============================================== |logout (退出) | =============================================== """ async def get_custom_time_string(t_format="%Y-%m-%d %X"): """获取指定格式的时间字符串 默认格式为"%Y-%m-%d %X" """ return time.strftime(t_format) async def get_db_pool(): """获取mysql连接池""" pool = await aiomysql.create_pool(**db_config) return pool async def get_redis_pool(): """获取redis连接池""" pool = await aioredis.create_pool( redis_unix_socket, minsize=5, maxsize=10) return pool async def db_query(sql): """mysql执行查询操作""" pool = await get_db_pool() results=() async with pool.acquire() as conn: async with conn.cursor() as cur: log.debug(f'查询数据库,SQL->[ {sql} ]') await cur.execute(sql) results = await cur.fetchall() pool.close() await pool.wait_closed() return results async def redis_query(cmd,*args): """redis执行查询操作""" pool=await get_redis_pool() with await pool as conn: results =await conn.execute(cmd,*args) pool.close() await pool.wait_closed() return results async def redis_dml(cmd,*args): """redis执行增删改操作""" pool=await get_redis_pool() with await pool as conn: await conn.execute(cmd,*args) pool.close() await pool.wait_closed() async def get_username_of_userid(userid): """获取userid对应的用户名""" if await redis_query('exists',KV_USERID_GET_USERNAME % userid): username=await redis_query('get',KV_USERID_GET_USERNAME % userid) return username.decode('utf-8') else: res=await db_query(QUERY_USERNAME_OF_USERID_SQL % userid) if res: username=res[0][0] await redis_dml('set',KV_USERID_GET_USERNAME % userid,username) return username return '' async def get_userid_of_wstream(wstream): """通过writestream获取userid""" try: userid=auth_wstream_map_userid[wstream] return userid except KeyError: return -1 async def get_wstream_of_userid(userid): """通过userid获取writestream""" try: write_stream=auth_userid_map_wstream[userid] return write_stream except KeyError: return '' async def write_data(writer,data): """发送数据""" b_data=bytes(data,encoding='utf-8') b_datasize=struct.pack('H',len(b_data)) try: writer.write(b_datasize+b_data) await writer.drain() return True except: await clean_wstream(writer) return False async def read_data(reader): """读取数据""" try: length=struct.unpack('H',await reader.read(2))[0] b_data=await reader.read(int(length)) return b_data.decode('utf-8') except Exception as e: log.debug(f'读取客户端数据异常:[{e}]') return '' async def clean_wstream(writer): """清理客户端登陆信息和关闭stream""" userid=await get_userid_of_wstream(writer) if userid != -1: try: del auth_userid_map_wstream[userid] except: pass try: del auth_wstream_map_userid[writer] except: pass writer.close() async def user_auth(*args): """用户认证""" userid=int(args[0]) pwd=args[1] sql=QUERY_AUTH_SQL % (userid) res=await db_query(sql) if res: if res[0][0] == pwd: return True return False async def userid_is_exists(userid): """"判断userid是否存在""" if await redis_query('exists',KV_EXISTS_USERID % userid): return True else: res=await db_query(QUERY_USERID_IS_EXISTS_SQL % userid) if res: await redis_dml('set',KV_EXISTS_USERID % userid,'') return True return False async def userid_is_online(userid): """判断用户是否在线""" try: writer=auth_userid_map_wstream[userid] return True except KeyError: return False async def offline_userid(operator_type,*args): """下线用户""" userid=int(args[0]) try: writer=auth_userid_map_wstream[userid] if operator_type == 0: msg='有用户登录您的账号,您已被挤下线!' elif operator_type == 1: msg='您已退出登录!' else: msg='您已退出系统!' await write_data(writer,msg) except Exception as e: log.debug(e) finally: try: del auth_userid_map_wstream[userid] except: log.debug('auth_userid_map_wstream 删除失败') try: del auth_wstream_map_userid[writer] except: log.debug('auth_wstream_map_userid 删除失败') async def send_user_msg(writer,*args): """发送用户消息""" to_userid=int(args[0]) from_userid=auth_wstream_map_userid[writer] content=' '.join(args[1:]) if not await userid_is_exists(to_userid): msg='系统不存在该userid!' await write_data(writer,msg) return from_username=await get_username_of_userid(from_userid) to_username=await get_username_of_userid(to_userid) custom_time=await get_custom_time_string() send_content=f'{custom_time}\n[ {from_username}|UID:{from_userid} ]:{content}' if to_userid == from_userid: send_content=f'{custom_time}\n[ {from_username}|UID:{from_userid} ]:{content}' await write_data(writer,send_content) return if await userid_is_online(to_userid): to_writer=auth_userid_map_wstream[to_userid] if await write_data(to_writer,send_content): await write_data(writer,send_content) return #用户不在线,发送离线消息 print("用户不在线,发送离线消息") await redis_dml('rpush',LIST_USERMESSAGES_OF_USERID % to_userid,send_content) await write_data(writer,f'[离线消息]\n{send_content}') async def user_is_auth(writer): """判断用户是否认证""" if writer in auth_wstream_map_userid.keys(): return True return False async def get_offline_msg_of_userid(userid): "获取userid对应的离线消息" return await redis_query('lrange',LIST_USERMESSAGES_OF_USERID % userid,0,-1) async def push_offline_msg(writer,*args): """推送离线消息""" userid=int(args[0]) user_offline_messages_list=await get_offline_msg_of_userid(userid) for user_offline_message in user_offline_messages_list: await write_data(writer,user_offline_message.decode('utf-8')) await redis_query('del',LIST_USERMESSAGES_OF_USERID % userid) async def update_auth(writer,*args): """更新auth_userid_map_wstream和auth_wstream_map_userid""" userid=int(args[0]) auth_userid_map_wstream[userid]=writer auth_wstream_map_userid[writer]=userid async def check_args_validity(writer,cmd,*args): if cmd == 'auth': if len(args) != 2: msg='提示:认证格式: [ auth userid pwd ]' await write_data(writer,msg) return False try: userid=int(args[0]) except: msg='userid为整数!' await write_data(writer,msg) return False elif cmd == 'msg': if len(args) < 2: msg='提示:消息格式: [ msg userid content ]' await write_data(writer,msg) return False try: to_userid=int(args[0]) except: msg='userid为整数!' await write_data(writer,msg) return False return True async def handler_client(reader,writer): """处理客户端连接""" address=writer.get_extra_info('peername') log=logging.getLogger('echo_{}_{}'.format(*address)) log.debug('收到新连接') args_ok=True cmd_ok=True while True: if not await user_is_auth(writer) and cmd_ok and args_ok: msg=u'请登陆!' await write_data(writer,msg) data=await read_data(reader) if not data: break cmd=data.split(' ')[0] args=data.split(' ')[1:] if cmd == '?' or cmd == 'help': await write_data(writer,f'{cmd_help}') cmd_ok=False continue elif cmd not in cmd_list: await write_data(writer,'输入的命令不支持,请使用 help 或 ? 查看帮助!') cmd_ok=False continue cmd_ok=True if not await user_is_auth(writer): if cmd != 'auth': continue if not await check_args_validity(writer,cmd,*args): args_ok=False continue args_ok=True if await user_auth(*args): await offline_userid(0,*args) await update_auth(writer,*args) msg='认证成功!' await write_data(writer,msg) await push_offline_msg(writer,*args) else: msg='认证失败!' await write_data(writer,msg) else: if not await check_args_validity(writer,cmd,*args): args_ok=False continue args_ok=True if cmd == 'msg': await send_user_msg(writer,*args) elif cmd == 'logout': userid=await get_userid_of_wstream(writer) await offline_userid(1,userid) elif cmd == 'auth': await write_data(writer,'您已登录!') context=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_cert_chain(certfile=CERTFILE,keyfile=KEYFILE) factory=asyncio.start_server(handler_client,*SERVER_ADDRESS,ssl=context) server=loop.run_until_complete(factory) log.debug('starting up on {} port {}'.format(*SERVER_ADDRESS)) try: loop.run_forever() except KeyboardInterrupt: pass finally: log.debug('关闭服务器.') server.close() loop.run_until_complete(server.wait_closed()) log.debug('关闭事件循环LOOP.') loop.close() ``` ### 客户端 chat_client.py ```py #coding:utf-8 ###################### # 作者: SheYinsong # # 时间: 2019-03-30 # ###################### import asyncio from aioconsole import ainput import concurrent.futures import sys import ssl import time import struct import logging #日志配置 logging.basicConfig( level=logging.DEBUG, format='%(name)s: %(message)s', stream=sys.stderr, ) log = logging.getLogger('main') #TLS配置 CERTFILE='ssl/server.crt' #chat服务器配置 SERVER_ADDRESS = ('chat.unotes.co',10000) #证书使用的chat.unotes.co域名,根据实际证书域名填写 loop = asyncio.get_event_loop() async def write_data(writer,data): """发送数据""" b_data=bytes(data,encoding='utf-8') b_datasize=struct.pack('H',len(b_data)) try: writer.write(b_datasize+b_data) await writer.drain() return True except: return False async def read_data(reader): """读取数据""" try: length=struct.unpack('H',await reader.read(2))[0] b_data=await reader.read(int(length)) return b_data.decode('utf-8') except Exception as e: log.debug(f'读取客户端数据异常:[{e}]') return '' async def read(reader,once=False): """循环读取数据""" while True: data=await read_data(reader) if data: if once: print(f'{data}\n',end='') break print(f'\x08\x08\x08{data}\n>>>',end='') async def chat_client(address): """聊天客户端""" log = logging.getLogger('chat_client') log.debug('connecting to {} port {}'.format(*address)) context=ssl.create_default_context(ssl.Purpose.SERVER_AUTH) context.load_verify_locations(CERTFILE) reader, writer = await asyncio.open_connection(*address,ssl=context) await read(reader,True) read_task=asyncio.create_task(read(reader)) while True: data=await ainput('>>>') if not data: data='?' await write_data(writer,data) try: loop.run_until_complete( chat_client(SERVER_ADDRESS) ) except: pass finally: log.debug('closing event loop') loop.close() ``` ### 运行服务器 ```bash $ python chat_server_asyncio_tls.py ``` ``` asyncio: Using selector: EpollSelector main: starting up on 0.0.0.0 port 10000 ``` ### 运行客户端1 #### 用户张三 ```bash python chat_client_asyncio_tls.py ``` ``` 请登陆! >>>auth 10001 123456 认证成功! >>>msg 10002 你好,我是张三 [离线消息] 2019-03-29 17:00:24 [ 张三|UID:10001 ]:你好,我是张三 >>> ``` ### 运行客户端2 #### 用户李四 ```bash python chat_client_asyncio_tls.py ``` ``` 请登陆! >>>auth 10002 123456 认证成功! 2019-03-29 17:00:24 [ 张三|UID:10001 ]:你好,我是张三 >>>msg 10001 你好,张三,我收到您的消息 2019-03-29 17:02:52 [ 李四|UID:10002 ]:你好,张三,我收到您的消息 >>> ``` #### 查看客户端1 ### 用户张三 ```bash $ python chat_client_asyncio_tls.py ``` ``` 请登陆! >>>auth 10001 123456 认证成功! >>>msg 10002 你好,我是张三 [离线消息] 2019-03-29 17:00:24 [ 张三|UID:10001 ]:你好,我是张三 2019-03-29 17:02:52 [ 李四|UID:10002 ]:你好,张三,我收到您的消息 ```
阅读 170 评论 0 收藏 0
阅读 170
评论 0
收藏 0

   2019-03-12 14:51:06    2019-06-17 10:27:42   

对称加密 非对称加密 摘要算法
### 加密算法 说明:这里为了方便介绍,引入发送方A和接收方B       #### 非对称加密算法(被加密数据的安全性) `场景`:加密(RSA/DH/Elgamal/ECC)少量数据、数字签名(RSA/DSA/Elgamal) `算法`:DH RSA DSA ECC(椭圆曲线算法) Elgamal Merkle-Hellman(背包算法) Miller-Rabin(素数测试算法) 加密/解密场景 ```bash 发送方A 加密:DH/RSA/DSA公钥(接收方B)+对称加密的KEY-->密文KEY 接收方B 解密:DH/RSA/DSA私钥(接收方B)+密文KEY-->对称加密的KEY ``` 数字签名/验签场景 ```bash 发送方A 签名: 1.(SHA1/MD5+明文)->摘要1 2.摘要1+(RSA/Elgamal)私钥(发送方A)-->数字签名 接收方B 验签: 1. RSA公钥(发送方A)+数字签名-->摘要1(解密出来的) 2. SHA1/MD5+明文->摘要2(接收方B自己计算的) 3. 摘要1==摘要2 ?"成功":"失败" (对比摘要1和摘要2的值是否一致) ```    #### 对称加密算法 `场景`:适合加密大数据 `算法`:AES DES 3DES PBE RC5 Blowfish 加密/解密场景 ```bash 加密过程:明文+AES/DES+对称加密的KEY->密文 解密过程:密文+AES/DES+对称加密的KEY->明文 ``` AES加密算法的Counter模式 - ECB(电子密码本模式) - CTR(计数器模式) - GCM(CTR和GMAC结合)    #### AEAD加密方式(Authenticated Encryption with Associated Data (AEAD) `介绍`:一种同时具备保密性,完整性和可认证性的加密形式 AEAD - EtM方式:加密 then MAC - MtE方式:MAC then 加密 - E&M方式:加密 and MAC 真正的AEAD - GCM=(AES-CTR模式+GMAC结合) - ChaCha20-IETF-Poly1305 - XChaCha20-IETF-Poly1305    #### 摘要算法 `算法` MD5 SHA1 MAC GMAC ```bash 摘要操作:MD5/SHA1+文本->固定长度的HASH值 ```
阅读 63 评论 0 收藏 0
阅读 63
评论 0
收藏 0

   2019-01-21 15:19:07    2019-01-21 15:19:07   

python
#### 分析 微信认证名中一般都会附带地区名,通过数据库的省市县名和微信名做存在判断(```注意:此方法存在一定概率的误判```) #### 准备 需要省、市、县数据表的数据,需要的话请联系博主 #### 步骤 1.判断对应的省名是否在微信认证名中,如果在,则匹配出数据,并且判断如果是直辖市,获取对应的市级名字,id 2.如果省没匹配成功,则匹配市级数据的名字,如果成功,则获取对应的省名及对应省id 3.如果省市都没匹配成功,则匹配县区级数据的名字,如果成功,则获取对应的省市名及对应省市id ##### 数据表结构 ```sql CREATE TABLE `province` ( `id` bigint(19) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT '省份名称', `short_name` varchar(32) DEFAULT NULL COMMENT '省份简称', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8; CREATE TABLE `city` ( `id` bigint(19) NOT NULL AUTO_INCREMENT COMMENT '标识', `name` varchar(32) NOT NULL COMMENT '城市名称', `short_name` varchar(32) DEFAULT NULL COMMENT '城市简称', `province_id` bigint(19) NOT NULL COMMENT '所属省份标识', `level` int(10) NOT NULL COMMENT '城市等级(0未知,1:一线,2:二线,3:三线,4:四线)', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`,`province_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=7471 DEFAULT CHARSET=utf8; CREATE TABLE `district` ( `id` bigint(19) NOT NULL AUTO_INCREMENT COMMENT '标识', `name` varchar(32) NOT NULL COMMENT '区县名称', `short_name` varchar(32) DEFAULT NULL COMMENT '区县简称', `city_id` bigint(19) NOT NULL COMMENT '所属城市标识', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`,`city_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=10069 DEFAULT CHARSET=utf8; ``` ##### python脚本 ```python vim get_loc.py ``` ```python #coding:utf-8 import pymysql db_host="xx.xx.xx.xx" db_user="root" db_pass="xxxxx" db_port=3306 db_name="xxxx" #微信认证名 file=open("weixin_auth.txt",'r',encoding="utf-8") def execute_query_sql(sql): #循环读取数据库状态是0的关键字100个 db= pymysql.connect(host=db_host,port=db_port,user=db_user, passwd=db_pass, db=db_name) # 使用 cursor() 方法创建一个游标对象 cursor cursor = db.cursor() #执行sql cursor.execute(sql) results=cursor.fetchall() # 关闭数据库连接 db.close() return results n=1 for f in file.readlines(): #获取省 pro_sql='select id,name from province;' result=execute_query_sql(pro_sql) is_has_pro=False pro_id=-1 city_id=-1 district_id=-1 pro_name="" for result_entry in result: if result_entry[1][0:2] in f: is_has_pro=True pro_name=result_entry[1] pro_id=result_entry[0] #这里写死id if pro_name=="北京市": city_id=7151 if pro_name=="上海市": city_id=7122 if pro_name=="天津市": city_id=7182 if pro_name=="重庆市": city_id=7430 break #获取市 is_has_city=False city_name="" if not is_has_pro: city_sql='select id,name from city where province_id>0;' result=execute_query_sql(city_sql) for result_entry in result: if result_entry[1].replace("市","").replace("县","") in f: is_has_city=True city_name=result_entry[1] city_id=result_entry[0] #获取省id pro_id_sql='select province_id from city where id='+str(city_id)+';' result=execute_query_sql(pro_id_sql) pro_id=result[0][0] break #获取区县 is_has_district=False district_name="" if not is_has_city and not is_has_pro: district_sql='select id,name from district where city_id>0;' result=execute_query_sql(district_sql) for result_entry in result: if result_entry[1].replace("市","").replace("县","") in f: is_has_district=True district_name=result_entry[1] district_id=result_entry[0] #获取市id city_id_sql='select city_id from district where id='+str(district_id)+';' result=execute_query_sql(city_id_sql) city_id=result[0][0] #获取省id pro_id_sql='select province_id from city where id='+str(city_id)+';' result=execute_query_sql(pro_id_sql) pro_id=result[0][0] break #打印对应的id #获取名字 if pro_id>0: sql='select name from province where id='+str(pro_id)+';' result=execute_query_sql(sql) pro_name=result[0][0] if city_id>0: sql='select name from city where id='+str(city_id)+';' result=execute_query_sql(sql) city_name=result[0][0] if district_id >0: sql='select name from district where id='+str(district_id)+';' result=execute_query_sql(sql) district_name=result[0][0] n+=1 print("当前执行到第"+str(n)+"行") print(f.strip()+","+str(pro_id)+","+str(city_id)+","+str(district_id)+","+pro_name+","+city_name+","+district_name+"\n") #把数据保存在文件中 file2=open("weixin_auth_loc.txt",'a',encoding="utf-8") file2.write(f.strip()+","+str(pro_id)+","+str(city_id)+","+str(district_id)+","+pro_name+","+city_name+","+district_name+"\n") file2.close() ```
阅读 73 评论 0 收藏 0
阅读 73
评论 0
收藏 0

   2019-01-16 12:00:16    2019-01-16 12:00:16   

python selenium 爬虫
### 本文讲解通过python selenium firefox mysql的方式爬取搜狗微信公众号数据 `说明:搜狗微信的反爬虫,scrapy框架爬取易被检测,使用selenium的方式(缺点:慢。优点:不易被检测到)。` 安装相关软件教程参考:https://ynotes.cn/blog/article_detail/158 #### 流程: 1.脚本循环查询关键字表(table keys)中关键字类型字段(column type)所对应的关键字字段(column keyword)前100条数据 2.通过获取关键字循环去搜狗微信去搜索 3.爬取搜狗搜索出来的微信公众号 4.判断页面是否有分页,有则循环爬取。爬取完一个页面,更新爬取页面数字段(column page_num),所有页面更新关键字表的状态字段(column status[0:表示未爬取,1:表示已爬取]) 5.对爬取出来的数据插入到微信公众号数据表(weixin_data)(建相关数据表) 6.更新关键字表的状态为已爬取状态 #### 数据表结构 ```sql CREATE TABLE `keys` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `keyword` varchar(255) DEFAULT NULL, `page_num` int(11) DEFAULT '0', `status` int(11) DEFAULT '0' COMMENT '0 未搜索 1 已搜索 99 丢弃', `type` varchar(255) DEFAULT NULL, `is_drop` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=119750 DEFAULT CHARSET=utf8; CREATE TABLE `weixin_data` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `key_id` int(255) DEFAULT NULL, `weixin_name` varchar(255) DEFAULT NULL, `weixin_account` varchar(255) DEFAULT NULL, `weixin_auth_info` varchar(255) DEFAULT NULL, `is_auth` int(11) DEFAULT NULL, `describe` varchar(6000) DEFAULT NULL, `img_url` varchar(255) DEFAULT NULL, `loc_info` varchar(255) DEFAULT NULL, `privince` varchar(255) DEFAULT NULL, `city` varchar(255) DEFAULT NULL, `district` varchar(255) DEFAULT NULL, `weixin_type` varchar(255) DEFAULT NULL, `other` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `weixin_account` (`weixin_account`) ) ENGINE=InnoDB AUTO_INCREMENT=139746 DEFAULT CHARSET=utf8; ``` #### 爬虫脚本 scrapy_sogou.py ```python #coding=utf-8 from selenium import webdriver import time from selenium.common.exceptions import NoSuchElementException,TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import pymysql import random 指定页面 #参数指定了缓存文件的路径,方便爬取需要登录的网站 profile = webdriver.FirefoxProfile(r'C:\Users\Administrator.GZLX-20180416SV\AppData\Roaming\Mozilla\Firefox\Profiles\yn80ouvt.default') #如果不需要cookie,则不需要指定,使用下面的配置 #profile = webdriver.FirefoxProfile() #禁止加载样式表 profile.set_preference("permissions.default.stylesheet",2) #禁止加载图片 profile.set_preference("permissions.default.image",2) #禁止加载JAVASCRIPT profile.set_preference("javascript.enabled",False) #设置代理 profile.set_preference('network.proxy.type', 1) profile.set_preference('network.proxy.http', 'xx.xx.xx.xx') profile.set_preference('network.proxy.http_port', xxxx) profile.set_preference('network.proxy.ssl', 'xx.xx.xx.xx') profile.set_preference('network.proxy.ssl_port', xxxx) profile.update_preferences() #数据库配置 db_host="xx.xx.xx.xx" db_user="root" db_pass="xxxx" db_port=3306 db_name="weixin_data" #指定Firefox的驱动 driver = webdriver.Firefox(firefox_profile=profile,executable_path="geckodriver") #搜索的关键字 key_search_list=['学校'] index_url='https://weixin.sogou.com/weixin?query=' keys_search_string="" for index in range(0,len(key_search_list)): if index==len(key_search_list)-1: keys_search_string+="'"+key_search_list[index]+"'" else: keys_search_string+="'"+key_search_list[index]+"'," class AnyEc: """ Use with WebDriverWait to combine expected_conditions in an OR. """ def __init__(self, *args): self.ecs = args def __call__(self, driver): for fn in self.ecs: try: if fn(driver): return True except: pass def execute_query_sql(sql): #循环读取数据库状态是0的关键字100个 db= pymysql.connect(host=db_host,port=db_port,user=db_user, passwd=db_pass, db=db_name) # 使用 cursor() 方法创建一个游标对象 cursor cursor = db.cursor() #执行sql cursor.execute(sql) results=cursor.fetchall() # 关闭数据库连接 db.close() return results def execute_update_sql(sql): #循环读取数据库状态是0的关键字100个 db= pymysql.connect(host=db_host,port=db_port,user=db_user, passwd=db_pass, db=db_name) # 使用 cursor() 方法创建一个游标对象 cursor cursor = db.cursor() # 执行sql语句 cursor.execute(sql) # 提交到数据库执行 db.commit() #执行sql # 关闭数据库连接 db.close() #爬取网站内容的函数 def parseWeb(driver,key_name,page_N): print("开始提取关键字"+key_name+",第"+str(page_N)+"页的数据:") for page in driver.find_elements_by_xpath('//ul[@class="news-list2"]/li'): weixin_name=page.find_element_by_xpath('./div[@class="gzh-box2"]/div[@class="txt-box"]/p[@class="tit"]/a').text img_url=page.find_element_by_xpath('./div[@class="gzh-box2"]/div[@class="img-box"]/a/img').get_attribute("src") weixin_account=page.find_element_by_xpath('./div[@class="gzh-box2"]/div[@class="txt-box"]/p[@class="info"]/label').text weixin_auth_info="" try: page.find_element_by_xpath('./dl[2]/dt[contains(text(),微信认证)]') dl_info=page.find_element_by_xpath('./dl[2]/dt').text if '微信认证' in dl_info: weixin_auth_info=page.find_element_by_xpath('.//dl[2]/dd').text except NoSuchElementException: weixin_auth_info="" print("微信认证:"+weixin_auth_info) try: page.find_element_by_xpath('./div[@class="gzh-box2"]/div[@class="txt-box"]/p[@class="tit"]/i') is_auth=1 except NoSuchElementException: is_auth=0 try: describe=page.find_element_by_xpath('.//dl[1]/dd').text except NoSuchElementException: describe="" #把数据插入酷内 insert_sql='insert into weixin_data(key_id,weixin_name,weixin_account,weixin_auth_info,is_auth,img_url,`describe`,weixin_type,other) values('+str(key_id)+',"'+weixin_name+'","'+weixin_account+'","'+weixin_auth_info+'",'+str(is_auth)+',"'+img_url+'","'+describe+'","'+'培训机构'+'",'+'NULL'+'); ' #print(insert_sql) try: print("准备插入数据:"+weixin_name) execute_update_sql(insert_sql) except: print("插入数据异常,可能是重复数据") #更新当前页数 update_sql='update `keys` set page_num='+str(page_N)+' where keyword="'+key_name+'";' print(update_sql) try: execute_update_sql(update_sql) except: print("更新爬取页数错误") return False return True #判断页面是否加载完成 def pageIsLoadFinished(driver): try: WebDriverWait(driver, 10).until( AnyEc( EC.presence_of_element_located( (By.XPATH, u'//div[@class="gzh-box2"]/div[@class="img-box"]/a/img')), EC.presence_of_element_located( (By.XPATH, u'//p[@class="ip-time-p"]')), EC.presence_of_element_located( (By.XPATH, u'//div[@id="noresult_part1_container"]')) )) return True except TimeoutException: return False #页面是否正常 def pageIsNomal(driver): try: driver.find_element_by_xpath('//p[@class="ip-time-p"]') print("IP访问频繁,准备重启浏览器") time.sleep(3) return False except NoSuchElementException: return True #页面是否404 def pageIsNotFound(driver,key_name): try: driver.find_element_by_xpath('//div[@id="noresult_part1_container"]') print("关键字"+key_name+"没有找到,搜索下一个关键字") return True except NoSuchElementException: return False #跳到指定页 def jumpNumPage(driver,page_N): #判断是否是当前页 try: current_page=driver.find_element_by_xpath('//div[@id="pagebar_container"]/span').text if int(page_N) == int(current_page): print("已经在当前页,无需跳转") return True except: print("没有当前页"+str(page_N)) return False try: driver.find_element_by_xpath('//div[@id="pagebar_container"]/a[@id="sogou_page_'+str(page_N)+'"]').click() except NoSuchElementException: print("没有第"+str(page_N)+"页面") return False return True #跳到下一页 def jumpNextPage(driver): try: driver.find_element_by_xpath('//div[@id="pagebar_container"]/a[@id="sogou_next"]').click() except NoSuchElementException: print("没有下一页") return False return True #页面是否准备好 def PageIsReady(driver,key_name,page_N): #判断页面已经加载完成,并且不存在ip频繁访问页面 if pageIsLoadFinished(driver) and pageIsNomal(driver): #判断页面不存在指定的标签页 if not jumpNumPage(driver,page_N): #判断页面是否404 if pageIsNotFound(driver,key_name): #更新数据库关键字字段 update_status_sql='update `keys` set status=1 where keyword="'+key_name+'";' try: execute_update_sql(update_status_sql) except: print("更新关键字"+key_name+"的状态失败!!!") return True else: return False return True #循环抓取 while True: get_keys="SELECT id,keyword FROM keys where status=0 and is_drop=0 and type in ("+keys_search_string+") limit 100;" print(get_keys) print("获取关键字中...") try: results=execute_query_sql(get_keys) except: print("数据库查询关键字失败,停止爬虫") break print("关键字查找完成") #生成url id_keys=[ re for re in results ] for id_key in id_keys: key_id=id_key[0] key_name=id_key[1] url=index_url+key_name print("开始爬取:"+url) try: driver.get(url) except TimeoutException: continue #获取爬取key的页数 get_page_sql='select page_num from `keys` where id='+str(key_id)+';' page_N=execute_query_sql(get_page_sql)[0][0]+1 if PageIsReady(driver,key_name,page_N): if not parseWeb(driver,key_name,page_N): continue else: time.sleep(1) driver.close() driver = webdriver.Firefox(firefox_profile=profile,executable_path="geckodriver") continue #跳转到当前页爬取 #爬取完当前页更新key关键字 #判断是否有下一页继续爬取,如果只爬取一页,则注释下面的代码 ##########是否爬取搜索关键字的所有页面--start isOk=True while jumpNextPage(driver): get_page_sql='select page_num from `keys` where id='+str(key_id)+';' page_N=execute_query_sql(get_page_sql)[0][0]+1 if PageIsReady(driver,key_name,page_N): if not parseWeb(driver,key_name,page_N): isOk=False break else: isOk=False break if not isOk: time.sleep(1) driver.close() driver = webdriver.Firefox(firefox_profile=profile,executable_path="geckodriver") continue ##########是否爬取搜索关键字的所有页面--end #更新关键字状态 update_status_sql='update `keys` set status=1 where keyword="'+key_name+'";' try: execute_update_sql(update_status_sql) except: print("更新数据库关键字"+key_name+"的状态发生错误") ```
阅读 97 评论 0 收藏 0
阅读 97
评论 0
收藏 0

   2018-11-11 23:24:22    2018-11-11 23:24:22   

c语言 C Windows API
阅读 350 评论 0 收藏 0
阅读 350
评论 0
收藏 0

第 1 页 / 共 4 页
 
第 1 页 / 共 4 页