兜兜    2021-07-05 00:27:42    2021-07-05 00:45:56   

sqlserver 锁表
查看锁表SQL(blocking_session_id为锁表session_id) ```sql SELECT es.session_id, database_name = DB_NAME(er.database_id), er.cpu_time, er.reads, er.writes, er.logical_reads, login_name, er.status, blocking_session_id, wait_type, individual_query = SUBSTRING (qt.text, er.statement_start_offset/2, (CASE WHEN er.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 ELSE er.statement_end_offset END - er.statement_start_offset)/2), parent_query = qt.text, program_name, host_name, nt_domain, start_time FROM sys.dm_exec_requests er INNER JOIN sys.dm_exec_sessions es ON er.session_id = es.session_id CROSS APPLY sys.dm_exec_sql_text(er.sql_handle)as qt WHERE es.session_id > 50 AND es.session_Id NOT IN (@@SPID) ORDER BY 1, 2 ``` 查询session_id的tcp连接信息(获取到对方的ip和端口) ```sql SELECT * FROM sys.dm_exec_connections WHERE session_id = [session_id] ``` `其他获取session_id的信息方法` 创建sp_who3 ```sql CREATE PROCEDURE sp_who3 ( @SessionID INT = NULL ) AS BEGIN SELECT SPID = er.session_id ,STATUS = ses.STATUS ,[Login] = ses.login_name ,Host = ses.host_name ,BlkBy = er.blocking_session_id ,DBName = DB_Name(er.database_id) ,CommandType = er.command ,SQLStatement = st.text ,ObjectName = OBJECT_NAME(st.objectid) ,ElapsedMS = er.total_elapsed_time ,CPUTime = er.cpu_time ,IOReads = er.logical_reads + er.reads ,IOWrites = er.writes ,LastWaitType = er.last_wait_type ,StartTime = er.start_time ,Protocol = con.net_transport ,ConnectionWrites = con.num_writes ,ConnectionReads = con.num_reads ,ClientAddress = con.client_net_address ,Authentication = con.auth_scheme FROM sys.dm_exec_requests er OUTER APPLY sys.dm_exec_sql_text(er.sql_handle) st LEFT JOIN sys.dm_exec_sessions ses ON ses.session_id = er.session_id LEFT JOIN sys.dm_exec_connections con ON con.session_id = ses.session_id WHERE er.session_id > 50 AND @SessionID IS NULL OR er.session_id = @SessionID ORDER BY er.blocking_session_id DESC ,er.session_id END GO ``` 查看session_id的信息 ```sql exec [db_name].dbo.sp_who3 [session_id] ```
阅读 637 评论 0 收藏 0
阅读 637
评论 0
收藏 0

兜兜    2021-06-30 13:28:31    2022-01-25 09:34:17   

sqlserver prometheus
创建监控数据的表 ```sql USE [distribution] GO /****** Object: Table [dbo].[tmpPendingResult] Script Date: 2021/6/30 13:19:50 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[tmpPendingResult]( [publisher] [nvarchar](200) NULL, [publisher_db] [nvarchar](200) NULL, [publication] [nvarchar](200) NULL, [subscriber] [nvarchar](200) NULL, [subscriber_db] [nvarchar](200) NULL, [subscription_type] [nvarchar](200) NULL, [pendingcmdcount] [bigint] NULL, [estimatedprocesstime] [bigint] NULL ) ON [PRIMARY] GO ``` 增加一个每分钟的定时作业 ```sql use distribution delete from distribution.dbo.tmpPendingResult declare my_cursor cursor for --my_cursor为游标的名称,随便起 SELECT a.publisher , a.publisher_db , a.publication , c.name AS subscriber , b.subscriber_db AS subscriber_db , CAST(b.subscription_type AS VARCHAR) AS subscription_type FROM dbo.MSreplication_monitordata a ( NOLOCK ) JOIN ( SELECT publication_id , subscriber_id , subscriber_db , subscription_type FROM MSsubscriptions (NOLOCK) GROUP BY publication_id , subscriber_id , subscriber_db , subscription_type ) b ON a.publication_id = b.publication_id JOIN sys.servers c ( NOLOCK ) ON b.subscriber_id = c.server_id WHERE a.agent_type = 1 --这是游标my_cursor的值,这里随便发挥看业务场景 --打开游标 open my_cursor --没什么好说的 --变量 declare @publisher NVARCHAR(200) declare @publisher_db NVARCHAR(200) declare @publication NVARCHAR(200) declare @subscriber NVARCHAR(200) declare @subscriber_db NVARCHAR(200) declare @subscription_type NVARCHAR(200) declare @pendingcmdcount BIGINT declare @estimatedprocesstime BIGINT declare @sql NVARCHAR(2000) --循环游标 fetch next from my_cursor into @publisher,@publisher_db,@publication,@subscriber,@subscriber_db,@subscription_type while @@FETCH_STATUS=0 --假如检索到了数据继续执行 begin set @sql='select @pendingcmdcount=pendingcmdcount,@estimatedprocesstime=estimatedprocesstime FROM OPENROWSET(''SQLOLEDB'',''Server='+@publisher+';Trusted_Connection=yes;'',''set fmtonly off;exec distribution..sp_replmonitorsubscriptionpendingcmds @publisher='''''+@publisher+''''',@publisher_db='''''+@publisher_db+''''',@publication='''''+@publication+''''',@subscriber='''''+@subscriber+''''',@subscriber_db='''''+@subscriber_db+''''',@subscription_type=0'')' exec sp_executesql @sql,N'@pendingcmdcount BIGINT out,@estimatedprocesstime BIGINT out',@pendingcmdcount out,@estimatedprocesstime out insert into distribution.dbo.tmpPendingResult values(@publisher,@publisher_db,@publication,@subscriber,@subscriber_db,@subscription_type,@pendingcmdcount,@estimatedprocesstime) fetch next from my_cursor into @publisher,@publisher_db,@publication,@subscriber,@subscriber_db,@subscription_type end--关闭释放游标 close my_cursor deallocate my_cursor ``` 1、开启Ad Hoc Distributed Queries组件,在sql查询编辑器中执行如下语句: ```sql exec sp_configure 'show advanced options',1 reconfigure exec sp_configure 'Ad Hoc Distributed Queries',1 reconfigure ``` 2、关闭Ad Hoc Distributed Queries组件,在sql查询编辑器中执行如下语句: ```sql exec sp_configure 'Ad Hoc Distributed Queries',0 reconfigure exec sp_configure 'show advanced options',0 reconfigure ``` 获取数据暴露metrics ```bash docker run -d \ --name prometheus-sql \ -p 8080:8080 \ -v /opt/docker/prometheus-sql/queries.yml:/queries.yml \ --link sqlagent:sqlagent \ dbhi/prometheus-sql ``` 修改queries.yml ```bash cat /opt/docker/prometheus-sql/queries.yml ``` ```yml - pendingcmdcount: driver: mssql connection: host: x.x.x.x port: 1433 user: monitor password: xxxxxx database: master sql: > select publisher,publisher_db,subscriber,subscriber_db,publication,sum(pendingcmdcount) as pe from distribution.dbo.tmpPendingResult group by publisher,publisher_db,subscriber,subscriber_db,publication data-field: pe interval: 30s ``` 查看暴露的参数 http://172.16.11.10:8080/metrics ``` ... query_result_pendingcmdcount{publication="xgj_w1_stl_dingding",publisher="mssql01",publisher_db="xgj_w1_stl",subscriber="172.16.13.3",subscriber_db="dingding_source"} 0 query_result_pendingcmdcount{publication="xgj_w1_stl_dingding_p1",publisher="mssql01",publisher_db="xgj_w1_stl",subscriber="172.16.13.3",subscriber_db="dingding_source"} 0 query_result_pendingcmdcount{publication="xgj_w1_stl_dingding_p2",publisher="mssql01",publisher_db="xgj_w1_stl",subscriber="172.16.13.3",subscriber_db="dingding_source"} 19 ... ```
阅读 540 评论 0 收藏 0
阅读 540
评论 0
收藏 0

兜兜    2021-06-16 20:11:18    2021-10-27 15:16:38   

mysql sqlserver SymmetricDS
##### 1.下载SymmetricDS [http://www.symmetricds.org/](https://www.symmetricds.org/) #### 2.安装配置 ##### 2.1解压安装包到E盘重命名为sym-38 ##### 2.2配置主从配置 拷贝安装包下的samples下的corp-000.properties和store-001.properties到目录engines 配置sqlserver(主) ```properties engine.name=corp-000 db.driver=net.sourceforge.jtds.jdbc.Driver db.url=jdbc:jtds:sqlserver://172.16.13.3:1433/dingding_source;sendStringParametersAsUnicode=false; useCursors=true;bufferMaxMemory=10240;lobBuffer=5242880 db.user=sa db.password=xxxxxxxx registration.url= sync.url=http://172.16.13.3:8050/sync/corp-000 group.id=corp external.id=000 job.purge.period.time.ms=7200000 job.routing.period.time.ms=5000 job.push.period.time.ms=10000 job.pull.period.time.ms=10000 auto.registration=true initial.load.create.first=true ``` 配置mysql(从) ```properties engine.name=store-001 db.driver=com.mysql.jdbc.Driver db.url=jdbc:mysql://172.16.13.23/shudoon-dingding-source?tinyInt1isBit=false&useSSL=false db.user=root db.password=xxxxxx registration.url=http://172.16.13.3:8050/sync/corp-000 group.id=store external.id=001 job.routing.period.time.ms=5000 job.push.period.time.ms=10000 job.pull.period.time.ms=10000 ``` #### 3.初始化sym系统表 ```bat symadmin --engine corp-000 create-sym-tables ``` #### 4.重置sym系统表数据 ```bat dbimport --engine corp-000 E:\sym-38\samples\insert_sample.sql ``` #### 5.创建业务表 ```bat dbimport --engine store-001 --format XML E:\sym-38\samples\create_sample.xml ``` #### 6.启动master端 ```bat sym --engine corp-000 --port 8050 ``` #### 7.启动slave端 ```bat sym --engine store-001 --port 8010 ``` #### 8.初始化item表结构 ```bat symadmin --engine corp-000 send-schema item --node 001 ``` #### 8.初始化item表数据 ```bat symadmin --engine corp-000 reload-table item --node 001 ``` #### 9.新增表tCommunicationRecords同步 主库执行 ```sql insert into sym_channel (channel_id, processing_order, max_batch_size, enabled, description) values('c_tCommunicationRecords', 3, 100000, 1, ''); insert into sym_router (router_id,source_node_group_id,target_node_group_id,router_type,create_time,last_update_time) values('corp_tCommunicationRecords_2_store', 'corp', 'store', 'default',current_timestamp, current_timestamp); insert into sym_trigger (trigger_id,source_table_name,channel_id,last_update_time,create_time) values('tCommunicationRecords','tCommunicationRecords','c_tCommunicationRecords',current_timestamp,current_timestamp); insert into sym_trigger_router (trigger_id,router_id,initial_load_order,last_update_time,create_time) values('tCommunicationRecords','corp_tCommunicationRecords_2_store', 200, current_timestamp, current_timestamp); ``` 命令行执行 ```bat symadmin --engine corp-000 send-schema tCommunicationRecords --node 001 symadmin --engine corp-000 reload-table tCommunicationRecords --node 001 ``` #### 常见问题: `1.处理sqlserver联合自增主键的表结构无法同步到mysql的问题?` 解决方法: 1.去掉建表语句中联合主键中非自增列,拷贝SQL语句手动在mysql建表。 2.修改sym_outgoing_batch表中batch_id对应数据的状态为OK ```sql update sym_outgoing_batch set status='OK' where batch_id='XXXXXX'; ``` 3.删除sym_data_event中对应batch_id和data_id的事件数据 ```sql delete from sym_data_event where batch_id='XXXXXX' and data_id='YYYYYY'; ```
阅读 1044 评论 0 收藏 0
阅读 1044
评论 0
收藏 0

兜兜    2020-01-11 09:53:26    2022-01-25 09:36:06   

iptables 防火墙
阅读 388 评论 0 收藏 0
阅读 388
评论 0
收藏 0

兜兜    2019-08-21 10:52:51    2019-11-14 14:25:36   

pygame NoVNC xvfb
### 流程 #### `浏览器--->noVNC--->xvfb虚拟桌面--->pygame程序` ### xvfb安装 #### 编辑Dockerfile ```bash cat Dockerfile ``` ```bash FROM ubuntu:xenial LABEL maintainer "sheyinsong" EXPOSE 5900 6099 ENV \ DEBIAN_FRONTEND="nonintractive" \ X11VNC_PASSWORD="password" RUN sed -i -e s#security.ubuntu.com#mirrors.aliyun.com#g -e s#archive.ubuntu.com#mirrors.aliyun.com#g /etc/apt/sources.list RUN apt-get update -y RUN apt-get install -y software-properties-common RUN add-apt-repository ppa:jonathonf/python-3.6 RUN apt-get update -y RUN apt-get install -y xvfb x11vnc python3.6 libglib2.0-0 RUN apt-get install -y curl RUN /bin/cp /usr/bin/python3.6 /usr/bin/python RUN curl https://bootstrap.pypa.io/get-pip.py | python3.6 RUN pip install pygame ADD ./start.sh /opt/start.sh ENTRYPOINT [ "bash","/opt/start.sh" ] ``` #### 创建start.sh脚本 ```bash cat start.sh ``` ```bash #!/bin/bash screen_width=${1:-800} screen_height=${2:-600} screen_depth=${3:-16} run_file=${4:-"main.py"} nohup Xvfb :1 -screen 0 ${screen_width}x${screen_height}x${screen_depth} >/dev/null 2>&1 & sleep 0.1 nohup /usr/bin/x11vnc -display :1.0 -passwd ${X11VNC_PASSWORD:-password} >/dev/null 2>&1 & sleep 0.1 DISPLAY=:1.0 export DISPLAY cd /run_game PYTHONIOENCODING=utf-8 python $run_file ``` #### 创建pygame镜像 ```bash docker build -t sheyinsong/pygame:1.0 . ```   ### Novnc安装 #### 编辑Dockerfile ```bash cat Dockerfile ``` ```bash FROM python MAINTAINER sys "sheyinsong" RUN apt-get update -y RUN apt-get upgrade -y RUN apt-get install -y python-numpy RUN apt-get clean RUN pip install redis RUN pip install simplejson ADD ./noVNC/ /noVNC/ CMD ["python", "/noVNC/utils/websockify/run", "--web", "/noVNC", "0.0.0.0:10240", "--target-config", "/noVNC/token.list"] ``` #### 下载noVNC 下载地址:https://github.com/novnc/noVNC/releases/ #### 创建token.list(token映射到对应的socket) ```bash cat noVNC/token.list #noVNC下载好后,到根目录创建该文件 ``` ```bash a35fe7f7fe8217b4369a0af4244d1fca: 127.0.0.1:5000 03b264c595403666634ac75d828439bc: 127.0.0.1:5001 ... ``` #### 创建novnc镜像 ```bash docker build -t sheyinsong/noVNC:1.0 . ``` #### 运行novnc容器 ```bash docker run -d --network="host" --restart=always -it sheyinsong/novnc:1.0 ```   #### 创建生成NoVNC地址的脚本 ```bash cat pygame_url.py ``` ```python #coding:utf-8 import sys import os import random import subprocess import hashlib import time import argparse import json parser = argparse.ArgumentParser() parser.add_argument("pygame_dir",nargs = 1, help="Pygame progrom root directory") parser.add_argument("--screen-width", help="Xvfb screen width,default value 800", default=800, type=int) parser.add_argument("--screen-height", help="Xvfb screen heigth,default value 600", default=600, type=int) parser.add_argument("--screen-depth", help="Xvfb screen depth,default value 16", default=16, type=int) parser.add_argument("--run-file", help="run specify file", default='main.py') args = parser.parse_args() pygame_dir=args.pygame_dir[0] screen_width=str(args.screen_width) screen_height=str(args.screen_height) screen_depth=str(args.screen_depth) run_file=args.run_file pygame_image='sheyinsong/pygame:1.0' sample_url='http://192.168.50.252:10240/vnc_lite.html?path=?token=' #地址为noVNC运行的地址 dict_data={} if not os.path.isdir(pygame_dir): dict_data['result']='error' dict_data['msg']=pygame_dir+'程序目录不存在.' print(json.dumps(dict_data)) exit(1) pygame_main=os.path.join(pygame_dir,'main.py') if not os.path.exists(pygame_main): dict_data['result']='error' dict_data['msg']=pygame_dir+'程序目录不存在文件main.py.' print(json.dumps(dict_data)) exit(1) source = open(pygame_main, 'r').read() + '\n' try: code_obj=compile(source, pygame_main, 'exec') except Exception as e: dict_data['result']='error' dict_data['msg']=str(e) print(json.dumps(dict_data)) exit(1) def get_free_random_port(): command="netstat -ltnp |grep x11vnc|awk '{ print $4}'|awk -F: '{ print $NF}'" p = subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,shell=True) usage_port_list=[ int(port) for port in p.stdout.read().split('\n') if port != '' ] free_post_list=set(range(5000,6000))-set(usage_port_list) return random.choice(list(free_post_list)) def convert_png(): command="find {0} -name *.png|while read line;do; convert $line $line; done".format(pygame_dir) subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,shell=True) free_random_port=get_free_random_port() convert_png() docker_out = subprocess.Popen(['docker', 'run','-ti','--cpus','0.2', '-d', '-p', str(free_random_port)+':5900', '-v', pygame_dir+':/run_game', pygame_image, screen_width, screen_height, screen_depth, run_file], stdout=subprocess.PIPE,stderr=subprocess.STDOUT) stdout,stderr = docker_out.communicate() time.sleep(1) containter_id=stdout.strip() p1 = subprocess.Popen('docker logs --tail 100 '+containter_id,stdout=subprocess.PIPE,shell=True) p2 = subprocess.Popen('grep -A 1000 Traceback',stdin=p1.stdout,stdout=subprocess.PIPE,shell=True) docker_out=p2.stdout.read() if docker_out: dict_data['result']='error' dict_data['msg']=docker_out.replace('\r\n','\\n').replace('"','\'') print(json.dumps(dict_data)) else: token=hashlib.md5(str(free_random_port)).hexdigest() url=sample_url+token+'&password=password' dict_data['result']='ok' dict_data['msg']=docker_out.replace('\r\n','\\n').replace('"','\'') dict_data['url']=url dict_data['cid']=containter_id print(json.dumps(dict_data)) ```   #### 运行脚本 ```bash python /data/app/pygame/pygame_url.py /data/app/pygame/Puser/ --run-file main.py ``` 程序返回结果(url为访问pygame的地址) ```json {"msg": "", "url": "http://192.168.50.252:10240/vnc_lite.html?path=?token=fcd4c889d516a54d5371f00e3fdd70dc&password=password", "result": "ok", "cid": "23cac4bb78e93f3fb1c7232d5778ad99468cd86cc927cfd8e5161e485239b46b"} ``` `/data/app/pygame/Puser/` 为程序根目录 `main.py` 为运行pygame的入口程序 ### 页面访问pygame ![](https://files.ynotes.cn/pygame_demo.png)
阅读 13680 评论 1 收藏 0
阅读 13680
评论 1
收藏 0

兜兜    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://files.ynotes.cn/ztree_demo.png) ### 实例 https://ynotes.cn/blog/pygame_v2/
阅读 2122 评论 0 收藏 0
阅读 2122
评论 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> ```
阅读 4462 评论 0 收藏 0
阅读 4462
评论 0
收藏 0

兜兜    2019-05-24 15:36:32    2019-11-14 14:29:32   

python pygame
### 流程: - 1.用户在线编辑或者上传代码 - 2.用户点击执行按钮 - 2.1 首先把页面的代码保存到执行目录 - 2.2 ajax调用代码运行API (注:获取访问pygame的链接) - 2.2.1 API收到请求做处理 - 2.2.1.1 运行pygame(docker)生成可访问链接 - 2.2.2 API返回链接 - 2.3 ajax页面获取到API调用结果 - 2.4 把链接加载到页面Iframe展示 &emsp; ### 上传代码和保存代码 #### 上传功能代码 视图view.py(注:只提取功能相关代码) ```python @login_required def pygame(request): upload_error='' code='' code_dir='' if request.method == 'POST': myfile=request.FILES.get('myfile', None) if myfile: myfile = request.FILES['myfile'] fs = FileSystemStorage() file_path=os.path.join('pygame',str(request.user.id)) filename = fs.save(file_path, myfile) temp_dir=os.path.join(os.path.dirname(fs.path(filename)),str(request.user.id),'tempDir') #判断临时目录是否存在存在则删除目录,创建目录 if os.path.exists(temp_dir): shutil.rmtree(temp_dir) os.mkdir(temp_dir) #判断文件是否为zip if zipfile.is_zipfile(fs.path(filename)): zip_ref = zipfile.ZipFile(fs.path(filename), 'r') zip_ref.extractall(temp_dir) is_exist_main_file=0 for fname in zip_ref.namelist(): if 'main.py' in fname: main_file=os.path.join(temp_dir,fname) is_exist_main_file=1 if is_exist_main_file: code=fs.open(main_file,'r').read() code_dir=os.path.dirname(main_file) else: upload_error='压缩包根目录下不存在main.py' zip_ref.close() fs.delete(filename) else: upload_error='文件不是压缩文件' return render(request, 'blog/pygame.html',{'upload_error': upload_error,'code':code,'code_dir':code_dir}) ``` &emsp; 模板blog/pygame.html(注:只提取功能相关代码) ```html <form method="post" class="form-inline" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <input type="file" id="myfile" name="myfile"> </div> <div class="form-group"> <button class="btn btn-primary" type="submit" >上传</button> </div> <label for="myfile">注意:请上传zip压缩包,且压缩包根目录包含main.py</label> </form> ``` &emsp; ### 保存功能代码 #### 视图view.py(注:只提取功能相关代码) ```python def pygame_save_code(request): code_dir=request.POST.get('codeDir','') code_content=request.POST.get('codeContent','') if not code_content: HttpResponse('{"error":"代码为空"}') #判断是否有codeDir,如果有则直接写入到当前目录,如果没有写入一个用户随机目录 if not code_dir: datetime_str=datetime.now().strftime('%Y_%m_%d_%H_%M_%S') create_dir=os.path.join(django_settings.MEDIA_ROOT,'pygame',str(request.user.id),datetime_str) os.mkdir(create_dir) code_dir=create_dir #把代码写入文件,返回写入的目录 file=open(os.path.join(code_dir,'main.py'),'w+') file.write(code_content) file.close() return HttpResponse({'{"NewCodeDir":"'+code_dir+'"}'}) ``` &emsp; #### 模板blog/pygame.html(注:只提取功能相关代码) ```html //代码编辑窗口 <textarea class="form-control line_number" id="yourcode" cols="80" rows="30"></textarea> //pygame展示窗口 <iframe id="pygame_iframe" src="" ></iframe> <button class="btn btn-success" id="pygame_run" type="button">运行</button>:write //处理代码运行逻辑 $('#pygame_run').click(function() { var code_content=$("#yourcode").val(); var codedir=$("#codeDir").val(); var token = $('input[name=csrfmiddlewaretoken]').val(); //调用保存编辑pygame代码API $.post('/blog/pygame_save_code/', { codeDir: codedir, codeContent: code_content, csrfmiddlewaretoken: token}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); var NewCodeDir=obj.NewCodeDir; $("#codeDir").val(NewCodeDir); setTimeout( function() { var codedir=$("#codeDir").val(); //调用执行pygame程序的API $.post('https://pygame.ynotes.cn:3443/pygame/', { codeDir: codedir}, function(returnedData){ var obj = jQuery.parseJSON(returnedData); if(obj.hasOwnProperty('url')){ $('#pygame_iframe').attr('src', obj.url); $('#pygame_text').text('运行成功'); $('#pygame_text').css('color', 'green'); } if(obj.hasOwnProperty('error')){ console.log(obj.error); $('#pygame_text').text(obj.error); $('#pygame_text').css('color', 'red'); } }).fail(function(){ console.log("error"); }); }, 100); }).fail(function(){ console.log("error"); }); }); ``` &emsp; ### 生成访问pygame链接 #### 视图view.py ```python def index(request): code_dir=request.POST.get('codeDir','nodir') command='python /data/app/pygame/pygame_url.py '+code_dir p = subprocess.Popen(command,stdout=subprocess.PIPE,shell=True) pygame_out=p.stdout.read() return HttpResponse(pygame_out) ``` &emsp; #### 生成pygame访问链接的python脚本 pygame_url.py ```python #coding:utf-8 import sys import os import random import subprocess import hashlib import time pygame_image='codemax/pygame:v3' sample_url='https://pygame.ynotes.cn:2443/vnc_lite.html?path=?token=' if len(sys.argv) < 2: print('{ "error":"请指定pygame程序目录"}') exit(1) pygame_dir=sys.argv[1] if not os.path.isdir(pygame_dir): print('{ "error":"'+pygame_dir+'程序目录不存在."}') exit(1) pygame_main=os.path.join(pygame_dir,'main.py') if not os.path.exists(pygame_main): print('{ "error":"'+pygame_dir+'程序目录不存在文件main.py."}') exit(1) source = open(pygame_main, 'r').read() + '\n' try: code_obj=compile(source, pygame_main, 'exec') except Exception as e: print('{"error":"'+str(e)+'"}') exit(1) def get_free_random_port(): command="netstat -ltnp |grep x11vnc|awk '{ print $4}'|awk -F: '{ print $NF}'" p = subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,shell=True) usage_port_list=[ int(port) for port in p.stdout.read().split('\n') if port != '' ] free_post_list=set(range(5000,6000))-set(usage_port_list) return random.choice(list(free_post_list)) free_random_port=get_free_random_port() docker_out = subprocess.Popen(['docker', 'run', '-d', '-it','--cpus=0.2','-p', str(free_random_port)+':5900', '-v', pygame_dir+':/run_game', pygame_image], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout,stderr = docker_out.communicate() time.sleep(1) containter_id=stdout.strip() p1 = subprocess.Popen('docker logs --tail 100 '+containter_id,stdout=subprocess.PIPE,shell=True) p2 = subprocess.Popen('grep -A 1000 Traceback',stdin=p1.stdout,stdout=subprocess.PIPE,shell=True) docker_out=p2.stdout.read() if docker_out: print('{"error":"'+docker_out.replace('\r\n','\\n').replace('"','\'')+'"}') else: token=hashlib.md5(str(free_random_port)).hexdigest() url=sample_url+token+'&password=password' print('{"url":"'+url+'"}') ``` &emsp; ### 构建pygame镜像(拥有运行pygame代码) #### DockerFile ```yaml FROM ubuntu:xenial LABEL maintainer "sheyinsong@unotes.co" EXPOSE 5900 6099 ENV \ DEBIAN_FRONTEND="nonintractive" \ X11VNC_PASSWORD="password" RUN apt-get update -y RUN apt-get install -y xvfb x11vnc python libglib2.0-0 ADD ./get-pip.py /opt/get-pip.py RUN python /opt/get-pip.py ADD ./pygame-1.9.6-cp27-cp27mu-manylinux1_x86_64.whl /opt/pygame-1.9.6-cp27-cp27mu-manylinux1_x86_64.whl RUN pip install /opt/pygame-1.9.6-cp27-cp27mu-manylinux1_x86_64.whl ADD ./start.sh /opt/start.sh ENTRYPOINT /opt/start.sh ``` #### 文件start.sh ```bash #!/bin/sh Xvfb :1 -screen 0 800x600x16 & sleep 0.1 /usr/bin/x11vnc -display :1.0 -passwd ${X11VNC_PASSWORD:-password} & sleep 0.1 DISPLAY=:1.0 export DISPLAY cd /run_game timeout 3600 python main.py ``` &emsp; ### 构建noVNC镜像(用于html访问pygaem(docker)中的GUI界面) #### DockerFile ```yaml FROM python MAINTAINER sys "sheyinsong@unotes.co" RUN apt-get update -y RUN apt-get upgrade -y RUN apt-get install -y python-numpy RUN apt-get clean RUN pip install redis RUN pip install simplejson ADD ./noVNC/ /noVNC/ CMD ["python", "/noVNC/utils/websockify/run", "--web", "/noVNC", "0.0.0.0:10240", "--target-config", "/noVNC/token.list"] ``` &emsp; #### noVNC下载链接 https://github.com/novnc/noVNC/releases
阅读 1540 评论 0 收藏 0
阅读 1540
评论 0
收藏 0

第 5 页 / 共 11 页
 
第 5 页 / 共 11 页