### 流程:
- 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展示
 
### 上传代码和保存代码
#### 上传功能代码
视图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})
```
 
模板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>
```
 
### 保存功能代码
#### 视图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+'"}'})
```
 
#### 模板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");
});
});
```
 
### 生成访问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)
```
 
#### 生成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+'"}')
```
 
### 构建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
```
 
### 构建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"]
```
 
#### noVNC下载链接
https://github.com/novnc/noVNC/releases