V1.2 增加创建分享链接功能

This commit is contained in:
kingmo888 2023-12-14 12:11:19 +08:00
parent 0f116462f6
commit 66a65b7923
13 changed files with 325 additions and 62 deletions

View File

@ -3,7 +3,7 @@
<p align="center">
<i>一个 python 实现的 Rustdesk API 接口,支持 WebUI 管理</i>
<br/>
<img src ="https://img.shields.io/badge/Version-1.1-blueviolet.svg"/>
<img src ="https://img.shields.io/badge/Version-1.2-blueviolet.svg"/>
<img src ="https://img.shields.io/badge/Python-3.7|3.8|3.9|3.10|3.11-blue.svg" />
<img src ="https://img.shields.io/badge/Django-3.2+|4.x-yelow.svg" />
<br/>
@ -137,7 +137,7 @@ services:
## 开发计划
- [ ] 分享设备给其他已注册用户
- [-] 分享设备给其他已注册用户
> 说明类似网盘url分享url激活后可以获得某个或某组或某个标签下的设备
> 备注其实web api作为中间件可做的不多更多功能还是需要修改客户端来实现就不太值当了。

View File

@ -93,6 +93,7 @@ admin.site.register(models.RustDeskToken, models.RustDeskTokenAdmin)
admin.site.register(models.RustDeskTag, models.RustDeskTagAdmin)
admin.site.register(models.RustDeskPeer, models.RustDeskPeerAdmin)
admin.site.register(models.RustDesDevice, models.RustDesDeviceAdmin)
admin.site.register(models.ShareLink, models.ShareLinkAdmin)
admin.site.unregister(Group)
admin.site.site_header = 'RustDesk自建Web'
admin.site.site_title = '未定义'

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.7 on 2023-12-04 21:05
# Generated by Django 4.2.7 on 2023-12-14 12:08
from django.db import migrations, models
@ -138,6 +138,34 @@ class Migration(migrations.Migration):
"ordering": ("-username",),
},
),
migrations.CreateModel(
name="ShareLink",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("uid", models.CharField(max_length=16, verbose_name="用户ID")),
("shash", models.CharField(max_length=60, verbose_name="链接Key")),
("peers", models.CharField(max_length=20, verbose_name="机器ID列表")),
("is_used", models.BooleanField(default=False, verbose_name="是否使用")),
("is_expired", models.BooleanField(default=False, verbose_name="是否过期")),
(
"create_time",
models.DateTimeField(auto_now_add=True, verbose_name="生成时间"),
),
],
options={
"verbose_name": "分享链接",
"verbose_name_plural": "链接列表",
"ordering": ("-create_time",),
},
),
migrations.CreateModel(
name="UserProfile",
fields=[

View File

@ -87,4 +87,28 @@ class RustDesDeviceAdmin(admin.ModelAdmin):
list_display = ('rid', 'hostname', 'memory', 'uuid', 'version', 'create_time', 'update_time')
search_fields = ('hostname', 'memory')
list_filter = ('rid', )
class ShareLink(models.Model):
''' 分享链接
'''
uid = models.CharField(verbose_name='用户ID', max_length=16)
shash = models.CharField(verbose_name='链接Key', max_length=60)
peers = models.CharField(verbose_name='机器ID列表', max_length=20)
is_used = models.BooleanField(verbose_name='是否使用', default=False)
is_expired = models.BooleanField(verbose_name='是否过期', default=False)
create_time = models.DateTimeField(verbose_name='生成时间', auto_now_add=True)
class Meta:
ordering = ('-create_time',)
verbose_name = "分享链接"
verbose_name_plural = "链接列表"
class ShareLinkAdmin(admin.ModelAdmin):
list_display = ('shash', 'uid', 'peers', 'is_used', 'is_expired', 'create_time')
search_fields = ('peers', )
list_filter = ('is_used', 'uid', 'is_expired' )

44
api/templates/base.html Normal file
View File

@ -0,0 +1,44 @@
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="{% static 'layui/css/layui.css' %}">
{% block link %}{% endblock %}
</head>
<body>
<script src={% static "layui/layui.js" %}></script>
<script>
layui.use('element', function(){
var element = layui.element; //导航的hover效果二级菜单等功能需要依赖element模块
//监听导航点击
element.on('nav(demo)', function(elem){
//console.log(elem)
layer.msg(elem.text());
});
});
</script>
<ul class="layui-nav">
<li class="layui-nav-item"><a href="/">首页</a></li>
<li class="layui-nav-item"><a href="/api/share">分享</a></li>
{% if u.is_admin %}
<li class="layui-nav-item"><a href="/admin">管理后台</a>
</li>
{% endif %}
</ul>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
<legend>{% block legend_name %}{% endblock %}</legend>
</fieldset>
{% block content %}{% endblock %}
</body>
</html>

12
api/templates/msg.html Normal file
View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}{{title}}{% endblock %}
{% block legend_name %}信息{% endblock %}
{% block content %}
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
{% autoescape off %}
{{msg}}
{% endautoescape %}
</div></div>
{% endblock %}

105
api/templates/share.html Normal file
View File

@ -0,0 +1,105 @@
{% extends "base.html" %}{% load static %}
{% block title %}分享机器{% endblock %}
{% block link %}<link rel="stylesheet" href="{% static 'layui/css/style.css' %}">{% endblock %}
{% block legend_name %}分享机器给其他用户{% endblock %}
{% block content %}
<div class="layui-container">
<div class="layui-card layui-col-md3-offset2">
<div class="layui-card-header">请将要分享的机器调整到右侧</div>
<div id="showdevice"></div>
<button id="create" type="button" class="layui-btn padding-5" lay-on="getData">生成分享链接</button>
</div>
<div class="layui-card">1链接有效期为15分钟切勿随意分享给他人</div>
<div class="layui-card">2所分享的机器被分享人享有相同的权限如果机器设置了保存密码被分享人也可以直接连接</div>
<div class="layui-card">3为保障安全链接有效期为15分钟链接仅有效1次链接一旦被非分享人的登录用户访问分享生效后续访问链接失效</div>
<div class="layui-card layui-col-md6-offset1">
<table class="layui-table">
<colgroup>
<col width="30">
<col width="100">
<col width="300">
<col>
</colgroup>
<thead>
<tr>
<th>链接地址</th>
<th>创建时间</th>
<th>ID列表</th>
</tr>
</thead>
<tbody>
{% for one in sharelinks %}
<tr>
<td>{{one.shash}} </td>
<td>{{one.create_time}} </td>
<td>{{one.peers}} </td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script>
layui.use(['transfer', 'jquery', 'layer'], function(){
var transfer = layui.transfer;
var $ = layui.jquery;
var layer = layui.layer;
//渲染
transfer.render({
elem: '#showdevice' //绑定元素
,title: ['我的机器', '分享机器'] //自定义标题
//,width: 500 //定义宽度
//,height: 300 //定义高度
,data: [//定义数据源
{%for peer in peers %}
{"value": "{{peer.id}}", "title": "{{peer.name}}"},
{%endfor%}
] //disabled 是否禁用 checked 是否选中
,id: 'device' //定义索引 重新加载reload或者获取右侧数据时可以用到
});
$("#create_bak").click(function(){
var getData = transfer.getData('device');
alert(JSON.stringify(getData));
});
$("#create").click(function(){
var getData = transfer.getData('device');
$.ajax({
url:'/api/share',
type:'post',
dataType:'json',
data:{
data:JSON.stringify(getData),
},
success:function(data){
if (data.code == 1) {
// var myMsg = layer.msg('处理中', {
// shade: 0.4,
// time:false //取消自动关闭
// });
//layer.msg('注册成功,请前往登录页登录。');
layer.alert('成功!如需分享,请复制以下链接给其他人:<br>'+ window.location + '/' +data.shash, function (index) {
location.reload();});
}else {
layer.msg(data.msg);
}
}
});
});
});
</script>
{% endblock %}

View File

@ -1,46 +1,7 @@
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>RustDesk WebUI</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="{% static 'layui/css/layui.css' %}">
</head>
<body>
<script src={% static "layui/layui.js" %}></script>
<script>
layui.use('element', function(){
var element = layui.element; //导航的hover效果二级菜单等功能需要依赖element模块
//监听导航点击
element.on('nav(demo)', function(elem){
//console.log(elem)
layer.msg(elem.text());
});
});
</script>
<ul class="layui-nav">
<li class="layui-nav-item"><a href="/">首页</a></li>
{% if u.is_admin %}
<li class="layui-nav-item"><a href="/admin">管理后台</a>
</li>
{% endif %}
</ul>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
<legend>综合屏</legend>
</fieldset>
{% extends "base.html" %}
{% block title %}RustDesk WebUI{% endblock %}
{% block legend_name %}综合屏{% endblock %}
{% block content %}
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
<div class="layui-col-md15">
@ -154,5 +115,4 @@ layui.use('element', function(){
</div></div>
</body>
</html>
{% endblock %}

View File

@ -18,4 +18,5 @@ urlpatterns = [
#url(r'^register',views.register),
url(r'^user_action',views.user_action), # 前端
url(r'^work',views.work), # 前端
url(r'^share',views.share), # 前端
]

View File

@ -11,17 +11,7 @@ from django.db.models import Q
import copy
from .views_front import *
salt = 'xiaomo'
EFFECTIVE_SECONDS = 7200
def getStrMd5(s):
if not isinstance(s, (str,)):
s = str(s)
myHash = hashlib.md5()
myHash.update(s.encode())
return myHash.hexdigest()
def login(request):
result = {}
@ -45,7 +35,7 @@ def login(request):
user.rid = rid
user.uuid = uuid
user.autoLogin = autoLogin
user.rtype = rtype
user.rtype = rtype
user.deviceInfo = json.dumps(deviceInfo)
user.save()

View File

@ -6,7 +6,7 @@ from django.http import JsonResponse
from django.db.models import Q
from django.contrib.auth.decorators import login_required
from django.contrib import auth
from api.models import RustDeskPeer, RustDesDevice, UserProfile
from api.models import RustDeskPeer, RustDesDevice, UserProfile, ShareLink
from django.forms.models import model_to_dict
from itertools import chain
@ -14,7 +14,20 @@ from django.db.models.fields import DateTimeField, DateField, CharField, TextFie
import datetime
from django.db.models import Model
import json
import time
import hashlib
salt = 'xiaomo'
EFFECTIVE_SECONDS = 7200
def getStrMd5(s):
if not isinstance(s, (str,)):
s = str(s)
myHash = hashlib.md5()
myHash.update(s.encode())
return myHash.hexdigest()
def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=None):
"""
@ -87,7 +100,8 @@ def model_to_dict2(instance, fields=None, exclude=None, replace=None, default=No
def index(request):
#return render(request, 'login3.html', {'info':''})
if request.user and request.user.username!='AnonymousUser':
return HttpResponseRedirect('/api/work')
return HttpResponseRedirect('/api/user_action?action=login')
@ -199,3 +213,87 @@ def work(request):
print(all_info)
return render(request, 'show_work.html', {'single_info':single_info, 'all_info':all_info, 'u':u})
def check_sharelink_expired(sharelink):
now = datetime.datetime.now()
if sharelink.create_time > now:
return False
if (now - sharelink.create_time).seconds <15 * 60:
return False
else:
sharelink.is_expired = True
sharelink.save()
return True
@login_required(login_url='/api/user_action?action=login')
def share(request):
peers = RustDeskPeer.objects.filter(Q(uid=request.user.id))
sharelinks = ShareLink.objects.filter(Q(uid=request.user.id) & Q(is_used=False) & Q(is_expired=False))
# 省资源:处理已过期请求,不主动定时任务轮询请求,在任意地方请求时,检查是否过期,过期则保存。
now = datetime.datetime.now()
for sl in sharelinks:
check_sharelink_expired(sl)
sharelinks = ShareLink.objects.filter(Q(uid=request.user.id) & Q(is_used=False) & Q(is_expired=False))
peers = [{'id':ix+1, 'name':f'{p.rid}|{p.alias}'} for ix, p in enumerate(peers)]
sharelinks = [{'shash':s.shash, 'is_used':s.is_used, 'is_expired':s.is_expired, 'create_time':s.create_time, 'peers':s.peers} for ix, s in enumerate(sharelinks)]
if request.method == 'GET':
url = request.build_absolute_uri()
if url.endswith('share'):
return render(request, 'share.html', {'peers':peers, 'sharelinks':sharelinks})
else:
shash = url.split('/')[-1]
sharelink = ShareLink.objects.filter(Q(shash=shash))
msg = ''
title = '成功'
if not sharelink:
title = '错误'
msg = f'链接{url}:<br>分享链接不存在或已失效。'
else:
sharelink = sharelink[0]
if str(request.user.id) == str(sharelink.uid):
title = '错误'
msg = f'链接{url}:<br><br>咱就说,你不能把链接分享给自己吧?!'
else:
sharelink.is_used = True
sharelink.save()
peers = sharelink.peers
peers = peers.split(',')
# 自己的peers若重叠需要跳过
peers_self_ids = [x.rid for x in RustDeskPeer.objects.filter(Q(uid=request.user.id))]
peers_share = RustDeskPeer.objects.filter(rid__in=peers)
peers_share_ids = [x.rid for x in peers_share]
for peer in peers_share:
if peer.rid in peers_self_ids:
continue
peer = RustDeskPeer.objects.get(rid=peer.rid)
peer.id = None
peer.uid = request.user.id
peer.save()
msg += f"{peer.rid},"
msg += '已被成功获取。'
return render(request, 'msg.html', {'title':msg, 'msg':msg})
else:
data = request.POST.get('data', '[]')
data = json.loads(data)
if not data:
return JsonResponse({'code':0, 'msg':'数据为空。'})
rustdesk_ids = [x['title'].split('|')[0] for x in data]
rustdesk_ids = ','.join(rustdesk_ids)
sharelink = ShareLink(
uid=request.user.id,
shash = getStrMd5(str(time.time())+salt),
peers=rustdesk_ids,
)
sharelink.save()
return JsonResponse({'code':1, 'shash':sharelink.shash})

BIN
db.sqlite3_bak Normal file

Binary file not shown.

Binary file not shown.