首页 > 其他分享 >电子公文传输系统-进展二

电子公文传输系统-进展二

时间:2024-05-26 16:13:01浏览次数:16  
标签:公文 file 进展 request 用户 传输 user key document

上周任务完成情况

  • 改进密码存储方案,解决用户密码存储未加密问题
  • 重新商讨密钥存储方案,解决用户公私钥对存在未加密问题
  • 修改css文件,美化前端页面
  • 完善后端数据库

功能实现修改

SM3哈希算法和加盐技术来存储密码

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: 用于哈希计算的盐值。
    • hash_hex: 通过SM3算法生成的密码哈希值(十六进制)。
    • new_hash_hex: 带盐的哈希值,前4个字符和第19个字符后的部分保持不变,中间部分替换为盐值。
  • 功能: 生成带盐的SM3哈希值。增加密码存储的安全性。
  • 步骤:
  1. 使用SM3算法将密码转换为哈希值。
  2. 将生成的哈希值前4个字符和第19个字符后面的部分保持不变,将中间的部分替换为盐值。
  3. 返回带盐的哈希值。
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_string: 生成的随机字符串。
  • 功能: 生成一个指定长度的随机字符串。用作盐值或其他需要随机字符串的地方。
  • 步骤:
  1. 定义可用字符集,包括字母、数字和标点符号。
  2. 随机选择指定长度的字符组成字符串。
  3. 返回生成的随机字符串。
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')  # 替换为你的模板路径
  • 变量:
    • request: 包含请求信息的对象。
    • id_in: 用户输入的学号。
    • password_in: 用户输入的密码。
    • user_profile: 从数据库中获取的用户信息。
    • salt: 存储在数据库中的盐值。
    • salted_hash_password_in: 用户输入密码的带盐哈希值。
    • LogData: 用于记录日志的对象。
  • 功能: 处理用户登录请求。验证用户身份,并在成功登录后记录日志。
  • 步骤:
  1. 从POST请求中获取用户输入的学号和密码。
  2. 查询数据库以检查用户是否存在。如果不存在,返回错误消息。
  3. 获取存储的盐值。
  4. 使用sm3_hash_with_salt函数计算用户输入密码的带盐哈希值。
  5. 将计算出的哈希值与数据库中存储的哈希值进行比较。如果匹配,则登录成功,记录登录日志,并将用户信息存储到session中。
  6. 如果哈希值不匹配,返回错误消息。
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')  # 替换为你的模板路径
  • 变量:
    • request: 包含请求信息的对象。
    • id: 用户输入的学号。
    • username_up: 用户输入的用户名。
    • email: 用户输入的邮箱。
    • password_up: 用户输入的密码。
    • salt: 生成的随机盐值。
    • salted_hash_password: 用户输入密码的带盐哈希值。
    • priKey: 生成的私钥对象。
    • pubKey: 生成的公钥对象。
    • new_user: 新创建的用户对象。
    • LogData: 用于记录日志的对象。
  • 功能: 处理用户注册请求。生成带盐的哈希密码和公私钥,创建新用户记录,并记录注册日志。
  • 步骤:
  1. 从POST请求中获取用户输入的学号、用户名、邮箱和密码。
  2. 检查学号的唯一性。如果学号已存在,返回错误消息。
  3. 生成随机盐值。
  4. 使用sm3_hash_with_salt函数计算用户输入密码的带盐哈希值。
  5. 生成用户的公钥和私钥。
  6. 创建新的用户记录,存储学号、用户名、邮箱、带盐的哈希密码、盐值、公钥和私钥。
  7. 记录注册日志。
  8. 返回成功消息,并重定向到登录页面。
create_user 函数
from django.shortcuts import render, redirect
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib import messages
from .models import UserProfile, Log
from .forms import UserForm
from .utils import sm3_hash_with_salt, generate_random_string
import time
from some_crypto_library import PrivateKey  # 假设使用某个库生成密钥对

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})

在上述代码中,主要的改动如下:

  • 添加了对 generate_random_string 和 sm3_hash_with_salt 的调用,以生成随机盐值并计算带盐的SM3哈希值。
  • 将计算后的带盐哈希值存储在数据库中。
  • 将盐值一起存储在数据库中,以便在用户登录时进行哈希验证。
change_userinfo 函数
from django.shortcuts import render, redirect
from django.contrib import messages
from .models import UserProfile, Log
from .utils import sm3_hash_with_salt, generate_random_string
import time

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')

在上述代码中,主要的改动如下:

  • 在更新密码时,首先生成新的随机盐值,并计算带盐的SM3哈希值。
  • 将新的带盐哈希值和盐值更新到用户信息中。
  • 仅在密码不为空时才更新密码和盐值,这样可以避免不必要的密码更新。

本周计划

  • 实现认证密码“HASH+盐”,防止彩虹表攻击
  • 按计划修改和实现密钥管理方案
  • 优化代码性能
  • 继续修改css文件,美化前端页面
  • 总体调试代码,确保要求功能均能成功实现

成品展示

用户管理模块

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

数据库中的用户模型

class UserProfile(models.Model):
    id = models.CharField(max_length=8, primary_key=True)
    username_up = models.CharField(max_length=16)
    email = models.EmailField()
    password_up = models.CharField(max_length=16)
    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。
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/[email protected]/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_private_key = sender.private_key
            sender_public_key = sender.public_key

            cc_office_user = UserProfile.objects.get(username_up=cc_office)
            cc_office_private_key = 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 时,表示用户提交了保存文档的请求。
从 session 中获取当前登录用户的信息,包括用户ID和用户名。
解析请求中的 JSON 数据,包括文档标题、内容、安全级别、抄送办公室、文件地址等信息。
创建一个新的 Word 文档对象,并添加标题和内容。
将文档保存到服务器上的指定路径。
将文档信息保存到数据库中,包括文档名称、所有者、签发办公室、签发日期、安全级别、抄送办公室等信息。
获取或生成 SM4 密钥,并使用该密钥对文档进行加密和哈希。
删除原始文件。
获取当前用户和抄送办公室用户的公钥和私钥,并对密钥进行加密和签名。
记录文档保存操作到日志中。
返回一个成功消息或错误消息,取决于操作的结果。如果保存文档过程中出现异常,则会打印异常信息并返回一个内部服务器错误的消息。

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 返回给前端。

标签:公文,file,进展,request,用户,传输,user,key,document
From: https://www.cnblogs.com/icdamlkn/p/18201416

相关文章

  • 《计算机网络微课堂》2-2 物理层下面的传输媒体
    请大家注意,传输媒体不属于计算机网络体系结构的任何一层,如果非要将它添加到体系结构中,‍‍那只能将其放在物理层之下。传输媒体可分为两类:一类是导引型传输媒体,‍‍另一类是非导引型传输媒体。在导引型传输媒体中,电磁波被导引沿着固体媒体传播,‍‍常见的导引型传输媒体有同轴电......
  • 《计算机网络微课堂》2-3 传输方式
    本节课我们介绍几种传输方式:串行传输和并行传输同步传输和异步传输单工,半双工‍‍以及全双工通信​​串行我们首先来看串行传输和并行传输,串行传输是指‍‍数据是一个比特依次发送的,因此在发送端和接收端之间‍‍只需要一条数据传输线路即可。‍并行传输是指‍‍一次发送......
  • 实验二 电子公文传输系统安全-进展2
    实验二电子公文传输系统安全-进展2上周任务完成情况(代码链接,所写文档等)本周计划上周任务完成情况使用国密算法SM2,在注册账号时为用户生成公私钥对try{//设置SM2曲线参数ECGenParameterSpececGenSpec=newECGenParameterSpec("sm2p256v1");KeyPair......
  • 实验二 电子传输系统安全-进展2
    实验二电子传输系统安全-进展2上周任务学习gmssl相关知识学习加密相关知识,选择合适的国密算法部署安装gmssl中的算法库将相关算法库接口利用maven导入系统进行调用调试运行更改加密算法后的系统·学习密码项目标准进行系统优化上周任务完成情况学号姓名任务完成......
  • IceRPC之多路复用传输>快乐的RPC
    作者引言很高兴啊,我们来到了IceRPC之多路复用传输>快乐的RPC,基础引导,打好基础,才能让自已不在迷茫,快乐的畅游世界。icerpc和多路复用传输了解icerpc协议和多路复用传输icerpc协议当创建到服务器地址icerpc://hello.zeroc.com的客户端连接时,指示IceRPC建立使用ic......
  • 实验二 电子传输系统安全-进展2
    任务详情上周任务完成情况(代码链接,所写文档等)本周计划上周任务完成情况采用sm4对存储的公文进行加密处理,密钥随机生成,乱序存储在数据库中。其中sm4采用cbc模式,iv固定,跟随密文一起存储。解密的时候读取密文并分离密文和iv,然后解密。SM3加盐存储代码packagecn.edu.nuc.a......
  • 探索SPI单线传输模式:时钟线与数据传输的简化之道
    探索SPI单线传输模式:时钟线与数据传输的简化之道在当今的嵌入式系统和微控制器通信中,串行外设接口(SPI)因其高速、全双工和同步的特点而广受欢迎。然而,随着设备尺寸和复杂性的不断减少,对SPI通信的简化需求也日益增加。在这种背景下,SPI的单线传输模式成为了一个备受关注的解决方案。......
  • 实验二 电子公文传输系统安全-进展2
    实验二电子公文传输系统安全-进展2任务详情上周任务完成情况(代码链接,所写文档等)本周计划上周任务完成情况SM3加盐packagecn.edu.nuc.article.util;importorg.bouncycastle.crypto.digests.SM3Digest;importorg.bouncycastle.jce.provider.BouncyCastleProvider;......
  • 《金融评论》《税务研究》文本分析技术最新进展总结盘点
    郭豫媚,王航,郭俊杰.货币政策预期测度与央行预期管理效果评估[J].金融评论,2023,15(06):47-71+123.   关键词:预期管理;;市场预期;;政策评价;;央行沟通 摘要:宏观经济政策效果评估是健全宏观经济治理体系的重要一环。然而,预期管理作为中央银行引导预期的重要宏观调控手段,......
  • 打破传统光学 突破性进展频发Nature Science揭示光学未来新方向
       第一天光学基础STUDY第一节深度学习与光学设计 1.1光学设计概述 1.2深度学习在成像光学系统的应用 1.3深度学习在光子器件的应用 1.4深度学习光学设计的优势 1.5深度学习光学设计的发展趋势与挑战第二节光学基础 2.1几何光学基本定理 2.1.......