first commit

This commit is contained in:
lihansani
2026-07-13 15:37:05 +08:00
commit 8b11aa2efc
299 changed files with 206339 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
# 初始化基类
from fuadmin import settings
class CoreInitialize:
"""
使用方法:继承此类,重写 run方法在 run 中调用 save 进行数据初始化
"""
creator_id = None
reset = False
def __init__(self, reset=False, creator_id=None):
"""
reset: 是否重置初始化数据
creator_id: 创建人id
"""
self.reset = reset or self.reset
self.creator_id = creator_id or self.creator_id
def save(self, obj, data: list, name=None, no_reset=False):
name = name or obj._meta.verbose_name
print(f"正在初始化[{obj._meta.label} => {name}]")
if not no_reset and self.reset and obj not in settings.INITIALIZE_RESET_LIST:
try:
obj.objects.all().delete()
settings.INITIALIZE_RESET_LIST.append(obj)
except Exception:
pass
for ele in data:
m2m_dict = {}
new_data = {}
for key, value in ele.items():
# 判断传的 value 为 list 的多对多进行抽离使用set 进行更新
if isinstance(value, list):
m2m_dict[key] = value
else:
new_data[key] = value
object, _ = obj.objects.get_or_create(id=ele.get("id"), defaults=new_data)
for key, m2m in m2m_dict.items():
m2m = list(set(m2m))
if m2m and len(m2m) > 0 and m2m[0]:
exec(f"""
if object.{key}:
values_list = object.{key}.all().values_list('id', flat=True)
values_list = list(set(list(values_list) + {m2m}))
object.{key}.set(values_list)
""")
print(f"初始化完成[{obj._meta.label} => {name}]")
def run(self):
raise NotImplementedError('.run() must be overridden')

113
code/utils/fu_auth.py Normal file
View File

@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/2 23:19
# @Author : 臧成龙
# @FileName: fu_auth.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
import re
from datetime import datetime
# from django.core.cache import cache
from fuadmin.settings import DEMO, SECRET_KEY, WHITE_LIST
from ninja.security import HttpBearer
from system.models import MenuButton, Users
from .fu_jwt import FuJwt
from .fu_ninja import FuFilters
from .usual import get_dept, get_user_info_from_token
METHOD = {
'GET': 0,
'POST': 1,
'PUT': 2,
'DELETE': 3,
}
class GlobalAuth(HttpBearer):
def authenticate(self, request, token):
# 直接放行所有请求
return token
# jwt = FuJwt(SECRET_KEY)
# value = jwt.decode(SECRET_KEY, token)
# time_now = int(datetime.now().timestamp())
# # 判断token是否过期
# if value.valid_to >= time_now:
# token_user = value.payload
# token_user_id = token_user['id']
# user = Users.objects.get(id=token_user_id)
# request_path = request.path
# request_method = request.method
# if DEMO:
# # 判断是否在白名单中
# if request_path in WHITE_LIST:
# return token
# if request_method == 'GET':
# return token
# else:
# raise TimeoutError(403, '演示环境')
# else:
# # 判断是否是超级管理员
# if not token_user['is_superuser']:
# # 判断是path是否是/数字’结尾
# result = re.search(r'/\d+$', request_path)
# if result:
# match_value = result.group()
# # 将数字结尾的接口替换成.*? 因为接口中是/{id}
# request_path = request_path.replace(match_value, '/*')
# # 判断是否在白名单中
# if request_path in WHITE_LIST:
# return token
# else:
# menuIds = user.role.values_list('permission__id', flat=True)
# queryset = MenuButton.objects.filter(id__in=menuIds, api__regex=request_path,
# method=METHOD[request_method])
# if queryset.exists():
# return token
# else:
# raise TimeoutError(403, '没有权限')
# # cache_token = cache.get(token_user_id)
# # if token == cache_token:
# return token
# else:
# raise TimeoutError(401, 'token时间过期')
def data_permission(request, filters: FuFilters):
return filters # 直接返回不进行任何权限过滤
# user_info = get_user_info_from_token(request)
# if user_info['is_superuser']:
# return filters
# user = Users.objects.get(id=user_info['id'])
# data_range_qs = user.role.values_list('data_range', flat=True)
# dept_ids = user.role.values_list('dept__id', flat=True)
# # 如果有多个角色,取数据权限最大的角色
# data_range = max(list(data_range_qs))
# # 仅本人数据权限
# if data_range == 0:
# filters.creator_id = user_info['id']
# # 本部门数据权限
# if data_range == 1:
# filters.belong_dept = user_info['dept']
# # 本部门及以下数据权限
# if data_range == 2:
# dept_and_below_ids = get_dept(user_info['dept'])
# filters.belong_dept__in = dept_and_below_ids
# # 自定义数据权限
# if data_range == 3:
# filters.belong_dept__in = list(dept_ids)
# # 所有数据权限
# if data_range == 4:
# pass
# return filters

242
code/utils/fu_crud.py Normal file
View File

@@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/6 14:22
# @Author : 臧成龙
# @FileName: usual.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
import os
from datetime import datetime
from urllib.parse import unquote
import openpyxl
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from fuadmin.settings import BASE_DIR, STATIC_URL
from ninja import Schema
from openpyxl import load_workbook
from .fu_auth import data_permission
from .fu_ninja import FuFilters
from .fu_response import FuResponse
from .usual import get_user_info_from_token
class ImportSchema(Schema):
path: str
def create(request, data, model):
"""
创建新记录的函数。
参数:
- request: 请求对象,用于获取用户信息。
- data: 创建记录的数据可以是字典或者支持dict()方法的对象。
- model: Django ORM模型指定要创建的对象类型。
返回值:
- 创建成功的对象查询集合。
"""
if not isinstance(data, dict):
# 如果data不是字典类型则转换为字典
data = data.dict()
user_info = get_user_info_from_token(request)
# 从请求中提取用户信息,并添加到数据中作为创建人、修改者和所属部门信息
data['creator_id'] = user_info['id']
data['modifier'] = user_info['name']
data['belong_dept'] = user_info['dept']
# 使用提供的模型和数据创建新记录
query_set = model.objects.create(**data)
return query_set
def batch_create(request, data, model):
"""
批量创建模型实例。
参数:
- request: HTTP请求对象用于获取用户信息。
- data: 一个包含创建数据的列表每个元素可以是字典或者具有dict方法的对象。
- model: Django模型类用于实例化和批量创建。
返回值:
- query_set: 批量创建后的模型实例查询集。
"""
user_info = get_user_info_from_token(request) # 从请求中获取用户信息
data_list = []
for item in data:
if not isinstance(item, dict):
item = item.dict() # 如果item不是字典则转换为字典
# 为每个创建的数据项添加默认的创建人、修改者和所属部门信息
item['creator_id'] = user_info['id']
item['modifier'] = user_info['name']
item['belong_dept'] = user_info['dept']
data_list.append(model(**item)) # 根据字典内容实例化模型对象并添加到列表中
query_set = model.objects.bulk_create(data_list) # 批量创建模型实例
return query_set
def delete(id, model):
"""
根据提供的ID和模型删除对象实例。
参数:
- id: 要删除的对象的ID。
- model: 对象所属的模型类。
返回值:
- 无返回值。
"""
# 根据ID和模型获取对象实例如果找不到则返回404错误页面
instance = get_object_or_404(model, id=id)
# 删除对象实例
instance.delete()
pass
def update(request, id, data, model):
"""
更新给定模型实例的数据。
参数:
- request: HTTP请求对象用于获取用户信息。
- id: 要更新的模型实例的ID。
- data: 包含更新数据的实例,应能转换为字典格式。
- model: 要更新的模型类。
返回值:
- 更新后的模型实例。
"""
dict_data = data.dict() # 将data转换为字典格式
user_info = get_user_info_from_token(request) # 从请求中获取用户信息
# 为更新的数据添加修改者信息
dict_data['modifier'] = user_info['name']
instance = get_object_or_404(model, id=id) # 获取指定ID的模型实例
# 遍历字典,将更新的数据设置到模型实例上
for attr, value in dict_data.items():
setattr(instance, attr, value)
instance.save() # 保存更新
return instance # 返回更新后的实例
def retrieve(request, model, filters: FuFilters = FuFilters()):
"""
根据提供的过滤条件从数据库中检索模型实例。
参数:
- request: HttpRequest对象用于获取请求信息。
- model: Django模型类指定要检索的数据模型。
- filters: FuFilters类的实例包含过滤条件。默认为FuFilters(),即无条件过滤。
返回值:
- query_set: 一个Django QuerySet对象包含根据过滤条件检索到的模型实例。
"""
# 根据请求和过滤条件应用数据权限控制
filters = data_permission(request, filters)
if filters is not None:
# 将filters中的空字符串值转换为None
for attr, value in filters.__dict__.items():
if getattr(filters, attr) == '':
setattr(filters, attr, None)
# 使用过滤条件查询模型实例
query_set = model.objects.filter(**filters.dict(exclude_none=True))
else:
# 如果没有有效的过滤条件,则返回所有模型实例
query_set = model.objects.all()
return query_set
def export_data(request, model, scheme, export_fields):
"""
导出数据为Excel文件。
参数:
- request: HttpRequest对象表示客户端请求。
- model: Django模型类指定要导出数据的模型。
- scheme: 表示数据转换规则的对象用于将ORM对象转换为字典。
- export_fields: 包含要导出的字段名的列表。
返回值:
- FileResponse对象提供下载Excel文件。
"""
title_dict = {}
# 根据export_fields列表获取字段的显示名称
for field in export_fields:
field_obj = getattr(model, field).field
title_dict[field] = field_obj.help_text
qs = retrieve(request, model)
list_data = []
# 将查询集中的每一项转换为指定格式的字典
for qs_item in qs:
qs_item = scheme.from_orm(qs_item)
dict_data = {}
for item, value in title_dict.items():
dict_data[value] = getattr(qs_item, item)
list_data.append(dict_data)
# 创建并初始化Excel工作簿
wb = openpyxl.Workbook()
ws = wb.active
# 向Excel中写入数据
for index, data in enumerate(list_data):
if index == 0:
# 写入表头
ws.append(list(data.keys()))
ws.append(list(data.values()))
# 生成唯一的文件名并设置文件路径
file_name = datetime.now().strftime('%Y%m%d%H%M%S%f') + '.xlsx'
current_ymd = datetime.now().strftime('%Y%m%d')
file_path = os.path.join(STATIC_URL, current_ymd)
file_url = os.path.join(file_path, file_name)
# 确保导出文件的目录存在
if not os.path.exists(file_path):
os.makedirs(file_path)
# 保存Excel文件到指定路径
wb.save(file_url)
# 返回供下载的文件响应
return FileResponse(open(file_url, "rb"), as_attachment=True)
def import_data(request, model, scheme, data, import_fields):
"""
导入数据到指定模型
参数:
- request: HttpRequest对象表示客户端请求
- model: Django模型类数据将被导入到这个模型
- scheme: 一个函数,用于根据给定的数据字典创建模型实例
- data: 包含要导入文件信息的对象比如上传的Excel文件
- import_fields: 一个列表,指定模型中需要导入的字段名
返回值:
- FuResponse对象包含导入结果的消息
"""
title_dict = {} # 字段名与Excel列对应的字典
for field in import_fields:
field_obj = getattr(model, field).field
title_dict[field_obj.help_text] = field_obj.column
# 文件路径处理
file_path = str(BASE_DIR) + unquote(data.path)
# 加载Excel工作簿
wb = load_workbook(file_path)
ws = wb.active # 获取活动工作表
title_value = []
for index_row, row in enumerate(ws.values):
if index_row == 0:
title_value = row # 读取Excel表头
else:
dict_data = {} # 存储每一行数据转换后的字典
for index, cell in enumerate(row):
title_cell = title_value[index]
value = title_dict.get(title_cell) # 根据表头查找对应字段
if value is not None:
dict_data[value] = cell
print(dict_data) # 打印处理后的数据,用于调试
data = scheme(**dict_data) # 根据处理后的字典创建模型实例
create(request, data, model) # 在数据库中创建模型实例
return FuResponse(msg='导入成功') # 返回成功消息

58
code/utils/fu_jwt.py Normal file
View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/14 16:19
# @Author : 臧成龙
# @FileName: fu_jwt.py
# @Software: PyCharm
from typing import Union
from simplejwt import util
from simplejwt.jwt import default_alg, _hash, Jwt
import datetime
import json
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
else:
return json.JSONEncoder.default(self, obj)
class FuJwt(Jwt):
def encode(self) -> str:
payload = {}
payload.update(self.registered_claims)
payload.update(self.payload)
return encode(self.secret, payload, self.alg, self.header)
def encode(secret: Union[str, bytes], payload: dict = None, alg: str = default_alg, header: dict = None) -> str:
"""
:param secret: The secret used to encode the token.
:type secret: Union[str, bytes]
:param payload: The payload to be encoded in the token.
:type payload: dict
:param alg: The algorithm used to hash the token.
:type alg: str
:param header: The header to be encoded in the token.
:type header: dict
:return: A new token
:rtype: str
"""
secret = util.to_bytes(secret)
payload = payload or {}
header = header or {}
header_json = util.to_bytes(json.dumps(header))
header_b64 = util.b64_encode(header_json)
payload_json = util.to_bytes(json.dumps(payload, cls=DateEncoder))
payload_b64 = util.b64_encode(payload_json)
pre_signature = util.join(header_b64, payload_b64)
signature = _hash(secret, pre_signature, alg)
signature_b64 = util.b64_encode(signature)
token = util.join(pre_signature, signature_b64)
return util.from_bytes(token)

66
code/utils/fu_ninja.py Normal file
View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/13 23:41
# @Author : 臧成龙
# @FileName: fu_ninja.py
# @Software: PyCharm
from typing import Any, List
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse
from ninja import Field, ModelSchema, NinjaAPI, Query, Router, Schema
from ninja.orm.metaclass import ModelSchemaMetaclass
from ninja.pagination import PaginationBase
from ninja.types import DictStrAny
from .fu_response import FuResponse
from .usual import get_user_info_from_token
class FuNinjaAPI(NinjaAPI):
def create_response(
self, request: HttpRequest, data: Any, *, status: int = 200, code: int = 2000, msg: str = "success",
temporal_response: HttpResponse = None,
) -> HttpResponse:
std_data = {
"code": code,
"result": data,
"message": msg,
"success": True
}
content = self.renderer.render(request, std_data, response_status=status)
content_type = "{}; charset={}".format(
self.renderer.media_type, self.renderer.charset
)
return HttpResponse(content, status=status, content_type=content_type)
class MyPagination(PaginationBase):
class Input(Schema):
pageSize: int = Field(10, gt=0)
page: int = Field(1, gt=-1)
class Output(Schema):
items: List[Any]
total: int
def paginate_queryset(
self,
queryset: QuerySet,
pagination: Input,
**params: DictStrAny,
) -> Any:
offset = pagination.pageSize * (pagination.page - 1)
limit: int = pagination.pageSize
return {
"page": offset,
"limit": limit,
"items": queryset[offset: offset + limit],
"total": self._items_count(queryset),
} # noqa: E203
class FuFilters(Schema):
creator_id: int = Field(None, alias="creator_id")
belong_dept: int = Field(None, alias="belong_dept")
belong_dept__in: List[int] = Field(None, alias="belong_dept__in")

42
code/utils/fu_response.py Normal file
View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# @Time : 2022/5/14 15:27
# @Author : 臧成龙
# @FileName: fu_response.py.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
import json
from django.http import HttpResponse
from .fu_jwt import DateEncoder
# class JsonResponse(HttpResponse):
#
# def __init__(
# self,
# data,
# safe=True,
# **kwargs,
# ):
# if safe and not isinstance(data, dict):
# raise TypeError(
# "In order to allow non-dict objects to be serialized set the "
# "safe parameter to False."
# )
# kwargs.setdefault("content_type", "application/json")
# data = json.dumps(data, cls=DateEncoder)
# super().__init__(content=data, **kwargs)
class FuResponse(HttpResponse):
def __init__(self, data=None, msg='success', code=2000, *args, **kwargs):
std_data = {
"code": code,
"result": data,
"message": msg,
"success": True
}
data = json.dumps(std_data, cls=DateEncoder)
super().__init__(data, *args, **kwargs)

View File

@@ -0,0 +1,76 @@
# Author 臧成龙
# coding=utf-8
# @Time : 2022/5/16 21:09
# @File : list_to_tree.py
# @Software: PyCharm
# @qq: 939589097
def add_node(p, node):
# ⼦节点list
p["children"] = []
for n in node:
if n.get("parent_id") == p.get("id"):
p["children"].append(n)
# 递归⼦节点,查找⼦节点的节点
for t in p["children"]:
if not t.get("children"):
t["children"] = []
t["children"].append(add_node(t, node))
# 退出递归的条件
if len(p["children"]) == 0:
p.pop('children')
p["choice"] = 1
return
def list_to_route(data):
root = []
node = []
# 初始化数据获取根节点和其他子节点list
for d in data:
d['meta'] = {
'title': d.pop('title'),
'ignoreKeepAlive': d.pop('keepalive'),
'orderNo': d.pop('sort'),
'hideMenu': d.pop('hide_menu'),
'icon': d.pop('icon')
}
d["choice"] = 0
if d.get("parent_id") is None:
root.append(d)
else:
node.append(d)
# print("root----",root)
# print("node----",node)
# 查找子节点
for p in root:
add_node(p, node)
# 无子节点
if len(root) == 0:
return node
return root
def list_to_tree(data):
root = []
node = []
# 初始化数据获取根节点和其他子节点list
for d in data:
d["choice"] = 0
if d.get("parent_id") is None:
root.append(d)
else:
node.append(d)
# print("root----",root)
# print("node----",node)
# 查找子节点
for p in root:
add_node(p, node)
# 无子节点
if len(root) == 0:
return node
return root

99
code/utils/middleware.py Normal file
View File

@@ -0,0 +1,99 @@
"""
日志 django中间件
"""
import json
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.utils.deprecation import MiddlewareMixin
from system.models import OperationLog, Users
from .request_util import (
get_browser,
get_os,
get_request_data,
get_request_ip,
get_request_path,
get_request_user,
get_verbose_name,
)
class ApiLoggingMiddleware(MiddlewareMixin):
"""
用于记录API访问日志中间件
"""
def __init__(self, get_response=None):
super().__init__(get_response)
self.enable = getattr(settings, 'API_LOG_ENABLE', None) or False
self.methods = getattr(settings, 'API_LOG_METHODS', None) or set()
self.operation_log_id = None
@classmethod
def __handle_request(cls, request):
request.request_ip = get_request_ip(request)
request.request_data = get_request_data(request)
request.request_path = get_request_path(request)
def __handle_response(self, request, response):
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
body = getattr(request, 'request_data', {})
# 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数)
if isinstance(body, dict) and body.get('password', ''):
body['password'] = '*' * len(body['password'])
if not hasattr(response, 'data') or not isinstance(response.data, dict):
response.data = {}
try:
if not response.data and response.content:
content = json.loads(response.content.decode())
response.data = content if isinstance(content, dict) else {}
except Exception:
return
user = get_request_user(request)
if isinstance(user, AnonymousUser):
return
info = {
'request_username': user.username if isinstance(user, Users) else user['username'],
'request_ip': getattr(request, 'request_ip', 'unknown'),
'creator_id': user.id if isinstance(user, Users) else user['id'],
'belong_dept': user.dept_id if isinstance(user, Users) else user['dept'],
'request_method': request.method,
'request_path': request.request_path,
'request_body': body,
'response_code': response.data.get('code'),
'request_os': get_os(request),
'request_browser': get_browser(request),
'request_msg': request.session.get('request_msg'),
'status': True if response.data.get('code') in [2000, ] else False,
'json_result': {"code": response.data.get('code'), "msg": response.data.get('result')},
}
operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=self.operation_log_id)
if not operation_log.request_modular and settings.API_MODEL_MAP.get(request.request_path, None):
operation_log.request_modular = settings.API_MODEL_MAP[request.request_path]
operation_log.save()
def process_view(self, request, view_func, view_args, view_kwargs):
if hasattr(view_func, 'cls') and hasattr(view_func.cls, 'queryset'):
if self.enable:
if self.methods == 'ALL' or request.method in self.methods:
log = OperationLog(request_modular=get_verbose_name(view_func.cls.queryset))
log.save()
self.operation_log_id = log.id
return
def process_request(self, request):
self.__handle_request(request)
def process_response(self, request, response):
"""
主要请求处理完之后记录
:param request:
:param response:
:return:
"""
if self.enable:
if self.methods == 'ALL' or request.method in self.methods:
self.__handle_response(request, response)
return response

62
code/utils/models.py Normal file
View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/5/31 031 22:08
@Remark: 公共基础model类
"""
import uuid
from django.apps import apps
from django.db import models
from fuadmin import settings
class CoreModel(models.Model):
"""
核心标准抽象模型模型,可直接继承使用
增加审计字段, 覆盖字段时, 字段名称请勿修改, 必须统一审计字段名称
"""
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
remark = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
belong_dept = models.IntegerField(help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
verbose_name="创建时间")
sort = models.IntegerField(default=1, null=True, blank=True, verbose_name="显示排序", help_text="显示排序")
class Meta:
abstract = True
verbose_name = '核心模型'
verbose_name_plural = verbose_name
def get_all_models_objects(model_name=None):
"""
获取所有 models 对象
:return: {}
"""
settings.ALL_MODELS_OBJECTS = {}
if not settings.ALL_MODELS_OBJECTS:
all_models = apps.get_models()
for item in list(all_models):
table = {
"tableName": item._meta.verbose_name,
"table": item.__name__,
"tableFields": []
}
for field in item._meta.fields:
fields = {
"title": field.verbose_name,
"field": field.name
}
table['tableFields'].append(fields)
settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
if model_name:
return settings.ALL_MODELS_OBJECTS[model_name] or {}
return settings.ALL_MODELS_OBJECTS or {}

1
code/utils/pca-code.json Normal file

File diff suppressed because one or more lines are too long

216
code/utils/request_util.py Normal file
View File

@@ -0,0 +1,216 @@
"""
Request工具类
"""
import json
import requests
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
from django.urls.resolvers import ResolverMatch
from system.models import LoginLog
from user_agents import parse
from .usual import get_user_info_from_token
def get_request_user(request):
"""
获取请求user
(1)如果request里的user没有认证,那么则手动认证一次
:param request:
:return:
"""
user: AbstractBaseUser = getattr(request, 'user', None)
if user and user.is_authenticated:
return user
try:
user = get_user_info_from_token(request)
except Exception as e:
pass
return user or AnonymousUser()
def get_request_ip(request):
"""
获取请求IP
:param request:
:return:
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[-1].strip()
return ip
ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
return ip or 'unknown'
def get_request_data(request):
"""
获取请求参数
:param request:
:return:
"""
request_data = getattr(request, 'request_data', None)
if request_data:
return request_data
data: dict = {**request.GET.dict(), **request.POST.dict()}
if not data:
try:
body = request.body
if body:
data = json.loads(body)
except Exception as e:
pass
if not isinstance(data, dict):
data = {'data': data}
return data
def get_request_path(request, *args, **kwargs):
"""
获取请求路径
:param request:
:param args:
:param kwargs:
:return:
"""
request_path = getattr(request, 'request_path', None)
if request_path:
return request_path
values = []
for arg in args:
if len(arg) == 0:
continue
if isinstance(arg, str):
values.append(arg)
elif isinstance(arg, (tuple, set, list)):
values.extend(arg)
elif isinstance(arg, dict):
values.extend(arg.values())
if len(values) == 0:
return request.path
path: str = request.path
for value in values:
path = path.replace('/' + value, '/' + '{id}')
return path
def get_request_canonical_path(request, ):
"""
获取请求路径
:param request:
:param args:
:param kwargs:
:return:
"""
request_path = getattr(request, 'request_canonical_path', None)
if request_path:
return request_path
path: str = request.path
resolver_match: ResolverMatch = request.resolver_match
for value in resolver_match.args:
path = path.replace(f"/{value}", "/{id}")
for key, value in resolver_match.kwargs.items():
if key == 'pk':
path = path.replace(f"/{value}", f"/{{id}}")
continue
path = path.replace(f"/{value}", f"/{{{key}}}")
return path
def get_browser(request, ):
"""
获取浏览器名
:param request:
:param args:
:param kwargs:
:return:
"""
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
return user_agent.get_browser()
def get_os(request, ):
"""
获取操作系统
:param request:
:param args:
:param kwargs:
:return:
"""
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
return user_agent.get_os()
def get_verbose_name(queryset=None, view=None, model=None):
"""
获取 verbose_name
:param request:
:param view:
:return:
"""
try:
if queryset and hasattr(queryset, 'model'):
model = queryset.model
elif view and hasattr(view.get_queryset(), 'model'):
model = view.get_queryset().model
elif view and hasattr(view.get_serializer(), 'Meta') and hasattr(view.get_serializer().Meta, 'model'):
model = view.get_serializer().Meta.model
if model:
return getattr(model, '_meta').verbose_name
else:
model = queryset.model._meta.verbose_name
except Exception as e:
pass
return model if model else ""
def get_ip_analysis(ip):
"""
获取ip详细概略
:param ip: ip地址
:return:
"""
data = {
"continent": "",
"country": "",
"province": "",
"city": "",
"district": "",
"isp": "",
"area_code": "",
"country_english": "",
"country_code": "",
"longitude": "",
"latitude": ""
}
if ip != 'unknown' and ip:
if getattr(settings, 'ENABLE_LOGIN_ANALYSIS_LOG', True):
res = requests.get(url='https://ip.django-vue-admin.com/ip/analysis', params={"ip": ip}, verify=False)
if res.status_code == 200:
res_data = res.json()
if res_data.get('code') == 0:
data = res_data.get('data')
return data
return data
def save_login_log(request):
"""
保存登录日志
:return:
"""
ip = get_request_ip(request=request)
analysis_data = get_ip_analysis(ip)
analysis_data['username'] = request.user.username
analysis_data['ip'] = ip
analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT']))
analysis_data['browser'] = get_browser(request)
analysis_data['os'] = get_os(request)
analysis_data['creator_id'] = request.user.id
analysis_data['belong_dept'] = getattr(request.user, 'dept_id', '')
LoginLog.objects.create(**analysis_data)

27
code/utils/ru_convert.py Normal file
View File

@@ -0,0 +1,27 @@
import re
class RuleConvert:
"""
命名规则转换 Tips大小驼峰及下划线互转
@descript 大驼峰: 首字母大写其余每一个逻辑断点(单词)都用大写字母标记,同帕斯卡命名法
@descript 小驼峰: 首字母小写其余每一个逻辑断点(单词)都用大写字母标记
@descript 下划线: 逻辑断点(单词)用的是下划线隔开
"""
@staticmethod
def to_underline(x):
"""转下划线命名"""
return re.sub('(?<=[a-z])[A-Z]|(?<!^)[A-Z](?=[a-z])', '_\g<0>', x).lower()
@staticmethod
def to_upper_camel_case(x):
"""转大驼峰法命名"""
s = re.sub('_([a-zA-Z])', lambda m: (m.group(1).upper()), x.lower())
return s[0].upper() + s[1:]
@staticmethod
def to_lower_camel_case(x):
"""转小驼峰法命名"""
s = re.sub('_([a-zA-Z])', lambda m: (m.group(1).upper()), x.lower())
return s[0].lower() + s[1:]

529
code/utils/server/linux.py Normal file
View File

@@ -0,0 +1,529 @@
#!/bin/python
# coding: utf-8
# +-------------------------------------------------------------------
# | django-vue3-lyadmin
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# ------------------------------
# linux系统命令工具类封装
# ------------------------------
import os, sys, re, time, json
import psutil
from django.core.cache import cache
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
PUBLIC_DICT = os.path.join(BASE_DIR, 'public.json')
def Md5(strings):
"""
@name 生成MD5
@author hwliang<hwl@bt.cn>
@param strings 要被处理的字符串
@return string(32)
"""
if type(strings) != bytes:
strings = strings.encode()
import hashlib
m = hashlib.md5()
m.update(strings)
return m.hexdigest()
def md5(strings):
return Md5(strings)
def get_preexec_fn(run_user):
'''
@name 获取指定执行用户预处理函数
@author hwliang<2021-08-19>
@param run_user<string> 运行用户
@return 预处理函数
'''
import pwd
pid = pwd.getpwnam(run_user)
uid = pid.pw_uid
gid = pid.pw_gid
def _exec_rn():
os.setgid(gid)
os.setuid(uid)
return _exec_rn
def get_error_info():
import traceback
errorMsg = traceback.format_exc()
return errorMsg
def ExecShell(cmdstring, timeout=None, shell=True, cwd=None, env=None, user=None):
'''
@name 执行命令
@author hwliang<2021-08-19>
@param cmdstring 命令 [必传]
@param timeout 超时时间
@param shell 是否通过shell运行
@param cwd 进入的目录
@param env 环境变量
@param user 执行用户名
@return 命令执行结果
'''
a = ''
e = ''
import subprocess, tempfile
preexec_fn = None
tmp_dir = '/dev/shm'
if user:
preexec_fn = get_preexec_fn(user)
tmp_dir = '/tmp'
try:
rx = md5(cmdstring)
succ_f = tempfile.SpooledTemporaryFile(max_size=4096, mode='wb+', suffix='_succ', prefix='btex_' + rx,
dir=tmp_dir)
err_f = tempfile.SpooledTemporaryFile(max_size=4096, mode='wb+', suffix='_err', prefix='btex_' + rx,
dir=tmp_dir)
sub = subprocess.Popen(cmdstring, close_fds=True, shell=shell, bufsize=128, stdout=succ_f, stderr=err_f,
cwd=cwd, env=env, preexec_fn=preexec_fn)
if timeout:
s = 0
d = 0.01
while sub.poll() == None:
time.sleep(d)
s += d
if s >= timeout:
if not err_f.closed: err_f.close()
if not succ_f.closed: succ_f.close()
return 'Timed out'
else:
sub.wait()
err_f.seek(0)
succ_f.seek(0)
a = succ_f.read()
e = err_f.read()
if not err_f.closed: err_f.close()
if not succ_f.closed: succ_f.close()
except:
return '', get_error_info()
try:
# 编码修正
if type(a) == bytes: a = a.decode('utf-8')
if type(e) == bytes: e = e.decode('utf-8')
except:
a = str(a)
e = str(e)
return a, e
def ReadFile(filename, mode='r'):
"""
读取文件内容
@filename 文件名
return string(bin) 若文件不存在则返回None
"""
import os
if not os.path.exists(filename): return False
fp = None
try:
fp = open(filename, mode)
f_body = fp.read()
except Exception as ex:
if sys.version_info[0] != 2:
try:
fp = open(filename, mode, encoding="utf-8")
f_body = fp.read()
except:
fp = open(filename, mode, encoding="GBK")
f_body = fp.read()
else:
return False
finally:
if fp and not fp.closed:
fp.close()
return f_body
def readFile(filename, mode='r'):
'''
@name 读取指定文件数据
@author hwliang<2021-06-09>
@param filename<string> 文件名
@param mode<string> 文件打开模式默认r
@return string or bytes or False 如果返回False则说明读取失败
'''
return ReadFile(filename, mode)
# xss 防御
def xsssec(text):
return text.replace('&', '&amp;').replace('"', '&quot;').replace('<', '&lt;').replace('>', '&gt;')
def get_os_version():
'''
@name 取操作系统版本
@author hwliang<2021-08-07>
@return string
'''
p_file = '/etc/.productinfo'
if os.path.exists(p_file):
s_tmp = readFile(p_file).split("\n")
if s_tmp[0].find('Kylin') != -1 and len(s_tmp) > 1:
version = s_tmp[0] + ' ' + s_tmp[1].split('/')[0].strip()
else:
version = readFile('/etc/redhat-release')
if not version:
version = readFile('/etc/issue').strip().split("\n")[0].replace('\\n', '').replace('\l', '').strip()
else:
version = version.replace('release ', '').replace('Linux', '').replace('(Core)', '').strip()
v_info = sys.version_info
try:
version = "{} {}(Py{}.{}.{})".format(version, os.uname().machine, v_info.major, v_info.minor, v_info.micro)
except:
version = "{} (Py{}.{}.{})".format(version, v_info.major, v_info.minor, v_info.micro)
return xsssec(version)
def GetSystemVersion():
# 取操作系统版本
key = 'lybbn_sys_version'
version = cache.get(key)
if version: return version
version = get_os_version()
cache.set(key, version, 10000)
return version
def GetLoadAverage():
try:
c = os.getloadavg()
except:
c = [0, 0, 0]
data = {}
data['one'] = float(c[0])
data['five'] = float(c[1])
data['fifteen'] = float(c[2])
data['max'] = psutil.cpu_count() * 2
data['limit'] = data['max']
data['safe'] = data['max'] * 0.75
temppercent = round(data['one'] / data['max'] * 100)
data['percent'] = 100 if temppercent > 100 else temppercent
return data
# 取内存信息
def GetMemInfo():
mem = psutil.virtual_memory()
memInfo = {}
# memInfo['percent'] = mem.percent
memInfo2 = {'memTotal': int(mem.total / 1024 / 1024), 'memFree': int(mem.free / 1024 / 1024),
'memBuffers': int(mem.buffers / 1024 / 1024), 'memCached': int(mem.cached / 1024 / 1024)}
memInfo['total'] = round(float(mem.total) / 1024 / 1024 / 1024, 2)
memInfo['free'] = round((memInfo2['memFree'] + memInfo2['memBuffers'] + memInfo2['memCached']) / 1024, 2)
memInfo['used'] = round(float(mem.used) / 1024 / 1024 / 1024, 2)
memInfo['percent'] = round((int(mem.used) / 1024 / 1024) / memInfo2['memTotal'] * 100, 1)
# memInfo['realUsed'] = round((memInfo2['memTotal'] - memInfo2['memFree'] - memInfo2['memBuffers'] - memInfo2['memCached']) / 1024,2) # memRealUsed
return memInfo
# 获取磁盘IO开销数据
def get_disk_iostat():
iokey = 'iostat'
diskio = cache.get(iokey)
mtime = int(time.time())
if not diskio:
diskio = {}
diskio['info'] = None
diskio['time'] = mtime
diskio_1 = diskio['info']
stime = mtime - diskio['time']
if not stime: stime = 1
diskInfo = {}
diskInfo['ALL'] = {}
diskInfo['ALL']['read_count'] = 0
diskInfo['ALL']['write_count'] = 0
diskInfo['ALL']['read_bytes'] = 0
diskInfo['ALL']['write_bytes'] = 0
diskInfo['ALL']['read_time'] = 0
diskInfo['ALL']['write_time'] = 0
diskInfo['ALL']['read_merged_count'] = 0
diskInfo['ALL']['write_merged_count'] = 0
try:
if os.path.exists('/proc/diskstats'):
diskio_2 = psutil.disk_io_counters(perdisk=True)
if not diskio_1:
diskio_1 = diskio_2
for disk_name in diskio_2.keys():
diskInfo[disk_name] = {}
diskInfo[disk_name]['read_count'] = int(
(diskio_2[disk_name].read_count - diskio_1[disk_name].read_count) / stime)
diskInfo[disk_name]['write_count'] = int(
(diskio_2[disk_name].write_count - diskio_1[disk_name].write_count) / stime)
diskInfo[disk_name]['read_bytes'] = int(
(diskio_2[disk_name].read_bytes - diskio_1[disk_name].read_bytes) / stime)
diskInfo[disk_name]['write_bytes'] = int(
(diskio_2[disk_name].write_bytes - diskio_1[disk_name].write_bytes) / stime)
diskInfo[disk_name]['read_time'] = int(
(diskio_2[disk_name].read_time - diskio_1[disk_name].read_time) / stime)
diskInfo[disk_name]['write_time'] = int(
(diskio_2[disk_name].write_time - diskio_1[disk_name].write_time) / stime)
diskInfo[disk_name]['read_merged_count'] = int(
(diskio_2[disk_name].read_merged_count - diskio_1[disk_name].read_merged_count) / stime)
diskInfo[disk_name]['write_merged_count'] = int(
(diskio_2[disk_name].write_merged_count - diskio_1[disk_name].write_merged_count) / stime)
diskInfo['ALL']['read_count'] += diskInfo[disk_name]['read_count']
diskInfo['ALL']['write_count'] += diskInfo[disk_name]['write_count']
diskInfo['ALL']['read_bytes'] += diskInfo[disk_name]['read_bytes']
diskInfo['ALL']['write_bytes'] += diskInfo[disk_name]['write_bytes']
if diskInfo['ALL']['read_time'] < diskInfo[disk_name]['read_time']:
diskInfo['ALL']['read_time'] = diskInfo[disk_name]['read_time']
if diskInfo['ALL']['write_time'] < diskInfo[disk_name]['write_time']:
diskInfo['ALL']['write_time'] = diskInfo[disk_name]['write_time']
diskInfo['ALL']['read_merged_count'] += diskInfo[disk_name]['read_merged_count']
diskInfo['ALL']['write_merged_count'] += diskInfo[disk_name]['write_merged_count']
cache.set(iokey, {'info': diskio_2, 'time': mtime})
except:
return diskInfo
return diskInfo
# 获取网卡数据
def GetNetWork():
cache_timeout = 86400
otime = cache.get("lybbn_otime")
ntime = time.time()
networkInfo = {}
networkInfo['network'] = {}
networkInfo['upTotal'] = 0
networkInfo['downTotal'] = 0
networkInfo['up'] = 0
networkInfo['down'] = 0
networkInfo['downPackets'] = 0
networkInfo['upPackets'] = 0
networkIo_list = psutil.net_io_counters(pernic=True)
for net_key in networkIo_list.keys():
networkIo = networkIo_list[net_key][:4]
up_key = "{}_up".format(net_key)
down_key = "{}_down".format(net_key)
otime_key = "lybbn_otime"
if not otime:
otime = time.time()
cache.set(up_key, networkIo[0], cache_timeout)
cache.set(down_key, networkIo[1], cache_timeout)
cache.set(otime_key, otime, cache_timeout)
networkInfo['network'][net_key] = {}
up = cache.get(up_key)
down = cache.get(down_key)
if not up:
up = networkIo[0]
if not down:
down = networkIo[1]
networkInfo['network'][net_key]['upTotal'] = networkIo[0]
networkInfo['network'][net_key]['downTotal'] = networkIo[1]
networkInfo['network'][net_key]['up'] = round(float(networkIo[0] - up) / 1024 / (ntime - otime), 2)
networkInfo['network'][net_key]['down'] = round(float(networkIo[1] - down) / 1024 / (ntime - otime), 2)
networkInfo['network'][net_key]['downPackets'] = networkIo[3]
networkInfo['network'][net_key]['upPackets'] = networkIo[2]
networkInfo['upTotal'] += networkInfo['network'][net_key]['upTotal']
networkInfo['downTotal'] += networkInfo['network'][net_key]['downTotal']
networkInfo['up'] += networkInfo['network'][net_key]['up']
networkInfo['down'] += networkInfo['network'][net_key]['down']
networkInfo['downPackets'] += networkInfo['network'][net_key]['downPackets']
networkInfo['upPackets'] += networkInfo['network'][net_key]['upPackets']
cache.set(up_key, networkIo[0], cache_timeout)
cache.set(down_key, networkIo[1], cache_timeout)
cache.set(otime_key, time.time(), cache_timeout)
networkInfo['up'] = round(float(networkInfo['up']), 2)
networkInfo['down'] = round(float(networkInfo['down']), 2)
networkInfo['iostat'] = get_disk_iostat()
return networkInfo
# 取系统启动时间
def GetBootTime():
key = 'lybbn_sys_time'
sys_time = cache.get(key)
if sys_time: return sys_time
import math
conf = readFile('/proc/uptime').split()
tStr = float(conf[0])
min = tStr / 60
hours = min / 60
days = math.floor(hours / 24)
hours = math.floor(hours - (days * 24))
min = math.floor(min - (days * 60 * 24) - (hours * 60))
sys_time = "{}".format(int(days))
cache.set(key, sys_time, 1800)
return sys_time
# 取CPU类型
def getCpuType():
cpuinfo = open('/proc/cpuinfo', 'r').read()
rep = "model\s+name\s+:\s+(.+)"
tmp = re.search(rep, cpuinfo, re.I)
cpuType = ''
if tmp:
cpuType = tmp.groups()[0]
else:
cpuinfo = ExecShell('LANG="en_US.UTF-8" && lscpu')[0]
rep = "Model\s+name:\s+(.+)"
tmp = re.search(rep, cpuinfo, re.I)
if tmp: cpuType = tmp.groups()[0]
return cpuType
# 取CPU信息
def GetCpuInfo(interval=1):
# 取CPU信息
cpuCount = psutil.cpu_count()
cpuNum = psutil.cpu_count(logical=False)
c_tmp = readFile('/proc/cpuinfo')
d_tmp = re.findall("physical id.+", c_tmp)
cpuW = len(set(d_tmp))
import threading
p = threading.Thread(target=get_cpu_percent_thead, args=(interval,))
# p.setDaemon(True)
p.start()
used = cache.get('lybbn_cpu_used_all')
if not used: used = get_cpu_percent_thead(interval)
used_all = psutil.cpu_percent(percpu=True)
cpu_name = getCpuType() + " * {}".format(cpuW)
return used, cpuCount, used_all, cpu_name, cpuNum, cpuW
def get_cpu_percent_thead(interval):
used = psutil.cpu_percent(interval)
cache.set('lybbn_cpu_used_all', used, 10)
return used
def get_cpu_time():
cpu_time = 0.00
cpu_times = psutil.cpu_times()
for s in cpu_times: cpu_time += s
return cpu_time
def GetDiskInfo(human=True):
# 取磁盘分区信息
key = 'lybbn_sys_disk'
diskInfo = cache.get(key)
if diskInfo: return diskInfo
if human:
temp = ExecShell("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")[0]
else:
temp = ExecShell("df -T -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")[0]
tempInodes = ExecShell("df -i -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")[0]
temp1 = temp.split('\n')
tempInodes1 = tempInodes.split('\n')
diskInfo = []
n = 0
cuts = ['/mnt/cdrom', '/boot', '/boot/efi', '/dev', '/dev/shm', '/run/lock', '/run', '/run/shm', '/run/user']
for tmp in temp1:
n += 1
try:
inodes = tempInodes1[n - 1].split()
disk = re.findall(
r"^(.+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\d%]{2,4})\s+(/.{0,100})$", tmp.strip())
if disk: disk = disk[0]
if len(disk) < 6: continue
if disk[2].find('M') != -1: continue
if disk[2].find('K') != -1: continue
if len(disk[6].split('/')) > 10: continue
if disk[6] in cuts: continue
if disk[6].find('docker') != -1: continue
if disk[1].strip() in ['tmpfs']: continue
arr = {}
arr['filesystem'] = disk[0].strip()
arr['type'] = disk[1].strip()
arr['path'] = disk[6].replace('/usr/local/lighthouse/softwares/btpanel', '/www')
tmp1 = [disk[2], disk[3], disk[4], disk[5].split('%')[0]]
arr['size'] = tmp1
arr['inodes'] = [inodes[1], inodes[2], inodes[3], inodes[4]]
diskInfo.append(arr)
except Exception as ex:
continue
cache.set(key, diskInfo, 10)
return diskInfo
def GetMsg(key, args=()):
"""
根据key获取内置消息返回
@key 指定消息的key
@args 消息内容中的参数
"""
try:
log_message = json.loads(ReadFile(PUBLIC_DICT));
keys = log_message.keys();
msg = None;
if key in keys:
msg = log_message[key];
for i in range(len(args)):
rep = '{' + str(i + 1) + '}'
msg = msg.replace(rep, args[i]);
return msg;
except:
return key
def getMsg(key, args=()):
return GetMsg(key, args)
def ReturnMsg(status, msg, args=()):
"""
@name 取通用dict返回
@author hwliang<hwl@bt.cn>
@param status 返回状态
@param msg 返回消息
@return dict {"status":bool,"msg":string}
"""
log_message = json.loads(ReadFile(PUBLIC_DICT))
keys = log_message.keys()
if type(msg) == str:
if msg in keys:
msg = log_message[msg]
for i in range(len(args)):
rep = '{' + str(i + 1) + '}'
msg = msg.replace(rep, args[i])
return {'status': status, 'msg': msg}
def returnMsg(status, msg, args=()):
"""
@name 取通用dict返回
@author hwliang<hwl@bt.cn>
@param status 返回状态
@param msg 返回消息
@return dict {"status":bool,"msg":string}
"""
return ReturnMsg(status, msg, args)
def RestartServer():
ExecShell("sync && init 6 &")
return returnMsg(True, 'SYS_REBOOT')

View File

@@ -0,0 +1,12 @@
{
"ERROR": "操作失败",
"SUCCESS": "操作成功",
"START": "启动",
"STOP": "停止",
"OFF": "停用",
"ON": "启用",
"OPEN": "打开",
"CLOSE": "关闭",
"SYS_EXEC_SUCCESS": "执行成功!",
"SYS_REBOOT": "命令发送成功!"
}

View File

@@ -0,0 +1,78 @@
#!/bin/python
#coding: utf-8
# +-------------------------------------------------------------------
# | django-vue3-lyadmin
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# ------------------------------
# 系统命令封装
# ------------------------------
import platform
plat = platform.system().lower()
if plat == 'windows':
import windows as myos
else:
import linux as myos
class system:
isWindows = False
def __init__(self):
self.isWindows = self.isWindows()
def isWindows(self):
plat = platform.system().lower()
if plat == 'windows':
return True
return False
def GetSystemAllInfo(self,isCache=False):
"""
获取系统所有信息
"""
data = {}
data['mem'] = self.GetMemInfo()
data['load_average'] = self.GetLoadAverage()
data['network'] = self.GetNetWork()
data['cpu'] = self.GetCpuInfo(1)
data['disk'] = self.GetDiskInfo()
data['time'] = self.GetBootTime()
data['system'] = self.GetSystemVersion()
data['is_windows'] = self.isWindows
return data
def GetMemInfo(self):
memInfo = myos.GetMemInfo()
return memInfo
def GetLoadAverage(self):
data = myos.GetLoadAverage()
return data
def GetNetWork(self):
data = myos.GetNetWork()
return data
def GetCpuInfo(self,interval=1):
data = myos.GetCpuInfo(interval)
return data
def GetBootTime(self):
data = myos.GetBootTime()
return data
def GetDiskInfo(self):
data = myos.GetDiskInfo()
return data
def GetSystemVersion(self):
data = myos.GetSystemVersion()
return data

View File

@@ -0,0 +1,818 @@
#!/bin/python
# coding: utf-8
# +-------------------------------------------------------------------
# | django-vue3-lyadmin
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# ------------------------------
# windows系统命令工具类封装
# ------------------------------
import platform
import os, time, re, json, socket
import psutil
import winreg
from random import Random
from django.core.cache import cache
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
PUBLIC_DICT = os.path.join(BASE_DIR, 'public.json')
def ReadFile(filename, mode='r'):
"""
读取文件内容
@filename 文件名
return string(bin) 若文件不存在则返回None
"""
import os, chardet
if not os.path.exists(filename): return False
if not os.path.isfile(filename): return False
f_body = '';
try:
fp = open(filename, mode)
f_body = fp.read()
except:
try:
fp.close()
except:
pass
try:
encoding = 'utf8'
fp = open(filename, mode, encoding=encoding)
f_body = fp.read()
except:
try:
fp.close()
except:
pass
try:
encoding = 'gbk'
fp = open(filename, mode, encoding=encoding)
f_body = fp.read()
except:
try:
fp.close()
except:
pass
encoding = 'ansi'
fp = open(filename, mode, encoding=encoding)
f_body = fp.read()
try:
if f_body[0] == '\ufeff':
# 处理带bom格式
new_code = chardet.detect(f_body.encode(encoding))["encoding"]
f_body = f_body.encode(encoding).decode(new_code);
except:
pass
fp.close()
return f_body
def readFile(filename, mode='r'):
return ReadFile(filename, mode)
def ReadReg(path, key):
"""
读取注册表
@path 注册表路径
@key 注册表键值
"""
import winreg
try:
newKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
value, type = winreg.QueryValueEx(newKey, key)
return value
except:
return False
def DelReg(path, key):
"""
删除注册表
@path 注册表路径
@key 注册表键值
"""
import winreg
try:
newKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
winreg.DeleteKey(newKey, key)
return True
except:
return False
def WriteReg(path, key, value, type=winreg.REG_SZ):
"""
写入/创建注册表
@path 注册表路径
@key 注册表键值
@value 注册表值
@type 注册表值类型
"""
import winreg
try:
newKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_ALL_ACCESS)
except:
newKey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, path)
try:
# SetValue 只支持字符串其他类型使用SetValueEx
if isinstance(value, int):
winreg.SetValueEx(newKey, key, 0, winreg.REG_DWORD, value)
else:
winreg.SetValueEx(newKey, key, 0, type, value)
return True
except Exception as ex:
return False
def get_mac_address():
"""
获取mac
"""
import uuid
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
return ":".join([mac[e:e + 2] for e in range(0, 11, 2)])
def process_exists(pname, exe=None):
"""
检查进程是否存在
@pname 进程名称
@exe 进程路径
"""
try:
pids = psutil.pids()
for pid in pids:
try:
p = psutil.Process(pid)
if p.name() == pname:
if not exe:
return True;
else:
if p.exe() == exe: return True
except:
pass
return False
except:
return True
def GetRandomString(length):
"""
@name 取随机字符串
@author hwliang<hwl@bt.cn>
@param length 要获取的长度
@return string(length)
"""
strings = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
chrlen = len(chars) - 1
random = Random()
for i in range(length):
strings += chars[random.randint(0, chrlen)]
return strings
def Md5(strings):
"""
@name 生成MD5
@author hwliang<hwl@bt.cn>
@param strings 要被处理的字符串
@return string(32)
"""
if type(strings) != bytes:
strings = strings.encode()
import hashlib
m = hashlib.md5()
m.update(strings)
return m.hexdigest()
def md5(strings):
return Md5(strings)
def ExecShell(cmdstring, cwd=None, timeout=None, shell=True):
"""
通过管道执行SHELL
@cmdstring 需要执行的cmd命令
@cwd 设置工作目录
@timeout 超时时间
@shell 是否通过shell输出
@output 是否通过文件重定向输出结果(当结果数据量过大必须使用此参数,否则造成管道阻塞)
"""
import shlex
import datetime
import subprocess
import time, tempfile
a = ''
e = ''
try:
rx = md5(cmdstring)
succ_f = tempfile.SpooledTemporaryFile(max_size=4096000, mode='wb+', suffix='_succ', prefix='btex_' + rx)
err_f = tempfile.SpooledTemporaryFile(max_size=4096000, mode='wb+', suffix='_err', prefix='btex_' + rx)
sub = subprocess.Popen(cmdstring, shell=shell, bufsize=128, stdout=succ_f, stderr=err_f)
sub.wait()
err_f.seek(0)
succ_f.seek(0)
a = succ_f.read()
e = err_f.read()
if not err_f.closed: err_f.close()
if not succ_f.closed: succ_f.close()
except:
pass
if type(a) == bytes:
try:
a = a.decode('utf-8')
except:
try:
a = a.decode('gb2312')
except:
try:
a = a.decode('utf-16')
except:
a = a.decode('gb2312', 'ignore')
if type(e) == bytes:
try:
e = e.decode('utf-8')
except:
try:
a = a.decode('utf-16')
except:
e = e.decode('gb2312', 'ignore')
return a, e
class ExcepError(Exception):
'''
通用异常对像
'''
def __init__(self, value):
self.value = value
def __str__(self):
return ("运行时发生错误: {}".format(repr(self.value)))
def exists_args(args, get):
'''
@name 检查参数是否存在
@author hwliang<2021-06-08>
@param args<list or str> 参数列表 允许是列表或字符串
@param get<dict_obj> 参数对像
@return bool 都存在返回True否则抛出KeyError异常
'''
if type(args) == str:
args = args.split(',')
for arg in args:
if not arg in get:
raise KeyError('缺少必要参数: {}'.format(arg))
return True
# xss 防御
def xssencode(text):
from html import escape, unescape
list = ['`', '~', '&', '#', '*', '$', '@', '<', '>', '\"', '\'', ';', '%', ',', '\\u']
ret = []
for i in text:
if i in list:
i = ''
ret.append(i)
str_convert = ''.join(ret)
text2 = escape(str_convert, quote=True)
return text2
def path_safe_check(path, force=True):
"""
校验路径安全
@path 校验路径
@force
"""
if len(path) > 256: return False
checks = ['..', './', '\\', '%', '$', '^', '&', '*', '~', '#', '"', "'", ';', '|', '{', '}', '`']
for c in checks:
if path.find(c) != -1: return False
if force:
rep = r"^[\w\s\.\/-]+$"
if not re.match(rep, path): return False
return True
def is_ipv4(ip):
"""
判断是否IPV4地址
@ip ip地址
return bool
"""
try:
socket.inet_pton(socket.AF_INET, ip)
except AttributeError:
try:
socket.inet_aton(ip)
except socket.error:
return False
return ip.count('.') == 3
except socket.error:
return False
return True
def is_ipv6(ip):
"""
判断是否IPV6地址
@ip ip地址
return bool
"""
try:
socket.inet_pton(socket.AF_INET6, ip)
except socket.error:
return False
return True
def to_size(size):
"""
字节单位转换
@size 字节大小
return 返回带单位的格式(如1 GB)
"""
if not size: return '0.00 b'
size = float(size)
d = ('b', 'KB', 'MB', 'GB', 'TB');
s = d[0];
for b in d:
if size < 1024: return ("%.2f" % size) + ' ' + b;
size = size / 1024;
s = b;
return ("%.2f" % size) + ' ' + b;
# 取通用对象
re_key_match = re.compile(r'^[\w\s\[\]\-]+$')
re_key_match2 = re.compile(r'^\.?__[\w\s[\]\-]+__\.?$')
key_filter_list = ['get', 'set', 'get_items', 'exists', '__contains__', '__setitem__', '__getitem__', '__delitem__',
'__delattr__', '__setattr__', '__getattr__', '__class__']
class dict_obj:
def __contains__(self, key):
return getattr(self, key, None)
def __setitem__(self, key, value):
if key in key_filter_list:
raise ExcepError("错误的字段名")
if not re_key_match.match(key) or re_key_match2.match(key):
raise ExcepError("错误的字段名")
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key, None)
def __delitem__(self, key):
delattr(self, key)
def __delattr__(self, key):
delattr(self, key)
def get_items(self):
return self
def exists(self, keys):
return exists_args(keys, self)
def set(self, key, value):
if not isinstance(value, str) or not isinstance(key, str): return False
if key in key_filter_list:
raise ExcepError("错误的字段名")
if not re_key_match.match(key) or re_key_match2.match(key):
raise ExcepError("错误的字段名")
return setattr(self, key, value)
def get(self, key, default='', format='', limit=[]):
'''
@name 获取指定参数
@param key<string> 参数名称,允许在/后面限制参数格式,请参考参数值格式(format)
@param default<string> 默认值,默认空字符串
@param format<string> 参数值格式(int|str|port|float|json|xss|path|url|ip|ipv4|ipv6|letter|mail|phone|正则表达式|>1|<1|=1),默认为空
@param limit<list> 限制参数值内容
@param return mixed
'''
if key.find('/') != -1:
key, format = key.split('/')
result = getattr(self, key, default)
if isinstance(result, str): result = result.strip()
if format:
if format in ['str', 'string', 's']:
result = str(result)
elif format in ['int', 'd']:
try:
result = int(result)
except:
raise ValueError("参数:{}要求int类型数据".format(key))
elif format in ['float', 'f']:
try:
result = float(result)
except:
raise ValueError("参数:{}要求float类型数据".format(key))
elif format in ['json', 'j']:
try:
result = json.loads(result)
except:
raise ValueError("参数:{}, 要求JSON字符串".format(key))
elif format in ['xss', 'x']:
result = xssencode(result)
elif format in ['path', 'p']:
if not path_safe_check(result):
raise ValueError("参数:{},要求正确的路径格式".format(key))
result = result.replace('//', '/')
elif format in ['url', 'u']:
regex = re.compile(
r'^(?:http|ftp)s?://'
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
r'localhost|'
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
r'(?::\d+)?'
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
if not re.match(regex, result):
raise ValueError('参数:{}要求正确的URL格式'.format(key))
elif format in ['ip', 'ipaddr', 'i', 'ipv4', 'ipv6']:
if format == 'ipv4':
if not is_ipv4(result):
raise ValueError('参数:{}要求正确的ipv4地址'.format(key))
elif format == 'ipv6':
if not is_ipv6(result):
raise ValueError('参数:{}要求正确的ipv6地址'.format(key))
else:
if not is_ipv4(result) and not is_ipv6(result):
raise ValueError('参数:{}要求正确的ipv4/ipv6地址'.format(key))
elif format in ['w', 'letter']:
if not re.match(r'^\w+$', result):
raise ValueError('参数:{},要求只能是英文字母或数据组成'.format(key))
elif format in ['email', 'mail', 'm']:
if not re.match(r"^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$)", result):
raise ValueError("参数:{},要求正确的邮箱地址格式".format(key))
elif format in ['phone', 'mobile', 'p']:
if not re.match("^[0-9]{11,11}$", result):
raise ValueError("参数:{},要求手机号码格式".format(key))
elif format in ['port']:
result_port = int(result)
if result_port > 65535 or result_port < 0:
raise ValueError("参数:{}要求端口号为0-65535".format(key))
result = result_port
elif re.match(r"^[<>=]\d+$", result):
operator = format[0]
length = int(format[1:].strip())
result_len = len(result)
error_obj = ValueError("参数:{},要求长度为{}".format(key, format))
if operator == '=':
if result_len != length:
raise error_obj
elif operator == '>':
if result_len < length:
raise error_obj
else:
if result_len > length:
raise error_obj
elif format[0] in ['^', '(', '[', '\\', '.'] or format[-1] in ['$', ')', ']', '+', '}']:
if not re.match(format, result):
raise ValueError("指定参数格式不正确, {}:{}".format(key, format))
if limit:
if not result in limit:
raise ValueError("指定参数值范围不正确, {}:{}".format(key, limit))
return result
def is_64bitos():
"""
判断是否x64系统(windows)
"""
bites = {'AMD64': 64, 'x86_64': 64, 'i386': 32, 'x86': 32}
info = platform.uname()
if bites.get(info.machine) == 64:
return True
return False
def get_registry_value(key, subkey, value):
"""
读取注册表信息
@key 注册表类型
@subkey 注册表路径
@value 注册表具体key值
"""
key = getattr(winreg, key)
handle = winreg.OpenKey(key, subkey)
(value, type) = winreg.QueryValueEx(handle, value)
return value
def GetSystemVersion():
"""
取操作系统版本
"""
try:
key = 'lybbn_sys_version'
version = cache.get(key)
if version: return version
bit = 'x86';
if is_64bitos(): bit = 'x64'
def get(key):
return get_registry_value("HKEY_LOCAL_MACHINE", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", key)
os = get("ProductName")
build = get("CurrentBuildNumber")
version = "%s (build %s) %s (Py%s)" % (os, build, bit, platform.python_version())
cache.set(key, version, 10000)
return version
except Exception as ex:
version = "未知系统版本."
cache.set(key, version, 10000)
return version
# 取负载信息
def GetLoadAverage():
data = {}
data['one'] = 0
data['five'] = 0
data['fifteen'] = 0
data['max'] = psutil.cpu_count() * 2
data['limit'] = data['max']
data['safe'] = data['max'] * 0.75
data['percent'] = 0
return data
# 取内存信息
def GetMemInfo():
mem = psutil.virtual_memory()
memInfo = {}
memInfo['percent'] = mem.percent
memInfo['total'] = round(float(mem.total) / 1024 / 1024 / 1024, 2)
memInfo['free'] = round(float(mem.free) / 1024 / 1024 / 1024, 2)
memInfo['used'] = round(float(mem.used) / 1024 / 1024 / 1024, 2)
return memInfo
# 获取网卡信息
def GetNetWork():
cache_timeout = 86400
otime = cache.get("otime")
ntime = time.time()
networkInfo = {}
networkInfo['network'] = {}
networkInfo['upTotal'] = 0
networkInfo['downTotal'] = 0
networkInfo['up'] = 0
networkInfo['down'] = 0
networkInfo['downPackets'] = 0
networkInfo['upPackets'] = 0
networkIo_list = psutil.net_io_counters(pernic=True)
for net_key in networkIo_list.keys():
if net_key.find('Loopback') >= 0 or net_key.find('Teredo') >= 0 or net_key.find('isatap') >= 0: continue
networkIo = networkIo_list[net_key][:4]
up_key = "{}_up".format(net_key)
down_key = "{}_down".format(net_key)
otime_key = "otime"
if not otime:
otime = time.time()
cache.set(up_key, networkIo[0], cache_timeout)
cache.set(down_key, networkIo[1], cache_timeout)
cache.set(otime_key, otime, cache_timeout)
networkInfo['network'][net_key] = {}
up = cache.get(up_key)
down = cache.get(down_key)
if not up:
up = networkIo[0]
if not down:
down = networkIo[1]
networkInfo['network'][net_key]['upTotal'] = networkIo[0]
networkInfo['network'][net_key]['downTotal'] = networkIo[1]
try:
networkInfo['network'][net_key]['up'] = round(float(networkIo[0] - up) / 1024 / (ntime - otime), 2)
networkInfo['network'][net_key]['down'] = round(float(networkIo[1] - down) / 1024 / (ntime - otime), 2)
except:
networkInfo['up'] = 0
networkInfo['down'] = 0
networkInfo['network'][net_key]['up'] = 0
networkInfo['network'][net_key]['down'] = 0
networkInfo['network'][net_key]['downPackets'] = networkIo[3]
networkInfo['network'][net_key]['upPackets'] = networkIo[2]
networkInfo['upTotal'] += networkInfo['network'][net_key]['upTotal']
networkInfo['downTotal'] += networkInfo['network'][net_key]['downTotal']
networkInfo['up'] += networkInfo['network'][net_key]['up']
networkInfo['down'] += networkInfo['network'][net_key]['down']
networkInfo['downPackets'] += networkInfo['network'][net_key]['downPackets']
networkInfo['upPackets'] += networkInfo['network'][net_key]['upPackets']
cache.set(up_key, networkIo[0], cache_timeout)
cache.set(down_key, networkIo[1], cache_timeout)
cache.set(otime_key, time.time(), cache_timeout)
networkInfo['up'] = round(float(networkInfo['up']), 2)
networkInfo['down'] = round(float(networkInfo['down']), 2)
networkInfo['iostat'] = {}
return networkInfo
# 取系统启动时间
def GetBootTime():
key = 'lybbn_sys_time'
sys_time = cache.get(key)
if sys_time: return sys_time
import math
tStr = time.time() - psutil.boot_time()
min = tStr / 60;
hours = min / 60;
days = math.floor(hours / 24);
hours = math.floor(hours - (days * 24));
min = math.floor(min - (days * 60 * 24) - (hours * 60));
sys_time = "{}".format(int(days))
cache.set(key, sys_time, 1800)
return sys_time
def getCpuType():
"""
取CPU类型
"""
try:
cpuType = ReadReg(r'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 'ProcessorNameString')
except:
cpuType = ''
return cpuType;
# 取CPU详细信息
def GetCpuInfo(interval=1):
cpuCount = psutil.cpu_count()
cpuNum = psutil.cpu_count(logical=False)
import threading
p = threading.Thread(target=get_cpu_percent_thead, args=(interval,))
# p.setDaemon(True)
p.start()
used = cache.get('lybbn_cpu_used_all')
if not used: used = get_cpu_percent_thead(interval)
used_all = psutil.cpu_percent(percpu=True)
used_total = 0
for x in used_all: used_total += x
ret = ExecShell('wmic cpu get NumberOfCores')[0]
cpuW = 0
arrs = ret.strip().split('\r\n')
for x in arrs:
val = x.strip()
if not val: continue;
try:
val = int(val)
cpuW += 1;
except:
pass
try:
cpu_name = '{} * {}'.format(
ReadReg(r'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 'ProcessorNameString').strip(), cpuW);
except:
cpu_name = ''
tmp = 0
if cpuW: tmp = cpuNum / cpuW
used = 0
if used_total: used = round(used_total / cpuCount, 2)
return used, cpuCount, used_all, cpu_name, tmp, cpuW
def get_cpu_percent_thead(interval):
used = psutil.cpu_percent(interval)
cache.set('lybbn_cpu_used_all', used, 10)
return used
# 取磁盘分区信息
def GetDiskInfo():
key = 'lybbn_sys_disk'
diskInfo = cache.get(key)
if diskInfo: return diskInfo
try:
diskIo = psutil.disk_partitions();
except:
import string
diskIo = []
for c in string.ascii_uppercase:
disk = c + ':'
if os.path.isdir(disk):
data = dict_obj()
data['mountpoint'] = disk + '/'
diskIo.append(data)
diskInfo = []
for disk in diskIo:
try:
tmp = {}
tmp['path'] = disk.mountpoint.replace("\\", "/")
usage = psutil.disk_usage(disk.mountpoint)
tmp['size'] = [to_size(usage.total), to_size(usage.used), to_size(usage.free), usage.percent]
tmp['inodes'] = False
diskInfo.append(tmp)
except:
pass
cache.set(key, diskInfo, 10)
return diskInfo
def GetMsg(key, args=()):
"""
根据key获取内置消息返回
@key 指定消息的key
@args 消息内容中的参数
"""
try:
log_message = json.loads(ReadFile(PUBLIC_DICT));
keys = log_message.keys();
msg = None;
if key in keys:
msg = log_message[key];
for i in range(len(args)):
rep = '{' + str(i + 1) + '}'
msg = msg.replace(rep, args[i]);
return msg;
except:
return key
def getMsg(key, args=()):
return GetMsg(key, args)
def ReturnMsg(status, msg, args=()):
"""
@name 取通用dict返回
@param status 返回状态
@param msg 返回消息
@return dict {"status":bool,"msg":string}
"""
log_message = json.loads(ReadFile(PUBLIC_DICT));
keys = log_message.keys();
if type(msg) == str:
if msg in keys:
msg = log_message[msg];
for i in range(len(args)):
rep = '{' + str(i + 1) + '}'
msg = msg.replace(rep, args[i]);
return {'status': status, 'msg': msg}
def returnMsg(status, msg, args=()):
return ReturnMsg(status, msg, args)
# 重启系统
def RestartServer():
try:
os.system("shutdown /r /f /t 0");
except:
pass
return returnMsg(True, 'SYS_REBOOT');

78
code/utils/system.py Normal file
View File

@@ -0,0 +1,78 @@
#!/bin/python
#coding: utf-8
# +-------------------------------------------------------------------
# | django-vue3-lyadmin
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# ------------------------------
# 系统命令封装
# ------------------------------
import platform
plat = platform.system().lower()
if plat == 'windows':
from .server import windows as myos
else:
from .server import linux as myos
class system:
isWindows = False
def __init__(self):
self.isWindows = self.isWindows()
def isWindows(self):
plat = platform.system().lower()
if plat == 'windows':
return True
return False
def GetSystemAllInfo(self,isCache=False):
"""
获取系统所有信息
"""
data = {}
data['mem'] = self.GetMemInfo()
data['load_average'] = self.GetLoadAverage()
data['network'] = self.GetNetWork()
data['cpu'] = self.GetCpuInfo(1)
data['disk'] = self.GetDiskInfo()
data['time'] = self.GetBootTime()
data['system'] = self.GetSystemVersion()
data['is_windows'] = self.isWindows
return data
def GetMemInfo(self):
memInfo = myos.GetMemInfo()
return memInfo
def GetLoadAverage(self):
data = myos.GetLoadAverage()
return data
def GetNetWork(self):
data = myos.GetNetWork()
return data
def GetCpuInfo(self,interval=1):
data = myos.GetCpuInfo(interval)
return data
def GetBootTime(self):
data = myos.GetBootTime()
return data
def GetDiskInfo(self):
data = myos.GetDiskInfo()
return data
def GetSystemVersion(self):
data = myos.GetSystemVersion()
return data

67
code/utils/usual.py Normal file
View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# @Time : 2022/6/4 23:32
# @Author : 臧成龙
# @FileName: usual.py
# @Software: PyCharm
# -*- coding: utf-8 -*-
from fuadmin.settings import SECRET_KEY
from system.models import Dept
from .fu_jwt import FuJwt
def get_user_info_from_token(request):
token = request.META.get("HTTP_AUTHORIZATION")
token = token.split(" ")[1]
jwt = FuJwt(SECRET_KEY)
value = jwt.decode(SECRET_KEY, token)
user_info = value.payload
return user_info
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
"""
递归获取部门的所有下级部门
:param dept_id: 需要获取的部门id
:param dept_all_list: 所有部门列表
:param dept_list: 递归部门list
:return:
"""
if not dept_all_list:
dept_all_list = Dept.objects.all().values('id', 'parent')
if dept_list is None:
dept_list = [dept_id]
for ele in dept_all_list:
if ele.get('parent') == dept_id:
dept_list.append(ele.get('id'))
get_dept(ele.get('id'), dept_all_list, dept_list)
return list(set(dept_list))
def insert_content_after_line(filename, target_line, content_to_insert):
try:
# 打开文件并读取内容
with open(filename, 'r', encoding='utf-8') as file:
lines = file.readlines()
# 找到目标行的索引位置
target_line_index = None
for i, line in enumerate(lines):
if target_line in line:
target_line_index = i
break
if target_line_index is not None:
# 在目标行后面插入内容
lines.insert(target_line_index + 1, content_to_insert + '\n')
# 将更新后的内容写回文件中
with open(filename, 'w', encoding='utf-8') as file:
file.writelines(lines)
print("内容已成功插入到目标行后。")
else:
print("未找到目标行。")
except FileNotFoundError:
print(f"文件 '{filename}' 未找到。")

85
code/utils/vdes_curd.py Normal file
View File

@@ -0,0 +1,85 @@
import logging
from django.db import transaction, IntegrityError
from time import perf_counter
from system.models import ShipLatestStatus
logger = logging.getLogger(__name__)
def safe_bulk_upsert(ship_data_list):
start_time = perf_counter()
created = updated = 0
try:
# 验证数据格式
valid_data = []
for data in ship_data_list:
if 'mmsi' not in data:
logger.warning("Missing MMSI in data: %s", data)
continue
valid_data.append(data)
if not valid_data:
return 0, 0
# 执行批量操作
with transaction.atomic():
current_mmsi = {item['mmsi'] for item in valid_data}
existing_map = ShipLatestStatus.objects.in_bulk(
current_mmsi, field_name='mmsi'
)
to_create = []
to_update = []
for data in valid_data:
mmsi = data['mmsi']
if mmsi in existing_map:
ship = existing_map[mmsi]
for field, value in data.items():
setattr(ship, field, value)
to_update.append(ship)
else:
to_create.append(ShipLatestStatus(**data))
if to_create:
created = len(ShipLatestStatus.objects.bulk_create(to_create))
if to_update:
update_fields = [f.name for f in ShipLatestStatus._meta.fields
if f.name not in ['id', 'mmsi']]
ShipLatestStatus.objects.bulk_update(to_update, update_fields)
updated = len(to_update)
except IntegrityError as e:
logger.error("Integrity error in bulk upsert: %s", str(e))
# 这里可以添加重试或更精细的错误处理
raise
except Exception as e:
logger.exception("Unexpected error in bulk upsert")
raise
elapsed = (perf_counter() - start_time) * 1000
logger.info(f"Processed {len(valid_data)} ships in {elapsed:.2f}ms: "
f"{created} created, {updated} updated")
return created, updated
def get_nearby_ships(latitude: float, longitude: float, radius_nm: float = 20.0):
# 将海里转换为公里Haversine公式使用公里
radius_km = radius_nm * 1.852
# 使用原生SQL计算
return ShipLatestStatus.objects.raw("""
SELECT *,
(6371 * acos(
cos(radians(%s))
* cos(radians(latitude))
* cos(radians(longitude) - radians(%s))
+ sin(radians(%s))
* sin(radians(latitude))
)) AS distance
FROM ship_latest_status
HAVING distance <= %s
ORDER BY distance
""", [latitude, longitude, latitude, radius_km])