公文传输系统 项目实现

用户管理模块

  • 用户注册、登录、权限管理等。

数据库中的用户模型

class UserProfile(models.Model):
    id = models.CharField(max_length=8, primary_key=True)
    username_up = models.CharField(max_length=50)
    email = models.EmailField()
    password_up = models.CharField(max_length=256)  # 增加密码字段长度
    salt = models.CharField(max_length=15)  # 存储盐值
    access_level = models.IntegerField(default=0)  # Access level: 0 - Regular user, 1 - Admin
    public_key = models.CharField(max_length=128, blank=True, null=True)
    private_key = models.CharField(max_length=128, blank=True, null=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)  # 添加用户头像字段

    def __str__(self):
        return self.username_up

id:用户的唯一标识符,字符长度为8,是主键。
username_up:用户的用户名,字符长度为16。
email:用户的电子邮件地址。
password_up:用户的密码,字符长度为16。
salt:存储盐值,字符长度为15。
access_level:用户的访问级别,整数类型,默认值为0(0 - 普通用户,1 - 管理员)。
public_key:用户的公钥,字符长度为128,可以为空。
private_key:用户的私钥,字符长度为128,可以为空。
avatar:用户的头像图片,上传路径为avatars/,可以为空。
str:定义用户对象的字符串表示形式,这里返回用户名。

用户管理界面

register.html
{% load static %}
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <link href="{% static 'images/icon.ico' %}" rel="icon" type="icon">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册账户</title>
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <section>
        <div class="login">
            <h2>注册界面</h2>
            <div class="innner">
                 <!-- 注册 -->
                <div class="container__form container--signup">
                   <form action="{% url 'register' %}" class="form" id="form1" onsubmit="return validateForm_up()" name="signup" method="post">
                        {% csrf_token %}
                        <input type="text" placeholder="学号(8位)" name="id" class="input" />
                        <input type="text" placeholder="用户名(6-16位字母、数字或下划线)" name="username_up" class="input" />
                        <input type="email" placeholder="邮箱" name="email" class="input" />
                        <input type="password" placeholder="密码(6-16位字母、数字或下划线)" name="password_up" class="input" />
                        <input type="submit" value="注册" id="btn">
                        {% if messages %}
                        <ul class="messages">
                            {% for message in messages %}
                                <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
                            {% endfor %}
                        </ul>
                        {% endif %}
                    </form>
                </div>
                <div class="group">
                    <a>已经有账户了?点击右边登录</a>
                    <a href="{% url 'login' %}">登录</a>
                </div>
            </div>
        </div>
    </section>

    <script src="{% static 'js/verify_up.js' %}"></script>
</body>
</html>

用户可以输入他们的学号、用户名、邮箱和密码进行注册。如果已经有账户,可以点击链接跳转到登录页面。表单提交前会进行客户端验证,并且包含CSRF保护以确保安全。

login.html
{% load static %}
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <link href="{% static 'images/icon.ico' %}" rel="icon" type="icon">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>公文传输系统登录界面</title>
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <section>
        <div class="login">
            <h2>登录界面</h2>
            <div class="innner">
                <!-- 登录 -->
                <div class="container__form container--sign">
                    <form action="{% url 'login' %}" class="form" id="form1" onsubmit="return validateForm_in()" name="signin" method="post">
                    {% csrf_token %}
                    <input type="text" placeholder="学号(8位)或用户名" name="id_in" class="input" />
                    <input type="password" placeholder="密码(6-16位字母、数字或下划线)" name="password_in" class="input" />
                    <input type="submit" value="登录" id="btn">
                    {% if messages %}
                    <ul class="messages">
                        {% for message in messages %}
                            <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
                        {% endfor %}
                    </ul>
                    {% endif %}
                    </form>
                </div>
                <div class="group">
                    <a href="#">忘记密码?</a>
                    <a href="{% url 'register' %}">还没有账号?赶紧注册</a>
                </div>
            </div>
        </div>

    </section>

    <script src="{% static 'js/verify_in.js' %}"></script>
</body>
</html>

用户可以输入他们的学号或用户名及密码进行登录。如果忘记密码,可以点击相应链接进行找回。如果还没有账号,可以点击链接跳转到注册页面。表单提交前会进行客户端验证,并且包含CSRF保护以确保安全。

adduser.html
<!-- adduser.html -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新增用户</title>
    <link rel="stylesheet" href="{% static 'css/adduserpage.css' %}">
    <!-- 此处包含您的样式表或其他资源 -->
</head>
<body>
    <h1>新增用户</h1>
    <form method="post" action="{% url 'create_user' %}">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">确认</button>
        <a href="{% url 'index' %}">取消</a>
    </form>
    <script src="{% static 'js/verify_up.js' %}"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            document.querySelector('form').addEventListener('submit', function(event) {
                event.preventDefault(); // 阻止表单默认提交行为

                // 提交表单后,等待 3 秒后跳转到 index 页面
                setTimeout(function() {
                    document.querySelector('form').submit(); // 提交表单
                }, 3000); // 等待 3 秒
            });
        });
    </script>
</body>
</html>

提供了一个用于新增用户的表单,并包含表单提交前的CSRF保护和JavaScript脚本,以便在表单提交后等待3秒再跳转到主页。

change_userinfo.html
{% load static %}
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>修改用户信息</title>
    <link rel="stylesheet" href="{% static 'css/change_userinfo.css' %}">
    <!-- 此处包含您的样式表或其他资源 -->
</head>
<body>
    <h1>修改用户信息</h1>
    <form method="post" action="{% url 'change_userinfo' user_profile.id %}">
        {% csrf_token %}
        <label for="id">学号:</label>
        <input type="text" id="id" name="id" value="{{ user_profile.id }}" disabled><br><br>

        <label for="username">用户名:</label>
        <input type="text" id="username" name="username_up" value="{{ user_profile.username_up }}"><br><br>

        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email" value="{{ user_profile.email }}"><br><br>

        <label for="password">密码:</label>
        <input type="password" id="password" name="password_up" value="{{ user_profile.password_up }}"><br><br>

        <!-- 如果还有其他字段需要修改,继续添加 -->

        <button type="submit">提交</button>
        <a href="{% url 'index' %}">取消</a>
    </form>
    <script src="{% static 'js/verify_up.js' %}"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            document.querySelector('form').addEventListener('submit', function(event) {
                event.preventDefault(); // 阻止表单默认提交行为

                // 提交表单后,等待 3 秒后跳转到 index 页面
                setTimeout(function() {
                    document.querySelector('form').submit(); // 提交表单
                }, 3000); // 等待 3 秒
            });
        });
    </script>
</body>
</html>

提供了一个用于修改用户信息的表单,并包含表单提交前的CSRF保护和JavaScript脚本,以便在表单提交后等待3秒再跳转到主页。

manage_permission.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户权限管理</title>
    <link href="../static/images/icon.ico" rel="icon" type="icon">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{% static 'css/manage.css' %}">
</head>
<body>
    <h1>用户权限管理</h1>
    <p>用户 ID: {{ user.id }}</p>
    <p>用户名: {{ user.username_up }}</p>
    <p>邮箱: {{ user.email }}</p>
    <p>密码: {{ user.password_up }}</p>

    <form method="post" action="{% url 'manage_permission' user.id %}">
        {% csrf_token %}
        <label for="accessLevel">访问权限:</label>
        <select id="accessLevel" name="access_level">
            <option value="0" {% if user.access_level == 0 %} selected {% endif %}>0</option>
            <option value="1" {% if user.access_level == 1 %} selected {% endif %}>1</option>
            <option value="2" {% if user.access_level == 2 %} selected {% endif %}>2</option>
            <option value="3" {% if user.access_level == 3 %} selected {% endif %}>3</option>
        </select>

        <button type="submit">保存权限</button>
        <a href="{% url 'index' %}">取消</a>

        <!-- 提示用户权限含义 -->
        <p>
            0: 普通用户<br>
            1: 管理员<br>
            2: 不能发送公文的普通用户<br>
            3: 黑名单用户,无法进行任何操作
        </p>
    </form>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            document.querySelector('form').addEventListener('submit', function(event) {
                event.preventDefault(); // 阻止表单默认提交行为

                // 提交表单后,等待 3 秒后跳转到 index 页面
                setTimeout(function() {
                    document.querySelector('form').submit(); // 提交表单
                }, 3000); // 等待 3 秒
            });
        });
    </script>
</body>
</html>

提供了一个用于管理用户权限的表单,管理员可以根据需要修改用户的访问权限,并包含表单提交前的CSRF保护和JavaScript脚本,以便在表单提交后等待3秒再跳转到主页。

upload_avatar.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传用户头像</title>
</head>
<body>
    <form id="avatarUploadForm" action="/upload_avatar/" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="avatarFile" accept="image/*">
        <input type="submit" value="上传头像">
    </form>
</body>
</html>

用于用户上传头像图片,表单通过POST方法提交到 /upload_avatar/ URL,并使用 multipart/form-data 编码类型,以便上传文件。

用户管理逻辑

login逻辑
def login(request):
    if request.method == 'POST':
        id_in = request.POST['id_in']
        password_in = request.POST['password_in']

        # 查询数据库,检查用户是否存在
        try:
            user_profile = UserProfile.objects.get(id=id_in)
        except UserProfile.DoesNotExist:
            messages.error(request, '学号或密码错误,请重新输入。')
            return redirect('login')

        # 获取存储的盐值
        salt = user_profile.salt

        # 计算带盐的SM3哈希值
        salted_hash_password_in = sm3_hash_with_salt(password_in, salt)
        print(salted_hash_password_in)

        # 比较哈希值
        if salted_hash_password_in == user_profile.password_up:
            # 登录成功,将用户信息存储到session中
            request.session['user_id'] = user_profile.id
            request.session['username'] = user_profile.username_up

            LogData = Log.objects.create(
                username = user_profile.username_up,
                documentname = "无",
                operation = f'用户{user_profile.username_up}于{timezone.now()}登录了系统。'
            )
            LogData.save()

            # 可以在这里添加其他处理,例如重定向到成功页面或显示成功消息
            # 登录成功,添加消息
            time.sleep(3)
            return redirect('index')
        else:
            messages.error(request, '学号或密码错误,请重新输入。')
            return redirect('login')

    return render(request, 'login.html')  # 替换为你的模板路径

当请求方法为 POST 时,表示用户提交了登录表单。
获取用户输入的学号和密码。
通过学号查询数据库,检查用户是否存在。如果用户不存在,则返回错误消息并重定向到登录页面。
如果用户存在,则获取数据库中存储的盐值。
使用 SM3 哈希算法计算带盐的密码哈希值。
将计算得到的哈希值与数据库中存储的密码哈希值进行比较,如果相同则表示密码正确,登录成功。
登录成功后,将用户信息存储到 session 中,并记录用户的登录操作到日志中。
最后,重定向到主页,并在这里可以添加其他处理,比如重定向到成功页面或显示成功消息。

register逻辑
def register(request):
    if request.method == 'POST':
        id = request.POST['id']
        username_up = request.POST['username_up']
        email = request.POST['email']
        password_up = request.POST['password_up']

        # 检查id的唯一性
        if UserProfile.objects.filter(id=id).exists():
            messages.error(request, '学号已存在,请使用不同的学号。')
            return redirect('register')

        # 生成随机盐值
        salt = generate_random_string(15)

        # 计算带盐的SM3哈希值
        salted_hash_password = sm3_hash_with_salt(password_up, salt)
        print(salted_hash_password)

        priKey = PrivateKey()
        pubKey = priKey.publicKey()

        new_user = UserProfile.objects.create(
            id=id,
            username_up=username_up,
            email=email,
            password_up=salted_hash_password,  # 存储带盐的SM3哈希值
            salt=salt,  # 存储盐值
            public_key=pubKey.toString(compressed=False),  # 存储公钥
            private_key=priKey.toString(), # 存储私钥
            avatar='avatars/default_avatar.png'
        )
        new_user.save()

        LogData = Log.objects.create(
            username = new_user.username_up,
            documentname = "无",
            operation = f'用户{new_user.username_up}于{timezone.now()}注册了账号。'
        )
        LogData.save()

        # 添加成功消息
        messages.success(request, '注册成功,请登录。')
        time.sleep(3)
        return redirect('login')

    return render(request, 'register.html')  # 替换为你的模板路径

当请求方法为 POST 时,表示用户提交了注册表单。
获取用户输入的学号、用户名、邮箱和密码。
检查学号的唯一性,如果该学号已存在,则返回错误消息并重定向到注册页面。
生成一个随机的盐值,用于加密密码。
使用 SM3 哈希算法计算带盐的密码哈希值。
使用 ECC 算法生成公钥和私钥。
创建一个新的用户对象,将学号、用户名、邮箱、密码哈希、盐值、公钥、私钥和默认头像路径等信息存储到数据库中。
记录用户的注册操作到日志中。
添加成功消息,并在3秒后重定向到登录页面。

sm3_hash_with_salt逻辑
def sm3_hash_with_salt(password, salt):
    # 使用SM3算法生成哈希值
    hash_hex = sm3_hash(func.bytes_to_list(bytes(password, encoding='utf-8')))
    # 使用提供的盐替换提取部分
    new_hash_hex = hash_hex[:4] + salt + hash_hex[19:]
    return new_hash_hex

接收两个参数 password 和 salt,password 是要进行哈希的密码,salt 是要添加到哈希中的盐值。
将密码转换为 UTF-8 编码的字节序列,并通过 sm3_hash 函数生成密码的 SM3 哈希值,得到一个长度为 64 的十六进制哈希字符串 hash_hex。
将 hash_hex 中的一部分提取出来,并在其中间插入盐值,然后返回这个新的哈希字符串。具体来说,将 hash_hex 的前 4 位与盐值和后面的部分(从第 20 位开始)连接起来,这样就生成了带盐的密码哈希值,长度仍然为 64。

generate_random_string逻辑
def generate_random_string(length=15):
    # 生成一个随机字符串
    characters = string.ascii_letters + string.digits + string.punctuation
    random_string = ''.join(random.choice(characters) for i in range(length))
    return random_string

接收一个参数 length,表示要生成的随机字符串的长度,默认为 15。
定义了一个包含大写字母、小写字母、数字和标点符号的字符集 characters。
使用列表推导式和 random.choice 函数从 characters 中随机选择字符,并将它们连接成一个长度为 length 的随机字符串 random_string。
返回生成的随机字符串。

index逻辑
def index(request):
    # 检查用户是否登录
    if 'user_id' in request.session:
        user_id = request.session['user_id']
        username = request.session['username']
        try:
            # 根据用户名查询用户的访问权限
            user_profile = UserProfile.objects.get(username_up=username)
            access_level = user_profile.access_level

            # 获取用户头像的 URL
            user_avatar_url = user_profile.avatar.url if user_profile.avatar else '/avatars/default_avatar.png'
            print(user_avatar_url)
            # 调整图片大小
            # 假设用户头像在media文件夹下的avatars文件夹内
            if user_avatar_url and 'avatars' in user_avatar_url:
                image_path = join(settings.BASE_DIR, 'web', user_avatar_url.lstrip('/').replace('/', '\\'))  # 移除url开头的斜杠并替换斜杠为反斜杠
                print(settings.MEDIA_ROOT)
                print(user_avatar_url[1:])
                print(image_path)
                image = Image.open(image_path)
                resized_image = image.resize((60, 60))  # 设置新的宽度和高度
                resized_image.save(image_path)  # 覆盖原始图片文件

            # 构建完整的 URL
            user_avatar_full_url = request.build_absolute_uri(user_avatar_url)

            return render(request, 'index.html',
                          {'user_id': user_id, 'username': username, 'access_level': access_level,
                           'user_avatar_url': user_avatar_full_url})
        except UserProfile.DoesNotExist:
            # 处理未找到用户的情况
            # 可以引发 Http404 异常或者进行其他适当的处理
            pass

    else:
        # 用户未登录,可以重定向到登录页面或其他处理
        return redirect('login')

首先检查用户是否已经登录,通过检查 session 中是否存在 user_id 键来判断。
如果用户已登录,从 session 中获取用户的 user_id 和 username。
根据用户名查询用户的访问权限,并获取用户头像的 URL。如果用户没有上传头像,则使用默认头像的 URL。
对用户头像进行调整大小的操作。这部分代码假设用户头像存储在 media 文件夹下的 avatars 文件夹内,根据 URL 的路径构建图片的绝对路径,然后使用 PIL 库打开图片并调整大小,最后保存覆盖原始图片文件。
构建完整的用户头像 URL,使用 request.build_absolute_uri 方法。
将用户的 user_id、username、访问权限 access_level 和头像 URL user_avatar_full_url 传递给模板,渲染主页。
如果查询用户信息时发生 UserProfile.DoesNotExist 异常,说明未找到对应的用户,可以引发 Http404 异常或者进行其他适当的处理。
如果用户未登录,重定向到登录页面。

get_users逻辑
def get_users(request):
    # 从数据库中获取用户信息
    users = UserProfile.objects.all().values()  # 假设 User 有合适的字段来表示用户信息

    # 将查询到的数据转换为列表,并以 JSON 格式返回给前端
    return JsonResponse(list(users), safe=False)

使用 UserProfile.objects.all() 查询数据库中的所有用户信息。
使用 .values() 方法将查询结果转换为一个包含每个用户信息的字典的 QuerySet。
使用 JsonResponse 将 QuerySet 转换为 JSON 格式,并返回给前端。由于 JsonResponse 默认不允许直接序列化 QuerySet,需要将 safe 参数设置为 False,表示可以序列化任意类型的数据结构,包括 QuerySet。

create_user逻辑
def create_user(request):
    current_user = request.session['username']
    if request.method == 'POST':
        form = UserForm(request.POST)
        if form.is_valid():
            # 获取表单数据并保存到数据库
            id = form.cleaned_data['id']
            username = form.cleaned_data['username_up']
            email = form.cleaned_data['email']
            password = form.cleaned_data['password_up']

            # 生成随机盐值
            salt = generate_random_string(15)
            # 计算带盐的SM3哈希值
            salted_hash_password = sm3_hash_with_salt(password, salt)

            priKey = PrivateKey()
            pubKey = priKey.publicKey()

            # 保存到数据库中
            UserProfile.objects.create(
                id=id,
                username_up=username,
                email=email,
                password_up=salted_hash_password,  # 存储带盐的SM3哈希值
                salt=salt,  # 存储盐值
                public_key=pubKey.toString(compressed=False),  # 存储公钥
                private_key=priKey.toString(),  # 存储私钥
                avatar='avatars/default_avatar.png'
            )

            LogData = Log.objects.create(
                username=current_user,
                documentname="无",
                operation=f'用户{current_user}于{timezone.now()}创建了新用户{username}。'
            )
            LogData.save()

            # 重定向到 index 页面,使用 HttpResponseRedirect 对象
            return HttpResponseRedirect(reverse('index'))

    else:
        form = UserForm()

    return render(request, 'adduser.html', {'form': form})

获取当前登录用户的用户名。
如果请求方法为 POST,表示用户提交了创建新用户的表单。
使用表单 UserForm 验证提交的数据是否有效。如果表单数据有效,继续以下步骤:
获取表单中的用户 ID、用户名、邮箱和密码。
生成一个随机盐值。
计算带盐的 SM3 哈希值,并将其作为加密后的密码存储。
生成用户的公钥和私钥。
将用户的 ID、用户名、邮箱、加密后的密码、盐值、公钥、私钥和头像路径存储到数据库中。
创建一条日志记录,记录当前用户创建新用户的操作。
重定向到主页。
如果请求方法不是 POST,即用户访问创建用户页面,渲染包含用户表单的模板。

delete_user逻辑
def delete_user(request, user_id):
    current_user = request.session['username']
    if request.method == 'DELETE':
        user = get_object_or_404(UserProfile, id=user_id)

        LogData = Log.objects.create(
            username=current_user,
            documentname="无",
            operation=f'用户{current_user}于{timezone.now()}删除了用户{user.username_up}。'
        )
        LogData.save()

        user.delete()
        return JsonResponse({'message': 'User deleted successfully'}, status=200)
    else:
        return JsonResponse({'message': 'Invalid request method'}, status=400)

获取当前登录用户的用户名。
当请求方法为 DELETE 时,表示用户提交了删除用户的请求。
通过用户的 ID 从数据库中获取要删除的用户对象。
记录用户删除操作到日志中。
删除数据库中的用户记录。
返回一个成功的 JSON 响应。
如果请求方法不是 DELETE,则返回一个错误的 JSON 响应。

change_userinfo逻辑
def change_userinfo(request, user_id):
    current_user = request.session['username']
    try:
        user_profile = UserProfile.objects.get(id=user_id)

        # 如果是 POST 请求,即提交表单
        if request.method == 'POST':
            # 获取表单中的数据
            username = request.POST.get('username_up')
            email = request.POST.get('email')
            password = request.POST.get('password_up')

            # 如果密码不为空,则更新密码
            if password:
                # 生成新的随机盐值
                salt = generate_random_string(15)
                # 计算带盐的SM3哈希值
                salted_hash_password = sm3_hash_with_salt(password, salt)
                # 更新用户密码和盐值
                user_profile.password_up = salted_hash_password
                user_profile.salt = salt

            # 更新用户名和邮箱
            user_profile.username_up = username
            user_profile.email = email

            LogData = Log.objects.create(
                username=current_user,
                documentname="无",
                operation=f'用户{current_user}于{timezone.now()}修改了{username}的用户信息。'
            )
            LogData.save()

            # 保存更新后的信息到数据库
            user_profile.save()

            messages.success(request, '用户信息更新成功。')
            return redirect('index')  # 更新成功后重定向到首页
        else:
            # 如果是 GET 请求,即用户访问修改页面
            return render(request, 'change_userinfo.html', {'user_profile': user_profile})

    except UserProfile.DoesNotExist:
        messages.error(request, '用户不存在。')
        return redirect('index')

获取当前登录用户的用户名。
使用用户 ID 查找用户资料。如果用户存在,继续以下步骤:
如果请求方法为 POST,表示用户提交了修改用户信息的表单:
从表单中获取用户名、邮箱和密码。
如果密码不为空,则执行以下操作:
生成新的随机盐值。
计算带盐的 SM3 哈希值。
更新用户的密码和盐值。
更新用户名和邮箱。
创建一条日志记录,记录当前用户修改用户信息的操作。
保存更新后的用户信息到数据库。
添加一条成功消息,提示用户信息更新成功。
重定向到主页。

manage_permission逻辑
def manage_permission(request, user_id):
    current_user = request.session['username']
    # 根据 user_id 获取特定用户的信息或权限
    user = UserProfile.objects.get(id=user_id)

    if request.method == 'POST':
        access_level = request.POST['access_level']
        user.access_level = access_level

        LogData = Log.objects.create(
            username=current_user,
            documentname="无",
            operation=f'用户{current_user}于{timezone.now()}修改了{user.username_up}的访问权限。'
        )
        LogData.save()

        user.save()  # 将更改后的访问权限保存到数据库

        # 重定向到 index 页面
        return redirect('index')

    # 渲染 manage_permission.html 页面,并传递用户信息或权限
    return render(request, 'manage_permission.html', {'user': user})

获取当前登录用户的用户名。
根据用户的 ID 从数据库中获取要管理权限的用户对象。
如果是 POST 请求,即用户提交了修改用户权限的表单,获取表单中的访问级别信息,并更新用户的访问级别到数据库中。
记录用户修改权限操作到日志中。
将更改后的访问权限保存到数据库中。
重定向到主页。
如果是 GET 请求,即用户访问管理权限页面,渲染包含用户权限信息的模板,将用户信息传递给模板以便显示。

logout逻辑
def logout(request):
    current_user = request.session['username']
    # 清除会话信息
    if 'user_id' in request.session:

        LogData = Log.objects.create(
            username=current_user,
            documentname="无",
            operation=f'用户{current_user}于{timezone.now()}退出了系统。'
        )

        LogData.save()

        del request.session['user_id']
        del request.session['username']

    # 重定向到主页或登录页
    return redirect('index')  # 'index' 应该是你的主页 URL 名称或路径

从会话中获取当前用户的用户名。
检查会话中是否存在用户ID('user_id'),如果存在,则执行以下操作:
创建一条日志记录,记录用户的退出操作。
删除会话中的用户ID和用户名信息。
将用户重定向到主页或登录页。

upload_avatar_page逻辑
def upload_avatar_page(request):
    # 传递表单给模板,以便在页面上显示上传表单
    form = AvatarUploadForm()
    return render(request, 'upload_avatar.html', {'form': form})

创建一个 AvatarUploadForm 的实例对象 form,用于处理头像上传的表单数据。
使用 render 函数将请求、模板文件名和一个包含表单对象的字典传递给模板,以便在页面上显示上传头像的表单。

upload_avatar逻辑
def upload_avatar(request):
    current_user = request.session.get('username')  # 使用 get 方法以避免键不存在时引发 KeyError
    if request.method == 'POST':
        form = AvatarUploadForm(request.POST, request.FILES)
        if form.is_valid():
            try:
                user_profile = UserProfile.objects.get(username_up=current_user)
                avatar_image = form.cleaned_data['avatarFile']
                image = Image.open(avatar_image)
                # 将图像转换为PNG格式并覆盖原始图像文件
                image = image.convert("RGB")
                image.save(user_profile.avatar.path, 'JPEG')
                user_profile.save()
                return redirect('index')  # 重定向到首页或其他页面
            except UserProfile.DoesNotExist:
                # 处理用户不存在的情况
                pass
    else:
        form = AvatarUploadForm()

    return render(request, 'upload_avatar.html', {'form': form})

使用 request.session.get('username') 获取当前用户的用户名,避免在键不存在时引发 KeyError。
检查请求方法是否为 POST,如果是,则使用 AvatarUploadForm 处理请求中的表单数据和文件。
如果表单数据有效 (form.is_valid()),则尝试获取当前用户的用户资料对象 (UserProfile),然后从表单数据中获取上传的头像文件 (avatarFile)。
使用 PIL 库打开上传的图像文件,并将其转换为 JPEG 格式,然后保存到用户资料对象的头像路径 (avatar.path) 中,并保存用户资料对象。
如果用户不存在 (UserProfile.DoesNotExist),则忽略异常。
如果请求方法不是 POST,则创建一个空的 AvatarUploadForm 对象,并将其传递给模板以显示上传头像的表单。
最后,使用 render 函数将请求、模板文件名和包含表单对象的字典传递给模板,以便在页面上显示上传头像的表单。

公文管理模块

  • 公文的创建、编辑、删除等。

数据库中的公文模型

class Document(models.Model):
    document_id = models.AutoField(primary_key=True)
    document_owner = models.CharField(max_length=255)
    document_name = models.CharField(max_length=255)
    issuing_office = models.CharField(max_length=255)
    issue_date = models.DateTimeField()
    security_level = models.CharField(max_length=50)
    cc_office = models.CharField(max_length=255)
    file_type = models.CharField(max_length=50)
    modifier = models.CharField(max_length=50)
    modified_date = models.DateTimeField()
    file_address = models.CharField(max_length=255)
    is_sent = models.IntegerField(default=0)
    is_pass = models.IntegerField(default=0)

    def __str__(self):
        return self.document_name

document_id:文档的唯一标识符,自增整数,是主键。
document_owner:文档的拥有者,字符长度为255。
document_name:文档的名称,字符长度为255。
issuing_office:发文机构,字符长度为255。
issue_date:发文日期,日期时间类型。
security_level:文档的安全级别,字符长度为50。
cc_office:抄送机构,字符长度为255。
file_type:文件类型,字符长度为50。
modifier:修改者,字符长度为50。
modified_date:修改日期,日期时间类型。
file_address:文件地址,字符长度为255。
is_sent:是否已发送,整数类型,默认值为0。
is_pass:是否通过,整数类型,默认值为0。
str:定义文档对象的字符串表示形式,这里返回文档名称。

公文管理界面

index.html
{% load static %}
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="{{ csrf_token }}">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>公文传输系统</title>
    <link rel="stylesheet" href="{% static 'css/style-syspage.css' %}">
    <link rel="stylesheet" href="{% static 'css/style-newnavi.css' %}">
    <link rel="stylesheet" href="{% static 'css/sidebarContainer.css' %}">
    <link rel="stylesheet" href="{% static 'css/homepage.css' %}">
    <link rel="stylesheet" href="{% static 'css/textmanagerpage.css' %}">
    <link rel="stylesheet" href="{% static 'css/textpage.css' %}">
    <link rel="stylesheet" href="{% static 'css/usermanagerpage.css' %}">
    <script src="https://cdn.jsdelivr.net/npm/vue@3.0.11/dist/vue.global.prod.js"></script>
    <!-- 在<head>标签中引入Quill.js的CSS文件 -->
    <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

    <!-- 在<body>标签中引入Quill.js的JavaScript文件 -->
    <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
</head>
<body>
    <div>
        <div class="navbar-top">
            <div class="menu-content">
            <div class="menu-icon">&#9776;</div>
            <input type="text" class="search-box" placeholder="搜索...">
            <div class="user-info">
                <span class="help-button">帮助文档</span>
                <span class="user-name">您好 {{ username }}!</span>
                <input type="hidden" id="loggedInUserId" value="{{ user_id }}">
            </div>
        </div>
        </div>
        <div>
            <div class="navbar-left">
                <a href="/upload_avatar_page/">
                    <div class="Avatar">
                        <img src="{{ user_avatar_url }}" alt="User Avatar" class="user-avatar">
                    </div>
                </a>
                <div class="sys">
                    公文传输系统
                </div>
                <div class="nav-links">
                <!-- 在左侧导航栏的每个功能键上添加@click事件处理程序 -->
                <a href="#" class="tab-button" @click="handleTabClick('首页')" data-page="homePage">首页</a>
                <a href="#" class="tab-button" @click="handleTabClick('公文管理')" data-page="documentManagementPage">公文管理</a>
                <a href="#" class="tab-button" @click="handleTabClick('公文审核')" data-page="documentReviewPage">公文审核</a>
                <a href="#" class="tab-button" @click="handleTabClick('公文草拟')" data-page="documentDraftPage">公文草拟</a>
                <a href="#" class="tab-button" @click="handleTabClick('用户管理')" data-page="userManagementPage">用户管理</a>
                <a href="#" class="tab-button" @click="handleTabClick('日志管理')" data-page="logManagementPage">日志管理</a>
                </div>
                <a href="#" class="quit" id="logoutButton">
                    退出系统
                </a>
            </div>
            <div id="sidebarContainer"></div>
        </div>
    </div>

    <script src="{% static 'js/main.js' %}"></script>
    <script>
        window.accessLevel = {{ access_level|default_if_none:0 }};
        const logoutButton = document.getElementById('logoutButton');

        logoutButton.addEventListener('click', (event) => {
            event.preventDefault();

            fetch('/logout/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRFToken': getCookie('csrftoken') // 替换为获取CSRF令牌的函数,这是一个示例
                },
                // ...其他请求数据
            })
            .then(response => {
                if (response.redirected) {
                    // 延迟3秒后执行重定向
                    setTimeout(() => {
                        window.location.href = response.url; // 重定向到指定页面
                    }, 3000); // 3000毫秒 = 3秒
                }
            })
            .catch(error => {
                console.error('退出时出错:', error);
            });
        });

        function getCookie(name) {
            const cookieValue = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
            return cookieValue ? cookieValue.pop() : '';
        }
    </script>
</body>
</html>

实现了一个具有基本功能的公文传输系统的前端界面,提供了用户友好的界面和交互体验。

公文管理逻辑

delete_document逻辑
@csrf_exempt
def delete_document(request, documentname):
    current_user = request.session['username']
    if request.method == 'DELETE':
        print(f'要删除的公文:{documentname}')
        # 从数据库中查找要删除的文档
        document = get_object_or_404(Document, document_name=documentname)

        file_enc_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'docx', (documentname + '.enc'))
        file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'docxs', (documentname + '.docx'))
        key_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (documentname + 'key.dat'))
        key_enc_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (documentname + 'encryptkey.dat'))
        keyhash_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'secure', (documentname + 'hash_decrypted.dat'))
        keyorig_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'secure', (documentname + 'hash_original.dat'))
        keysign_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'sign', (documentname + 'signkey.dat'))
        if os.path.exists(file_enc_path):
            os.remove(file_enc_path)
            print(f"File {file_enc_path} deleted successfully")
        else:
            print(f"File {file_enc_path} does not exist")
        if os.path.exists(file_path):
            os.remove(file_path)
            print(f"File {file_path} deleted successfully")
        else:
            print(f"File {file_path} does not exist")
        if os.path.exists(key_path):
            os.remove(key_path)
            print(f"File {key_path} deleted successfully")
        else:
            print(f"File {key_path} does not exist")
        if os.path.exists(key_enc_path):
            os.remove(key_enc_path)
            print(f"File {key_enc_path} deleted successfully")
        else:
            print(f"File {key_enc_path} does not exist")
        if os.path.exists(keyhash_path):
            os.remove(keyhash_path)
            print(f"File {keyhash_path} deleted successfully")
        else:
            print(f"File {keyhash_path} does not exist")
        if os.path.exists(keyorig_path):
            os.remove(keyorig_path)
            print(f"File {keyorig_path} deleted successfully")
        else:
            print(f"File {keyorig_path} does not exist")
        if os.path.exists(keysign_path):
            os.remove(keysign_path)
            print(f"File {keysign_path} deleted successfully")
        else:
            print(f"File {keysign_path} does not exist")

        LogData = Log.objects.create(
            username=current_user,
            documentname=document.document_name,
            operation=f'用户{current_user}于{timezone.now()}删除了公文:{document.document_name}。'
        )
        LogData.save()

        # 删除文档
        document.delete()

        # 返回成功的 JSON 响应
        return JsonResponse({'message': 'Document deleted successfully'})

    # 如果请求方法不是 DELETE,则返回错误响应
    return JsonResponse({'error': 'Invalid request method'}, status=400)

使用 @csrf_exempt 装饰器来取消 CSRF 保护,这意味着该视图可以接受不带 CSRF 令牌的请求。
获取当前登录用户的用户名。
当请求方法为 DELETE 时,表示用户提交了删除文档的请求。
从数据库中查找要删除的文档,如果文档不存在,则返回 404 错误。
构建文档、密钥和签名文件的路径。
对每个文件路径进行检查,如果文件存在,则删除该文件,并在控制台打印相应的消息。
记录文档删除操作到日志中。
删除数据库中的文档记录。
返回一个成功的 JSON 响应。
如果请求方法不是 DELETE,则返回一个错误的 JSON 响应。

decrypt_document逻辑
def decrypt_document(request):
    current_user = request.session['username']
    if request.method == 'POST':
        # 获取传递的文件地址或其他必要信息
        data = json.loads(request.body)
        file_title = data.get('file_title')
        file_name = data.get('file_name')

        print(file_title)
        print(file_name)

        doc = Document.objects.get(document_name=file_title)
        file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'docx', file_name)
        print(file_path)
        doc_owner = doc.issuing_office
        cc_office = doc.cc_office
        sender_keyowner = UserProfile.objects.get(id=doc_owner)
        sender_publickey = sender_keyowner.public_key
        sender_privatekey = sender_keyowner.private_key
        key_owner = UserProfile.objects.get(username_up=cc_office)
        publickey = key_owner.public_key
        privatekey = key_owner.private_key

        key_sign_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'sign', (file_title + 'signkey.dat'))
        key_encrypt_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (file_title + 'encryptkey.dat'))
        verify_signature(public_key=sender_publickey,
                         private_key=sender_privatekey,
                         input_file=key_encrypt_path,
                         signature_file=key_sign_path)

        key_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (file_title + 'key.dat'))

        decrypt_data(public_key=publickey,
                     private_key=privatekey,
                     input_file=key_encrypt_path,
                     output_file=key_path)

        key = get_or_generate_key(file_title)
        print(f'对称密钥的类型为:{type(key)}')
        # 在这里执行文件解密的操作
        decrypted_file_address = decrypt_and_hash_file(file_path, key, file_title)

        LogData = Log.objects.create(
            username=current_user,
            documentname=doc.document_name,
            operation=f'用户{current_user}于{timezone.now()}下载了公文:{doc.document_name}。'
        )
        LogData.save()

        # 返回解密结果(成功或失败)
        return JsonResponse({'message': '文件解密成功', 'decrypted_file_address': decrypted_file_address})  # 或者其他成功信息
    else:
        return JsonResponse({'message': '无效的请求'}, status=400)

获取当前登录用户的用户名:从会话中获取当前登录用户的用户名 (current_user),用于记录操作日志。
处理 POST 请求:如果请求方法为 POST,则表示用户提交了解密文件的请求。解析请求体中的 JSON 数据,提取出文件标题 (file_title) 和文件名 (file_name)。
获取文档信息:根据文件标题查询数据库,找到对应的文档对象,并获取相关信息,如文档的发布部门、抄送部门等。
获取密钥信息:根据文档的发布部门和抄送部门,获取对应用户的公钥和私钥。
验证签名:根据发送者的公钥和私钥,验证密钥文件的签名是否有效。
解密密钥:使用接收者的私钥解密密钥文件,获取对称密钥。
解密文件:使用对称密钥解密文件,并返回解密后的文件地址。
记录操作日志:创建一条日志记录,记录当前用户下载了哪个公文,记录操作的类型和时间。
返回 JSON 响应:如果文件解密成功,返回状态码 200 和消息 "文件解密成功",同时返回解密后的文件地址。如果请求方法不是 POST,则返回状态码 400 和消息 "无效的请求"。

get_or_generate_key逻辑
def get_or_generate_key(document_name):
    # 尝试从外部输入获取密钥
    key_file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (document_name + 'key.dat'))
    print(key_file_path)
    if os.path.exists(key_file_path):
        with open(key_file_path, 'rb') as key_file:
            key = key_file.read()
            print(key)
            print(len(key))
            if len(key) != 16:
                print("密钥长度必须为16字节")
                return None
    else:
        # 生成随机的16字节密钥
        key = secrets.token_bytes(16)
        with open(key_file_path, 'wb') as key_file:
            key_file.write(key)

    return key

构建密钥文件路径:根据文档名称构建密钥文件的路径。
检查密钥文件是否存在:检查密钥文件是否存在于指定路径中。
如果存在,则尝试从文件中读取密钥内容。
如果密钥长度不是16字节,则打印消息并返回None。
如果不存在,则生成一个16字节的随机密钥,并将其写入文件中。
返回密钥:返回读取或生成的密钥。

encrypt_and_hash_file逻辑
def encrypt_and_hash_file(input_file_path, key, document_name):
    # 读取文件内容
    with open(input_file_path, 'rb') as file:
        plaintext = file.read()

    # 计算文件的哈希值
    hash_value = sm3_hash(list(plaintext))  # Convert bytes to list
    hash_file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'secure', (document_name + 'hash_original.dat'))
    with open(hash_file_path, 'w') as hash_file:
        hash_file.write(hash_value)

    print(f'原文件的哈希值已保存到:{hash_file_path}')

    # 初始化SM4加密器
    crypt_sm4 = CryptSM4()

    # 设置密钥
    crypt_sm4.set_key(key, SM4_ENCRYPT)

    # 加密文件内容
    ciphertext = crypt_sm4.crypt_ecb(plaintext)

    # 创建加密后的文件
    encrypted_file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'docx', (document_name + '.enc'))
    with open(encrypted_file_path, 'wb') as file:
        file.write(ciphertext)

    print(f'文件加密成功:{encrypted_file_path}')

读取文件内容:使用二进制模式打开文件,读取文件内容为 plaintext。
计算文件哈希值:使用 SM3 哈希算法计算文件内容的哈希值,并将其写入文件中。
初始化 SM4 加密器:创建一个 SM4 加密器对象。
设置密钥:使用给定的密钥设置 SM4 加密器的密钥。
加密文件内容:使用 ECB 模式对文件内容进行 SM4 加密。
创建加密后的文件:将加密后的内容写入新的文件中。
打印日志:打印加密后的文件路径,以及原文件的哈希值保存路径。

decrypt_and_hash_file逻辑
def decrypt_and_hash_file(encrypted_file_path, key, document_name):
    # 初始化SM4解密器
    crypt_sm4 = CryptSM4()

    # 设置密钥
    crypt_sm4.set_key(key, SM4_DECRYPT)

    # 读取加密文件内容
    with open(encrypted_file_path, 'rb') as file:
        ciphertext = file.read()

    # 解密文件内容
    plaintext = crypt_sm4.crypt_ecb(ciphertext)

    # 创建解密后的文件
    decrypted_file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'docxs', (document_name + '.docx'))
    with open(decrypted_file_path, 'wb') as file:
        file.write(plaintext)

    print(f'文件解密成功:{decrypted_file_path}')

    # 计算解密文件的哈希值
    hash_value = sm3_hash(list(plaintext))  # Convert bytes to list

    # 将哈希值保存到hash_decrypted.txt文件
    hash_decrypted_file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'secure', (document_name + 'hash_decrypted.dat'))
    with open(hash_decrypted_file_path, 'w') as hash_file:
        hash_file.write(hash_value)

    print(f'解密文件的哈希值已保存到:{hash_decrypted_file_path}')

    # 比较原始哈希和解密后的哈希
    hash_original_file = os.path.join(settings.BASE_DIR, 'web', 'static', 'secure',
                                      (document_name + 'hash_original.dat'))
    hash_decrypted_file = os.path.join(settings.BASE_DIR, 'web', 'static', 'secure',
                                       (document_name + 'hash_decrypted.dat'))

    with open(hash_original_file, 'rb') as original, open(hash_decrypted_file, 'rb') as decrypted:
        original_hash = original.read()
        decrypted_hash = decrypted.read()

        if original_hash == decrypted_hash:
            print("加密和解密后的文件内容一致。")
        else:
            print("加密和解密后的文件内容不一致。")

    decrypted_file_path_str = f'/static/docxs/{document_name}'+'.docx'

    return decrypted_file_path_str

初始化 SM4 解密器:创建一个 SM4 解密器对象。
设置密钥:使用给定的密钥设置 SM4 解密器的密钥。
读取加密文件内容:使用二进制模式打开加密文件,读取文件内容为 ciphertext。
解密文件内容:使用 ECB 模式对文件内容进行 SM4 解密,得到解密后的内容 plaintext。
创建解密后的文件:将解密后的内容写入新的文件中。
计算解密文件的哈希值:使用 SM3 哈希算法计算解密后文件内容的哈希值,并将其写入文件中。
比较原始哈希和解密后的哈希:读取原始文件和解密后文件的哈希值,比较它们是否相同,以验证解密的正确性。
返回解密后文件的路径:返回解密后文件的路径字符串,用于后续操作。

CurveFp逻辑
class CurveFp:
    def __init__(self, A, B, P, N, Gx, Gy, name):
        self.A = A
        self.B = B
        self.P = P
        self.N = N
        self.Gx = Gx
        self.Gy = Gy
        self.name = name


sm2p256v1 = CurveFp(
    name="sm2p256v1",
    A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,
    B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93,
    P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,
    N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123,
    Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7,
    Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
)

这段代码定义了一个名为 CurveFp 的类,用于表示椭圆曲线的参数。具体解释如下:
CurveFp 类有七个属性:A、B、P、N、Gx、Gy 和 name,分别表示椭圆曲线的参数。
A、B、P、N 分别表示椭圆曲线方程中的参数,Gx 和 Gy 表示基点的坐标,name 表示该椭圆曲线的名称。
sm2p256v1 是一个实例化的 CurveFp 对象,表示了一个特定的椭圆曲线参数集,通常用于密码学中的椭圆曲线加密算法。

multiply逻辑
def multiply(a, n, N, A, P):
    return fromJacobian(jacobianMultiply(toJacobian(a), n, N, A, P), P)

实现了椭圆曲线上的点乘法运算。具体解释如下:
multiply 函数接受五个参数:a 表示要进行乘法的点,n 表示乘法的倍数,N 表示椭圆曲线的参数 N,A 和 P 分别表示椭圆曲线的参数 A 和 P。
首先,调用 toJacobian(a) 将点 a 转换为雅各比坐标系下的表示。
然后,调用 jacobianMultiply 函数对转换后的点进行倍乘运算,得到倍乘后的结果。
最后,调用 fromJacobian 函数将结果从雅各比坐标系转换回椭圆曲线上的点表示,并返回最终结果。

add逻辑
def add(a, b, A, P):
    return fromJacobian(jacobianAdd(toJacobian(a), toJacobian(b), A, P), P)

实现了椭圆曲线上的点加法运算。具体解释如下:
add 函数接受四个参数:a 和 b 分别表示要进行加法运算的两个点,A 和 P 分别表示椭圆曲线的参数 A 和 P。
首先,调用 toJacobian(a) 和 toJacobian(b) 将点 a 和 b 分别转换为雅各比坐标系下的表示。
然后,调用 jacobianAdd 函数对转换后的两个点进行加法运算,得到加法后的结果。
最后,调用 fromJacobian 函数将结果从雅各比坐标系转换回椭圆曲线上的点表示,并返回最终结果。

inv逻辑
def inv(a, n):
    if a == 0:
        return 0
    lm, hm = 1, 0
    low, high = a % n, n
    while low > 1:
        r = high // low
        nm, new = hm - lm * r, high - low * r
        lm, low, hm, high = nm, new, lm, low
    return lm % n

实现了求整数 a 在模 n 意义下的乘法逆元。具体解释如下:
首先检查 a 是否为 0,如果是则直接返回 0,因为 0 在模任何数意义下都不存在乘法逆元。
接着初始化两组变量 lm、hm 和 low、high,分别代表上一次迭代的 m 和 m-1 的系数,以及 n 与 a 对 n 取余的结果。
进入循环,当 low 大于 1 时,执行以下操作:
计算除法商 r,用于得到新一轮的 m 和 m-1 的系数。
更新 nm 和 new,分别代表新的 m 和 m-1 的系数。
更新 lm、low、hm、high,将 new 赋给 low,将 low 赋给 high,将 nm 赋给 hm,将 lm 赋给 low。
最后返回 lm % n,即 a 在模 n 意义下的乘法逆元。

toJacobian逻辑
def toJacobian(Xp_Yp):
    Xp, Yp = Xp_Yp
    return (Xp, Yp, 1)

接受一个二元组 Xp_Yp 作为参数,表示一个点的坐标 (Xp, Yp)。
将输入的点坐标转换为雅可比坐标形式,即将 (Xp, Yp) 转换为 (Xp, Yp, 1)。
返回转换后的雅可比坐标形式的点。

fromJacobian逻辑
def fromJacobian(Xp_Yp_Zp, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    z = inv(Zp, P)
    return ((Xp * z ** 2) % P, (Yp * z ** 3) % P)

计算 Zp 的逆元 z,这里使用了之前定义的 inv 函数,若 Zp 为 0,则返回 0。
计算仿射坐标 (X, Y) 中的 X:X = (Xp * z^2) % P,其中 P 是椭圆曲线的模数。
计算仿射坐标 (X, Y) 中的 Y:Y = (Yp * z^3) % P。

jacobianDouble逻辑
def jacobianDouble(Xp_Yp_Zp, A, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    if not Yp:
        return (0, 0, 0)
    ysq = (Yp ** 2) % P
    S = (4 * Xp * ysq) % P
    M = (3 * Xp ** 2 + A * Zp ** 4) % P
    nx = (M ** 2 - 2 * S) % P
    ny = (M * (S - nx) - 8 * ysq ** 2) % P
    nz = (2 * Yp * Zp) % P
    return (nx, ny, nz)

获取输入点的坐标 (Xp, Yp, Zp)。
如果输入点的 Y 坐标为 0,则返回 (0, 0, 0),表示无穷远点。
计算 Y 坐标的平方:ysq = (Yp ** 2) % P。
计算 S:S = (4 * Xp * ysq) % P。
计算 M:M = (3 * Xp ** 2 + A * Zp ** 4) % P。
计算新点的 X 坐标:nx = (M ** 2 - 2 * S) % P。
计算新点的 Y 坐标:ny = (M * (S - nx) - 8 * ysq ** 2) % P。
计算新点的 Z 坐标:nz = (2 * Yp * Zp) % P。
返回新点的雅可比坐标 (nx, ny, nz)。

jacobianAdd逻辑
def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    Xq, Yq, Zq = Xq_Yq_Zq
    if not Yp:
        return (Xq, Yq, Zq)
    if not Yq:
        return (Xp, Yp, Zp)
    U1 = (Xp * Zq ** 2) % P
    U2 = (Xq * Zp ** 2) % P
    S1 = (Yp * Zq ** 3) % P
    S2 = (Yq * Zp ** 3) % P
    if U1 == U2:
        if S1 != S2:
            return (0, 0, 1)
        return jacobianDouble((Xp, Yp, Zp), A, P)
    H = U2 - U1
    R = S2 - S1
    H2 = (H * H) % P
    H3 = (H * H2) % P
    U1H2 = (U1 * H2) % P
    nx = (R ** 2 - H3 - 2 * U1H2) % P
    ny = (R * (U1H2 - nx) - S1 * H3) % P
    nz = (H * Zp * Zq) % P
    return (nx, ny, nz)

定义了 jacobianAdd 函数,用于计算椭圆曲线上点的雅可比坐标的加法。
如果其中一个点的 Y 坐标为 0,则直接返回另一个点的坐标。
否则,根据雅可比坐标的加法规则计算两点之和的坐标。
如果两个点的 X 坐标相等但 Y 坐标不等,则返回无穷远点的坐标。
否则,根据加法公式计算两点之和的新坐标。

jacobianMultiply逻辑
def jacobianMultiply(Xp_Yp_Zp, n, N, A, P):
    Xp, Yp, Zp = Xp_Yp_Zp
    if Yp == 0 or n == 0:
        return (0, 0, 1)
    if n == 1:
        return (Xp, Yp, Zp)
    if n < 0 or n >= N:
        return jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P)
    if (n % 2) == 0:
        return jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P)
    if (n % 2) == 1:
        return jacobianAdd(jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P), (Xp, Yp, Zp), A, P)

如果点的 Y 坐标为 0 或 n 为 0,则返回无穷远点的坐标。
如果 n 为 1,则返回点的坐标。
如果 n 小于 0 或大于等于 N(曲线的阶),则将 n 取模为 N,并递归调用 jacobianMultiply。
如果 n 是偶数,则将点加倍,即调用 jacobianDouble。
如果 n 是奇数,则将点加倍后与原点相加,即先调用 jacobianDouble,然后调用 jacobianAdd。

PrivateKey逻辑
class PrivateKey:
    def __init__(self, curve=sm2p256v1, secret=None):
        self.curve = curve
        self.secret = secret or SystemRandom().randrange(1, curve.N)

    def publicKey(self):
        curve = self.curve
        xPublicKey, yPublicKey = multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)
        return PublicKey(xPublicKey, yPublicKey, curve)

    def toString(self):
        return "{}".format(str(hex(self.secret))[2:].zfill(64))

init 方法用于初始化私钥对象。它接受两个参数:curve 表示椭圆曲线对象,默认为 sm2p256v1;secret 表示私钥的值,默认为在曲线的阶 N 内随机选择一个值作为私钥。
publicKey 方法用于计算并返回对应的公钥对象。它利用私钥对象的 secret 值和椭圆曲线参数,在曲线上执行点乘运算,得到公钥的坐标 (xPublicKey, yPublicKey),并将其传递给 PublicKey 构造函数创建公钥对象。
toString 方法用于将私钥对象的 secret 值转换为字符串表示,并返回。在这里,hex(self.secret) 将私钥值转换为十六进制表示,然后通过字符串格式化操作 str.format 将其填充为长度为 64 的字符串(不足 64 位的在左侧用 0 填充)。

PublicKey逻辑
class PublicKey:
    def __init__(self, x, y, curve):
        self.x = x
        self.y = y
        self.curve = curve

    def toString(self, compressed=True):
        return {
            True: str(hex(self.x))[2:],
            False: "{}{}".format(str(hex(self.x))[2:].zfill(64), str(hex(self.y))[2:].zfill(64))
        }.get(compressed)

init 方法用于初始化公钥对象。它接受三个参数:x 和 y 分别表示公钥在椭圆曲线上的 x 坐标和 y 坐标,curve 表示椭圆曲线对象。
toString 方法用于将公钥对象转换为字符串表示。它接受一个可选参数 compressed,默认为 True,表示是否压缩公钥。根据 compressed 的值,使用字典的 get 方法返回相应的结果:
当 compressed 为 True 时,返回公钥的 x 坐标的十六进制表示(去除开头的 '0x')。
当 compressed 为 False 时,返回将公钥的 x 和 y 坐标的十六进制表示连接起来,并使用 '0' 填充至 64 位长度的字符串。

encrypt_data逻辑
def encrypt_data(public_key, private_key, input_file, output_file):
    sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

    with open(input_file, 'rb') as f:
        data = f.read()
    print(public_key)
    print(private_key)
    print(type(public_key))
    print(type(private_key))
    encrypted_data = sm2_crypt.encrypt(data)

    with open(output_file, 'wb') as f:
        f.write(encrypted_data)

接受四个参数:public_key(公钥)、private_key(私钥)、input_file(输入文件路径)和 output_file(输出文件路径)。
创建 SM2 加密器 sm2_crypt。
打开输入文件并读取数据。
使用公钥和私钥对数据进行加密。
将加密后的数据写入输出文件。

decrypt_data逻辑
def decrypt_data(public_key, private_key, input_file, output_file):
    sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

    with open(input_file, 'rb') as f:
        encrypted_data = f.read()

    decrypted_data = sm2_crypt.decrypt(encrypted_data)

    with open(output_file, 'wb') as f:
        f.write(decrypted_data)

接受四个参数:public_key(公钥)、private_key(私钥)、input_file(输入文件路径)和 output_file(输出文件路径)。
创建 SM2 解密器 sm2_crypt。
打开输入文件并读取加密后的数据。
使用公钥和私钥对加密后的数据进行解密。
将解密后的数据写入输出文件。

sign_data逻辑
def sign_data(public_key, private_key, input_file, signature_file):
    sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

    with open(input_file, 'rb') as f:
        data = f.read()

    random_hex_str = func.random_hex(sm2_crypt.para_len)
    signature = sm2_crypt.sign(data, random_hex_str)

    with open(signature_file, 'wb') as f:
        f.write(binascii.unhexlify(signature))

接受四个参数:public_key(公钥)、private_key(私钥)、input_file(输入文件路径)和 signature_file(签名文件路径)。
创建 SM2 签名器 sm2_crypt。
打开输入文件并读取数据。
生成随机的十六进制字符串 random_hex_str,长度为签名器 sm2_crypt 的参数长度。
使用私钥对数据进行签名,得到签名结果 signature。
将签名结果写入输出文件,使用 binascii.unhexlify 将十六进制字符串转换为字节流。

verify_signature逻辑
def verify_signature(public_key, private_key, input_file, signature_file):
    sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

    with open(input_file, 'rb') as f:
        data = f.read()

    with open(signature_file, 'rb') as f:
        signature = f.read()

    if sm2_crypt.verify(binascii.hexlify(signature).decode(), data):
        print("Signature verification successful")
    else:
        print("Signature verification failed")

创建一个 SM2 加密器 sm2_crypt,使用提供的公钥和私钥初始化。
打开输入文件 input_file 并读取其中的数据。
打开签名文件 signature_file 并读取签名数据。
调用 SM2 加密器的 verify 方法验证签名。如果验证成功,则打印"Signature verification successful",否则打印"Signature verification failed"。

审批流程模块

  • 公文的审批流程设计,包括多级审批、并行审批等。

审批流程逻辑

get_documents_in_docjudge逻辑
def get_documents_in_docjudge(request):
    current_user = request.session['username']
    # 从数据库中获取所有公文

    documents = Document.objects.filter(cc_office=current_user, is_sent=1)
    # 将公文数据转换为 JSON 格式
    data = [
        {
            'current_user': current_user,
            'id': doc.document_id,
            'title': doc.document_name,
            'securityLevel': doc.security_level,
            'owner': doc.document_owner,
            'office': doc.issuing_office,
            'sendTime': doc.issue_date.strftime('%Y-%m-%d'),  # 将日期格式化为字符串
            'is_sent': doc.is_sent,
            'cc_office': doc.cc_office,
            'file_address': doc.file_address,
            'is_pass': doc.is_pass,
            # 可以添加其他字段
        }
        for doc in documents
    ]

    # 发送 JSON 格式的数据给前端
    return JsonResponse(data, safe=False)

获取当前登录用户的用户名:
从会话中获取当前登录用户的用户名。
查询数据库中需要当前用户审核的公文:
查找抄送办公室为当前用户且已发送的公文。
将公文数据转换为 JSON 格式:
将公文对象转换为字典列表,包括公文 ID、标题、安全级别、所有者、发布办公室、发布日期、是否已发送、抄送办公室、文件地址和审核状态。
返回 JSON 数据:
使用 JsonResponse 将转换后的公文数据以 JSON 格式发送给前端。

approveDocument逻辑
@csrf_exempt
def approveDocument(request):
    current_user = request.session['username']
    if request.method == 'POST':
        data = json.loads(request.body)
        document_name = data.get('documentName')

        # 添加日志打印
        print("Received document_name:", document_name)
        try:
            # 根据 document_id 获取对应的文档
            document = Document.objects.get(document_name=document_name)
            document.is_pass = 1
            document.is_sent = 1

            LogData = Log.objects.create(
                username=current_user,
                documentname=document.document_name,
                operation=f'用户{current_user}于{timezone.now()}通过了公文:{document.document_name}。'
            )
            LogData.save()

            document.save()

            return JsonResponse({'message': 'Document status updated successfully'})
        except Document.DoesNotExist:
            print(f"Document with ID {document_name} does not exist.")
            return JsonResponse({'message': 'Document does not exist'}, status=404)
        except Exception as e:
            print("Error:", e)
            return JsonResponse({'message': 'Internal Server Error'}, status=500)

    return JsonResponse({'message': 'Invalid request'}, status=400)

获取当前登录用户的用户名:从会话中获取当前登录用户的用户名 (current_user),用于记录操作日志。
处理 POST 请求:如果请求方法为 POST,则表示用户提交了批准文档的请求。解析请求体中的 JSON 数据,提取出文档名称 (documentName)。
更新文档状态:根据文档名称查询数据库,找到对应的文档对象。将文档的 is_pass 字段和 is_sent 字段都设置为 1,表示文档已经通过审批并且已发送。
记录操作日志:创建一条日志记录,记录当前用户通过了哪个文档,记录操作的类型和时间。
返回 JSON 响应:如果文档不存在,返回状态码 404 和消息 "Document does not exist"。如果发生其他异常,返回状态码 500 和消息 "Internal Server Error"。如果更新成功,返回状态码 200 和消息 "Document status updated successfully"。

rejectDocument逻辑
@csrf_exempt
def rejectDocument(request):
    current_user = request.session['username']
    if request.method == 'POST':
        data = json.loads(request.body)
        document_name = data.get('documentName')
        doc_issuing_office = data.get('issuing_office')

        # 添加日志打印
        print("Received document_name:", document_name)
        print("Received doc_issuing_office:", doc_issuing_office)
        try:
            # 根据 document_id 获取对应的文档
            document = Document.objects.get(document_name=document_name)
            document.is_pass = 0
            document.is_sent = 0
            document.document_owner = document.issuing_office

            LogData = Log.objects.create(
                username=current_user,
                documentname=document.document_name,
                operation=f'用户{current_user}于{timezone.now()}拒绝了公文:{document.document_name}。'
            )
            LogData.save()

            document.save()

            return JsonResponse({'message': 'Document status updated successfully'})
        except Document.DoesNotExist:
            print(f"Document with ID {document_name} does not exist.")
            return JsonResponse({'message': 'Document does not exist'}, status=404)
        except Exception as e:
            print("Error:", e)
            return JsonResponse({'message': 'Internal Server Error'}, status=500)

    return JsonResponse({'message': 'Invalid request'}, status=400)

获取当前登录用户的用户名:从会话中获取当前登录用户的用户名 (current_user),用于记录操作日志。
处理 POST 请求:如果请求方法为 POST,则表示用户提交了拒绝文档的请求。解析请求体中的 JSON 数据,提取出文档名称 (documentName) 和文档的发布部门 (issuing_office)。
更新文档状态:根据文档名称查询数据库,找到对应的文档对象。将文档的 is_pass 字段和 is_sent 字段都设置为 0,表示文档被拒绝并且未发送。将文档的 document_owner 字段设置为文档的发布部门,即回退至上一级审批人。
记录操作日志:创建一条日志记录,记录当前用户拒绝了哪个文档,记录操作的类型和时间。
返回 JSON 响应:如果文档不存在,返回状态码 404 和消息 "Document does not exist"。如果发生其他异常,返回状态码 500 和消息 "Internal Server Error"。如果更新成功,返回状态码 200 和消息 "Document status updated successfully"。

通知模块

  • 公文的分发和通知功能。

通知逻辑

update_document_status逻辑
@csrf_exempt
def update_document_status(request):
    current_user = request.session['username']
    if request.method == 'POST':
        data = json.loads(request.body)
        document_name = data.get('documentName')
        cc_office = data.get('ccOffice')

        # 添加日志打印
        print("Received document_name:", document_name)
        print("Received cc_office:", cc_office)
        try:
            # 根据 document_id 获取对应的文档
            document = Document.objects.get(document_name=document_name)
            document.is_sent = 1
            document.document_owner = cc_office

            LogData = Log.objects.create(
                username=current_user,
                documentname=document.document_name,
                operation=f'用户{current_user}于{timezone.now()}发送了公文:{document.document_name}。'
            )
            LogData.save()

            document.save()

            return JsonResponse({'message': 'Document status updated successfully'})
        except Document.DoesNotExist:
            print(f"Document with ID {document_name} does not exist.")
            return JsonResponse({'message': 'Document does not exist'}, status=404)
        except Exception as e:
            print("Error:", e)
            return JsonResponse({'message': 'Internal Server Error'}, status=500)

    return JsonResponse({'message': 'Invalid request'}, status=400)

获取当前登录用户的用户名:从会话中获取当前登录用户的用户名 (current_user),用于记录操作日志。
处理 POST 请求:如果请求方法为 POST,则表示用户提交了更新文档状态的请求。解析请求体中的 JSON 数据,提取出文档名称 (documentName) 和抄送办公室 (ccOffice)。
更新文档状态:根据文档名称查询数据库,找到对应的文档对象。将文档的 is_sent 字段设置为 1,表示文档已发送。将文档的 document_owner 字段设置为抄送办公室。
记录操作日志:创建一条日志记录,记录当前用户发送了哪个文档,记录操作的类型和时间。
返回 JSON 响应:如果文档不存在,返回状态码 404 和消息 "Document does not exist"。如果发生其他异常,返回状态码 500 和消息 "Internal Server Error"。如果更新成功,返回状态码 200 和消息 "Document status updated successfully"。

存档模块

  • 公文的存档和查询功能。

数据库中的日志模型

class Log(models.Model):
    username = models.CharField(max_length=8)
    documentname = models.CharField(max_length=255)
    operation = models.CharField(max_length=2000)

    def __str__(self):
        return self.operation

username:用户名,字符长度为8。
documentname:文档名称,字符长度为255。
operation:操作记录,字符长度为2000。
str:定义日志对象的字符串表示形式,这里返回操作记录。

存档逻辑

save_document逻辑
def save_document(request: HttpRequest):
    if request.method == 'POST':
        try:
            # 获取当前登录的用户
            current_user = request.session['user_id']
            current_user_name = request.session['username']

            data = json.loads(request.body)
            title = data.get('title')
            content = data.get('content')
            security_level = data.get('securityLevel')
            cc_office = data.get('cc_office')
            # print(data)
            file_address = data.get('file_address')

            # 获取当前时间
            current_time = timezone.now()
            docname = file_address + '.docx'
            # 创建一个新的文档对象
            doc = Docu()

            # 添加标题
            doc.add_heading(title, level=1)

            # 添加 HTML 内容
            doc.add_paragraph(content)

            # 保存文档
            file_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'docx', docname)
            # 在保存文档前打印文件路径
            # print("File path:", file_path)

            # 检查文档对象是否被正确创建
            # print("Document:", doc)
            doc.save(file_path)

            # 保存文档信息到数据库
            new_document = Document.objects.create(
                document_name=title,
                document_owner = current_user,
                issuing_office=current_user,
                issue_date=current_time,
                security_level=security_level,
                cc_office=cc_office,
                file_type='docx',
                modifier=current_user,
                modified_date=current_time,
                file_address=docname
            )
            new_document.save()
            print("Document saved successfully to the database.")

            key = get_or_generate_key(file_address) ##sm4密钥
            encrypt_and_hash_file(file_path, key, file_address)

            # 删除原始文件
            os.remove(file_path)
            print(f'原文件已删除:{file_path}')

            sender = UserProfile.objects.get(id=current_user)
            sender_password = sender.password_up
            sm3_hashed_password = sm3_hash_data(sender_password)[:16]
            sender_private_key = sm4_decrypt(sm3_hashed_password, sender.private_key)
            print(sender_private_key)
            sender_public_key = sender.public_key

            cc_office_user = UserProfile.objects.get(username_up=cc_office)
            cc_office_user_password = cc_office_user.password_up
            cc_sm3_hash_pw = sm3_hash_data(cc_office_user_password)[:16]
            cc_office_private_key = sm4_decrypt(cc_sm3_hash_pw, cc_office_user.private_key)
            cc_office_public_key = cc_office_user.public_key

            key_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (file_address + 'key.dat'))
            print(key_path)
            key_encrypt_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'key', (file_address + 'encryptkey.dat'))
            encrypt_data(public_key=cc_office_public_key,
                         private_key=cc_office_private_key,
                         input_file=key_path,
                         output_file=key_encrypt_path)
            os.remove(key_path)
            key_sign_path = os.path.join(settings.BASE_DIR, 'web', 'static', 'sign', (file_address + 'signkey.dat'))
            print(key_sign_path)
            sign_data(public_key=sender_public_key,
                      private_key=sender_private_key,
                      input_file=key_encrypt_path,
                      signature_file=key_sign_path)

            LogData = Log.objects.create(
                username=current_user_name,
                documentname=new_document.document_name,
                operation=f'用户{current_user_name}于{timezone.now()}创建了公文:{new_document.document_name}。'
            )
            LogData.save()

            return HttpResponse({'message': 'Document saved successfully'})  # 返回成功消息
        except Exception as e:
            traceback.print_exc()  # 打印异常信息到控制台
            return HttpResponse({'message': 'Internal Server Error'}, status=500)

    return HttpResponse({'message': 'Invalid request'}, status=400)  # 处理无效请求

检查请求方法是否为 POST,如果是,则继续处理;否则返回状态码 400。
从请求的 JSON 数据中获取文档的标题、内容、安全级别、抄送单位等信息。
创建一个 Docu 对象,并根据标题和内容添加标题和段落内容。
将文档保存到静态文件夹中的 docx 目录下,并使用与文件名相同的内容创建一个新的 Document 对象,并将其保存到数据库中。
获取或生成对称密钥,并使用该密钥加密和哈希文档。
删除原始文档文件。
使用发件人和抄送单位的私钥解密并签名对称密钥,然后将对称密钥加密并保存到文件中。
保存操作日志到数据库中,记录文档的创建操作。
如果出现异常,将异常信息打印到控制台,并返回状态码 500。
如果处理成功,返回一个成功消息。

get_documents_in_docmanager逻辑
def get_documents_in_docmanager(request):
    current_user = request.session['user_id']
    # 从数据库中获取所有公文

    documents = Document.objects.filter(document_owner=current_user, is_sent=0)
    # 将公文数据转换为 JSON 格式
    data = [
        {
            'current_user': current_user,
            'id': doc.document_id,
            'title': doc.document_name,
            'securityLevel': doc.security_level,
            'owner': doc.document_owner,
            'office': doc.issuing_office,
            'sendTime': doc.issue_date.strftime('%Y-%m-%d'),  # 将日期格式化为字符串
            'is_sent': doc.is_sent,
            'cc_office': doc.cc_office,
            'file_address': doc.file_address
            # 可以添加其他字段
        }
        for doc in documents
    ]

    # 发送 JSON 格式的数据给前端
    return JsonResponse(data, safe=False)

获取当前登录用户的用户 ID。
从数据库中获取所有当前用户拥有且未发送的文档 (is_sent=0 表示未发送)。
将文档数据转换为 JSON 格式。具体来说,对于每个文档对象,将其各个字段(如文档 ID、标题、安全级别、所有者、发文单位、发文时间、是否已发送、抄送单位和文件地址等)提取出来,放入一个字典中,组成一个文档列表。
使用 JsonResponse 将文档列表以 JSON 格式发送给前端。

get_user_logs逻辑
def get_user_logs(request):
    current_user = request.session.get('username')
    user = UserProfile.objects.get(username_up=current_user)
    print(user.username_up)

    if user.access_level == 1:  # 管理员权限
        user_logs = Log.objects.filter(documentname="无")  # 获取所有用户日志
        user_logs_data = list(user_logs.values())  # 转换为字典列表
        print(user_logs_data)
        return JsonResponse(user_logs_data, safe=False)

    if user.access_level != 1:
        user_logs = Log.objects.filter(username=user.username_up, documentname="无")
        user_logs_data = list(user_logs.values())  # 转换为字典列表
        print(user_logs_data)
        return JsonResponse(user_logs_data, safe=False)

从请求中获取当前用户的用户名(username),这里使用了 request.session.get('username') 方法。
使用当前用户的用户名从 UserProfile 模型中获取用户的详细信息,包括用户名和访问级别。
如果用户的访问级别为管理员(access_level == 1),则获取所有用户的日志信息,即过滤条件为 documentname="无" 的所有日志。
如果用户的访问级别不是管理员,则只获取该用户的日志信息,同样是过滤条件为 documentname="无" 的日志,但是加上了用户名的过滤条件。
将查询到的日志信息转换为字典列表,并通过 JsonResponse 返回给前端。

get_document_logs逻辑
def get_document_logs(request):
    current_user = request.session.get('username')
    user = UserProfile.objects.get(username_up=current_user)

    if user.access_level == 1:  # 管理员权限
        document_logs = Log.objects.exclude(documentname="无")  # 获取所有公文操作日志
        document_logs_data = list(document_logs.values())  # 转换为字典列表
        print(document_logs_data)
        return JsonResponse(document_logs_data, safe=False)
    if user.access_level != 1:
        document_logs = Log.objects.exclude(documentname="无").filter(username=user.username_up)
        document_logs_data = list(document_logs.values())  # 转换为字典列表
        print(document_logs_data)
        return JsonResponse(document_logs_data, safe=False)

从请求中获取当前用户的用户名(username),这里使用了 request.session.get('username') 方法。
使用当前用户的用户名从 UserProfile 模型中获取用户的详细信息,包括用户名和访问级别。
如果用户的访问级别为管理员(access_level == 1),则获取所有的公文操作日志信息,即过滤条件为 documentname!="无" 的所有日志。
如果用户的访问级别不是管理员,则只获取该用户相关的公文操作日志信息,同样是过滤条件为 documentname!="无" 的日志,但是加上了用户名的过滤条件。
将查询到的公文操作日志信息转换为字典列表,并通过 JsonResponse 返回给前端。

posted @ 2024-05-27 10:25  20211405周睿雅  阅读(5)  评论(0编辑  收藏  举报