学期结束,“物品复活软件”也进行了迭代,谈谈我的心得体会

软件工程作业:“物品复活“软件开发之PSP数据的统计

大学生经常有些物品觉得扔掉可惜,不处理又觉得浪费自己的地方。请你编写一个物品“复活”软件

该程序允许添加物品的信息(物品名称,物品描述,联系人信息),删除物品的信息,显示物品列表,也允许查找物品的信息

加分功能需求:

1、物品有公共的信息(物品名称,物品说明,物品所在地址,联系人手机,邮箱)。为了便于管理和查询,物品可以分成不同的类别(例如食品、书籍、工具等),不同类别的物品可能有不同的属性(例如食品有保质期,数量;书籍有作者,出版社等)。

2、物品复活系统有两种类型的用户:管理员和普通用户。

管理员可以设置新的物品类型(定义物品类型的名称和各个属性),修改物品类型。
普通用户在添加物品时先选择物品类型,然后再填入物品信息。普通用户搜寻物品时,需要先选择类型,再输入关键字,关键字可以再用户名称和说明中进行匹配。
普通用户需要注册(填入基本信息,包括住址,联系方式等),管理员批准后才能成为正式用户。

3、为了便于使用上述功能,软件需要提供GUI。

计划(Planning)

Estimate
明确需求和其他相关因素,指明时间成本和依赖关系
需求:

  1. 实现物品类
  2. 实现物品管理器类,管理物品列表,实现添加物品、删除物品、显示物品、查找物品等功能
  3. 实现用户类
  4. 实现用户管理器类,管理用户列表,实现用户批准等功能

时间成本:
由于本人是初学者,计划用20个小时完成代码的编写和测试,10个小时完成GUI设计和测试,5个小时用于集成测试和微小修改。

依赖关系:

  1. 实现物品对象可以在添加物品时完成
  2. 删除物品需要查找物品的功能作为基础

开发(Development)

Analysis
分析需求
本次作业的需求比较简单,使用Python内置的数据结构就可以完成,不需要第三方库及其接口。只有GUI设计需要第三方库,本次设计选用了PyQt5库。

Design Spec
生成设计文档

物品复活软件设计文档

项目概述

物品复活软件是一个简单的管理工具,允许用户添加、删除、显示和查找物品。该软件使用 Python 和 PyQt5 库构建,提供一个友好的用户界面,以便用户进行交互。

功能需求

1. 添加物品

  • 用户可以输入物品的名称、描述和联系信息。
  • 如果物品已存在,则返回提示信息。

2. 删除物品

  • 用户可以通过物品名称删除已存在的物品。
  • 如果物品不存在,则返回提示信息。

3. 显示所有物品

  • 用户可以查看所有已添加的物品列表。

4. 查找物品

  • 用户可以通过名称中的子字符串搜索物品。
  • 如果没有找到匹配的物品,则返回提示信息。

技术栈

  • 编程语言: Python
  • : PyQt5(可选,用于数据可视化)
  • 数据存储: 使用内存中的列表和字典来存储物品信息

数据结构

本软件使用面向对象设计,类图如下:

Design Review
设计复审(和同事审核设计文档)
本项目为个人独立完成,所以省略此步骤。

Coding Standard
代码规范(为目前的开发制定合适的规范)

  1. 缩进和对齐:

    • 使用四个空格进行缩进,而不是使用制表符(Tab)。这有助于保持代码在不同编辑器中的一致性。
  2. 行长度:

    • 每行代码的长度应尽量不超过 79 个字符,以提高可读性。
  3. 空行:

    • 函数之间使用一个空行进行分隔,以便更清晰地分辨不同功能。
  4. 注释:

    • 使用注释来解释复杂的逻辑或提供额外的信息。注释应简洁明了,并保持与代码的相关性。
    • 重要的功能或复杂的部分应有文档字符串(docstring)来说明其用途和参数。
  5. 异常处理:

    • 在可能出现错误的地方使用异常处理,以便优雅地处理错误并提供反馈。
  6. 变量和函数命名:

    • 采用小写字母和下划线(snake_case)命名法。例:
      • add_item
      • delete_item
      • display_items
      • search_items
  7. 参数命名:

    • 参数名称应简洁且具有描述性。例如:
      • namedescriptioncontact,这些参数名称明确表达了其内容。
  8. 模块和文件命名:

    • 文件和模块名应使用驼峰命名法,以便于识别和使用。例如:
      • MainWindow.py

Design
具体设计

  • 普通用户

    • 参与的用例:
      • 登录:普通用户可以登录系统。
      • 注册:普通用户可以注册成为新用户。
      • 添加物品:普通用户可以添加物品。
      • 删除物品:普通用户可以删除自己的物品。
      • 查询物品:普通用户可以查询物品。
      • 退出登录:普通用户可以退出系统。
      • 展示所有物品:普通用户可以查看所有物品。
      • 注销账号:普通用户可以注销自己的账号。
  • 管理员用户

    • 参与的用例:
      • 登录:管理员用户也可以登录系统。
      • 注册成为管理员:管理员用户可以通过注册成为管理员来获得更高权限。
      • 添加物品:管理员用户可以添加物品。
      • 删除物品:管理员用户可以删除物品。
      • 查询物品:管理员用户可以查询物品。
      • 展示所有物品:管理员用户可以查看所有物品。
      • 添加或修改物品类型和属性:管理员用户可以添加或修改物品的类型及其属性。
      • 批准普通用户登录:管理员用户可以批准普通用户的登录请求。
      • 退出登录:管理员用户可以退出系统。
      • 注销账号:管理员用户可以注销自己的账号。

Coding
具体编码

GUI设计

1. 主页面的初始化

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.item_manager = ItemManager()
        self.user_manager = UserManager()
        self.setWindowTitle("物品复活系统")
        self.setGeometry(100, 100, 1000, 800)

        # 创建一个 QStackedWidget 实现顺序模式
        self.stacked_widget = QStackedWidget()
        self.setCentralWidget(self.stacked_widget)

        # 添加选项卡
        self.registration_tab = self.create_registration_tab()  # 用户注册
        self.stacked_widget.addWidget(self.registration_tab)

        self.login_tab = self.create_login_tab()  # 用户登录
        self.stacked_widget.addWidget(self.login_tab)

        self.main_tab = self.create_main_tab()  # 主界面
        self.stacked_widget.addWidget(self.main_tab)

        # 初始显示用户注册界面
        self.stacked_widget.setCurrentWidget(self.registration_tab)

2. 创建注册界面

# 创建用户注册界面
    def create_registration_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()

        self.username_input = QLineEdit()
        self.password_input = QLineEdit()
        self.address_input = QLineEdit()
        self.phone_input = QLineEdit()
        self.email_input = QLineEdit()

        self.register_button = QPushButton("注册用户")
        self.register_button.clicked.connect(self.register_user)
        
        self.go_to_login_button = QPushButton("去登录")
        self.go_to_login_button.clicked.connect(self.go_to_login)
        
        # 注册成为管理员按钮
        self.register_as_admin_button = QPushButton("注册成为管理员")
        self.register_as_admin_button.clicked.connect(self.register_as_admin)

        layout.addWidget(QLabel("用户名"))
        layout.addWidget(self.username_input)
        layout.addWidget(QLabel("密码"))
        layout.addWidget(self.password_input)
        layout.addWidget(QLabel("地址"))
        layout.addWidget(self.address_input)
        layout.addWidget(QLabel("电话"))
        layout.addWidget(self.phone_input)
        layout.addWidget(QLabel("邮箱"))
        layout.addWidget(self.email_input)
        layout.addWidget(self.register_button)
        layout.addWidget(self.register_as_admin_button)
        layout.addWidget(self.go_to_login_button)

        tab.setLayout(layout)
        return tab

3. 创建用户登录界面

def create_login_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()

        self.login_username_input = QLineEdit()
        self.login_password_input = QLineEdit()

        self.login_button = QPushButton("登录")
        self.login_button.clicked.connect(self.login_user)
        
        self.go_to_register_button = QPushButton("去注册")
        self.go_to_register_button.clicked.connect(self.go_to_register)

        layout.addWidget(QLabel("用户名"))
        layout.addWidget(self.login_username_input)
        layout.addWidget(QLabel("密码"))
        layout.addWidget(self.login_password_input)
        layout.addWidget(self.login_button)
        layout.addWidget(self.go_to_register_button)

        tab.setLayout(layout)
        return tab

4. 创建主界面(包括物品管理、账户管理、管理员)

# 创建主界面
    def create_main_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()

        self.tabs = QTabWidget()
        self.item_management_tab = self.create_item_management_tab()
        self.account_management_tab = self.create_account_management_tab()
        self.tabs.addTab(self.item_management_tab, "物品管理")
        self.tabs.addTab(self.account_management_tab, "账户管理")

        layout.addWidget(self.tabs)

        tab.setLayout(layout)
        return tab

    # 创建主界面中的物品管理界面选项卡
    def create_item_management_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()
        
        self.item_name_input = QLineEdit()
        self.item_description_input = QLineEdit()
        self.item_address_input = QLineEdit()
        self.item_phone_input = QLineEdit()
        self.item_email_input = QLineEdit()

        self.item_type_combo = QComboBox()
        self.item_type_combo.addItems(self.item_manager.item_types)
        self.item_type_combo.currentIndexChanged.connect(self.update_extra_fields)
        
        # 初始选择为“食品”
        self.item_type_combo.setCurrentText("食品")
    
        self.extra_layout = QFormLayout()
        
        # 初始显示食品类型的所有属性
        attributes = self.item_manager.get_item_type_attributes("食品")

        for attr in attributes:
            input_field = QLineEdit()
            self.extra_layout.addRow(attr, input_field)

        # 添加物品按钮
        self.add_item_button = QPushButton("添加物品")
        self.add_item_button.clicked.connect(self.add_item)
        
        # 删除物品按钮
        self.delete_item_button = QPushButton("删除物品")
        self.delete_item_button.clicked.connect(self.delete_item)

        # 查询物品按钮
        self.search_item_button = QPushButton("查询物品")
        self.search_item_button.clicked.connect(self.search_item_dialog)

        # 展示全部物品按钮
        self.show_all_items_button = QPushButton("展示全部物品")
        self.show_all_items_button.clicked.connect(self.show_all_items)
        
        # 物品列表
        self.item_list_table = QTableWidget()
        self.item_list_table.setColumnCount(8)
        self.item_list_table.setHorizontalHeaderLabels(["物品名称", "描述", "地址", "联系人手机", "邮箱", "物品类型", "发布者姓名", "删除"])

        layout.addWidget(QLabel("物品名称"))
        layout.addWidget(self.item_name_input)
        layout.addWidget(QLabel("物品描述"))
        layout.addWidget(self.item_description_input)
        layout.addWidget(QLabel("物品地址"))
        layout.addWidget(self.item_address_input)
        layout.addWidget(QLabel("联系人电话"))
        layout.addWidget(self.item_phone_input)
        layout.addWidget(QLabel("联系人邮箱"))
        layout.addWidget(self.item_email_input)
        layout.addWidget(QLabel("物品类型"))
        layout.addWidget(self.item_type_combo)

        layout.addLayout(self.extra_layout)
        layout.addWidget(self.add_item_button)
        layout.addWidget(self.show_all_items_button)
        layout.addWidget(self.search_item_button)
        layout.addWidget(self.item_list_table)
        
        tab.setLayout(layout)
        return tab

    # 创建主界面中的账户管理界面选项卡
    def create_account_management_tab(self):
        tab = QWidget()
        layout = QVBoxLayout()

        self.logout_button = QPushButton("退出登录")
        self.logout_button.clicked.connect(self.logout)

        self.delete_account_button = QPushButton("注销账号")
        self.delete_account_button.clicked.connect(self.delete_account)
        
        self.admin_channel_button = QPushButton("管理员通道")
        self.admin_channel_button.clicked.connect(self.open_admin_channel)

        layout.addWidget(self.logout_button)
        layout.addWidget(self.delete_account_button)
        layout.addWidget(self.admin_channel_button)

        tab.setLayout(layout)
        return tab

# 创建主界面中的管理员选项卡
    def create_admin_tab(self):
        
        # 隐藏“管理员通道”按钮,防止重复点击
        self.admin_channel_button.setVisible(False)
        
        tab = QWidget()
        layout = QVBoxLayout()
        
        self.show_all_users_button = QPushButton("展示所有用户")
        self.show_all_users_button.clicked.connect(self.update_user_list)
        
        # 物品类型管理按钮
        self.manage_item_type_button = QPushButton("管理物品类型")
        self.manage_item_type_button.clicked.connect(self.open_item_type_management)

        user_list_table = QTableWidget()
        user_list_table.setColumnCount(7)
        user_list_table.setHorizontalHeaderLabels(["用户名", "地址", "电话", "邮箱", "账户类型", "状态", "操作"])
        
        self.user_list_table = user_list_table  # 保持对user_list_table的引用

        layout.addWidget(self.show_all_users_button)
        layout.addWidget(self.manage_item_type_button)
        layout.addWidget(user_list_table)
        tab.setLayout(layout)

        return tab

具体功能设计

1. 注册(普通用户、管理员用户)

# 注册用户
    def register_user(self):
        username = self.username_input.text()
        password = self.password_input.text()
        address = self.address_input.text()
        phone = self.phone_input.text()
        email = self.email_input.text()

        # 检查输入是否为空
        if not username or not password or not address or not phone or not email:
            QMessageBox.warning(self, '错误', "所有字段不能为空,请完整填写信息!")
            return

        # 检查用户名是否已被占用
        if self.user_manager.is_username_taken(username):
            QMessageBox.warning(self, '错误', "该用户名已被占用,请选择其他用户名。")
            return
        
        # 创建并添加用户
        user = OrdinaryUser(username, password, address, phone, email)
        self.user_manager.add_user(user)

        QMessageBox.information(self, '注册成功', f"{username} 注册成功,等待管理员批准!")
    
    # 注册成为管理员    
    def register_as_admin(self):
        username = self.username_input.text()
        
        # 检查用户名是否已被占用
        if self.user_manager.is_username_taken(username):
            QMessageBox.warning(self, '错误', "该用户名已被占用,请选择其他用户名。")
            return
        
        # 弹出对话框输入邀请码
        dialog = QDialog(self)
        dialog.setWindowTitle("输入邀请码")
        layout = QVBoxLayout()

        self.invite_code_input = QLineEdit()
        submit_button = QPushButton("提交")
        cancel_button = QPushButton("取消")

        layout.addWidget(QLabel("请输入邀请码"))
        layout.addWidget(self.invite_code_input)
        layout.addWidget(submit_button)
        layout.addWidget(cancel_button)

        dialog.setLayout(layout)

        submit_button.clicked.connect(self.check_invite_code)
        cancel_button.clicked.connect(dialog.reject)

        dialog.exec_()

    # 检查邀请码是否正确
    def check_invite_code(self):
        invite_code = self.invite_code_input.text()

        if invite_code == "SJTUCS3331":
            username = self.username_input.text()
            password = self.password_input.text()
            address = self.address_input.text()
            phone = self.phone_input.text()
            email = self.email_input.text()

            user = AdministratorUser(username, password, address, phone, email)
            self.user_manager.add_user(user)
            QMessageBox.information(self, "注册成功", f"{username} 注册成功,您已成为管理员。")
        else:
            QMessageBox.warning(self, "邀请码错误", "邀请码错误!")

2. 对账户的一系列操作(登录、退出登录、注销账号等)

# 用户登录
    def login_user(self):
        username = self.login_username_input.text()
        password = self.login_password_input.text()

        # 检查用户名是否为空
        if not username:
            QMessageBox.warning(self, '错误', "用户名不能为空!")
            return

        # 检查用户是否已经登录
        if self.item_manager.current_user is not None and self.item_manager.current_user.username == username:
            QMessageBox.warning(self, '错误', f"用户 {username} 已经登录,请勿重复登录。")
            return

        # 用户登录验证
        user = self.user_manager.authenticate_user(username, password)

        if user:
            # 检查是否是普通用户且是否被管理员批准
            if isinstance(user, OrdinaryUser) and not user.is_approved:
                QMessageBox.warning(self, '登录失败', f"用户 {username} 尚未被管理员批准,无法登录。")
                return

            self.item_manager.current_user = user
            QMessageBox.information(self, '登录成功', f"欢迎 {username}!")

            # 登录成功后,切换到主界面
            self.stacked_widget.setCurrentWidget(self.main_tab)
        else:
            QMessageBox.warning(self, '登录失败', "用户名或密码错误,请重试。")

    # 退出登录
    def logout(self):
        reply = QMessageBox.question(self, '确认退出',
                                     "你确定要退出登录吗?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            if hasattr(self, 'admin_tab') and self.admin_tab is not None:
                self.tabs.removeTab(2)
                self.admin_tab = None  # 清除管理员选项卡的引用
            self.item_manager.current_user = None
            self.stacked_widget.setCurrentWidget(self.login_tab)
            # 恢复“管理员通道”按钮的可见性
            self.admin_channel_button.setVisible(True)

    # 注销账号
    def delete_account(self):
        reply = QMessageBox.question(self, '确认注销',
                                     "你确定要注销账号吗?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            if hasattr(self, 'admin_tab') and self.admin_tab is not None:
                self.tabs.removeTab(2)
                self.admin_tab = None  # 清除管理员选项卡的引用
            self.user_manager.delete_user(self.item_manager.current_user.username)
            self.stacked_widget.setCurrentWidget(self.registration_tab)
            self.item_manager.current_user = None
            # 恢复“管理员通道”按钮的可见性
            self.admin_channel_button.setVisible(True)

3. 添加物品

# 添加物品到物品列表
    def add_item(self):
        # 获取用户输入的物品信息
        item_name = self.item_name_input.text()
        item_description = self.item_description_input.text()
        item_address = self.item_address_input.text()
        item_phone = self.item_phone_input.text()
        item_email = self.item_email_input.text()
        item_type = self.item_type_combo.currentText()

        # 检查是否有空字段
        if not item_name or not item_description or not item_address or not item_phone or not item_email:
            QMessageBox.warning(self, '错误', "所有字段不能为空,请完整填写物品信息!")
            return
        
        # 根据物品类型创建物品属性字典
        item_attributes = {}
        attributes = self.item_manager.get_item_type_attributes(item_type)
        for i, attr in enumerate(attributes):
            input_field = self.extra_layout.itemAt(i*2 + 1).widget()
            item_attributes[attr] = input_field.text()

        item = Item(item_name, item_description, item_address, item_phone, item_email, item_type, item_attributes)

        # 添加物品到物品管理器
        self.item_manager.add_item(item)

        # 展示全部物品
        self.show_all_items()

        # 清空输入框
        self.item_name_input.clear()
        self.item_description_input.clear()
        self.item_address_input.clear()
        self.item_phone_input.clear()
        self.item_email_input.clear()

        # 重置额外属性输入框
        self.update_extra_fields()
# 更新不同物品类型的额外字段
    def update_extra_fields(self):
        # 清除之前的额外字段
        index = 0
        while True:
            item = self.extra_layout.itemAt(index)
            if item is None:  # 如果找不到更多的项
                break
            widget = item.widget()
            if widget:
                widget.deleteLater()
            index += 1

        # 根据物品类型,显示不同的额外属性
        item_type = self.item_type_combo.currentText()
        attributes = self.item_manager.get_item_type_attributes(item_type)

        for attr in attributes:
            input_field = QLineEdit()
            self.extra_layout.addRow(attr, input_field)

        # 确保布局更新以反映更改
        self.extra_layout.update()  # 强制更新布局
        self.extra_layout.parent().update()  # 强制更新父布局

# 展示给定的物品列表
    def show_items(self, items):
        self.item_list_table.setRowCount(len(items))  # 设置表格行数
        for row, item in enumerate(items):
            # 设置物品的各项信息
            self.item_list_table.setItem(row, 0, QTableWidgetItem(item.name))
            self.item_list_table.setItem(row, 1, QTableWidgetItem(item.description))
            self.item_list_table.setItem(row, 2, QTableWidgetItem(item.address))
            self.item_list_table.setItem(row, 3, QTableWidgetItem(item.phone))
            self.item_list_table.setItem(row, 4, QTableWidgetItem(item.email))
            self.item_list_table.setItem(row, 5, QTableWidgetItem(item.item_type))
            self.item_list_table.setItem(row, 6, QTableWidgetItem(self.item_manager.current_user.username))

            # 添加“删除”按钮
            delete_button = QPushButton("删除")
            delete_button.clicked.connect(lambda checked, row=row: self.delete_item(row))
            self.item_list_table.setCellWidget(row, 7, delete_button)

4. 删除物品

# 删除某一件物品    
    def delete_item(self, selected_row):
        if selected_row != -1:
            item_name = self.item_list_table.item(selected_row, 0).text()
            item_owner_name = self.item_list_table.item(selected_row, 6).text()
            if self.item_manager.current_user.username != item_owner_name and not self.item_manager.current_user.is_admin:
                QMessageBox.warning(self, "警告", "普通用户不能删除其他人发布的物品。")
            elif self.item_manager.current_user.username != item_owner_name and self.item_manager.current_user.is_admin:
                self.item_manager.delete_item(item_name)  # 删除物品
                self.show_all_items()  # 更新物品列表
                QMessageBox.information(self, "成功删除", "管理员用户可以删除其他人发布的物品。")
            else:
                self.item_manager.delete_item(item_name)  # 删除物品
                self.show_all_items()  # 更新物品列表
                QMessageBox.information(self, "成功删除", "该物品已成功删除。")
        else:
            QMessageBox.warning(self, "警告", "请选择要删除的物品!")

5. 查询物品(弹出一个对话框)

# 查询物品对话框
    def search_item_dialog(self):
        dialog = QDialog(self)
        dialog.setWindowTitle("查询物品")

        layout = QVBoxLayout()

        # 物品类型选择框
        item_type_combo = QComboBox()
        item_type_combo.addItems(self.item_manager.item_types)

        # 查询字段输入框
        query_input = QLineEdit()

        submit_button = QPushButton("查询")
        cancel_button = QPushButton("取消")

        layout.addWidget(QLabel("选择物品类型"))
        layout.addWidget(item_type_combo)
        layout.addWidget(QLabel("输入查询字段"))
        layout.addWidget(query_input)
        layout.addWidget(submit_button)
        layout.addWidget(cancel_button)

        dialog.setLayout(layout)

        submit_button.clicked.connect(lambda: self.search_item(item_type_combo.currentText(), query_input.text(), dialog))
        cancel_button.clicked.connect(dialog.reject)

        dialog.exec_()

    # 执行查询物品
    def search_item(self, item_type, query_text, dialog):
        if not query_text:
            QMessageBox.warning(self, "警告", "请输入查询关键字!")
            return

        # 进行查询
        filtered_items = self.item_manager.search_item(item_type, query_text)

        if filtered_items:
            self.show_items(filtered_items)
        else:
            QMessageBox.information(self, "无结果", "没有找到匹配的物品。")

        dialog.accept()

6. 展示全部物品

# 展示全部物品    
    def show_all_items(self):
        all_items = self.item_manager.get_all_items()
        self.show_items(all_items)

7. 管理员批准普通用户登录(先进入管理员通道,在用户列表上操作)

# 管理员通道
    def open_admin_channel(self):
        if self.item_manager.current_user and self.item_manager.current_user.is_admin:
            self.admin_tab = self.create_admin_tab()
            self.tabs.addTab(self.admin_tab, "管理员")
        else:
            QMessageBox.warning(self, "权限不足", "您不是管理员,无法访问管理员通道。")

 # 更新用户列表
    def update_user_list(self):
        # 更新用户列表,展示所有用户
        user_list_table = self.user_list_table
        user_list_table.setRowCount(0)  # 清空当前表格内容

        for user in self.user_manager.get_all_users():
            row = user_list_table.rowCount()
            user_list_table.insertRow(row)
            user_list_table.setItem(row, 0, QTableWidgetItem(user.username))
            user_list_table.setItem(row, 1, QTableWidgetItem(user.address))
            user_list_table.setItem(row, 2, QTableWidgetItem(user.phone))
            user_list_table.setItem(row, 3, QTableWidgetItem(user.email))
            user_list_table.setItem(row, 4, QTableWidgetItem("普通用户" if not user.is_admin else "管理员"))
            user_list_table.setItem(row, 5, QTableWidgetItem("待批准" if isinstance(user, OrdinaryUser) and not user.is_approved else "已批准"))

            if isinstance(user, OrdinaryUser) and not user.is_approved:
                approve_button = QPushButton("批准")
                approve_button.clicked.connect(lambda _, u=user: self.approve_user(u))
                user_list_table.setCellWidget(row, 6, approve_button)

    # 批准普通用户登录
    def approve_user(self, user):
        if isinstance(user, OrdinaryUser):
            self.user_manager.approve_user(user.username)
            QMessageBox.information(self, '成功', f"{user.username} 已经批准成为正式用户。")

            # 在 user_list_table 中找到该用户的行,并更新“状态”列为“已批准”
            for row in range(self.user_list_table.rowCount()):
                if self.user_list_table.item(row, 0).text() == user.username:  # 查找用户名
                    self.user_list_table.setItem(row, 5, QTableWidgetItem("已批准"))  # 更新“状态”列

8. 管理物品类型:添加新的类型或更新新的类型对话框

管理员新增的物品类型必须更新到ItemManager类的item_type属性,并且GUI的“物品管理界面”必须被更新。

# 管理物品类型:添加新的类型或更新新的类型对话框
    def open_item_type_management(self):
        dialog = QDialog(self)
        dialog.setWindowTitle("管理物品类型")
        layout = QVBoxLayout()

        # 输入框
        self.new_item_type_name = QLineEdit()
        self.new_item_type_name.setPlaceholderText("物品类型名称")

        self.new_item_type_attributes = QLineEdit()
        self.new_item_type_attributes.setPlaceholderText("属性列表,用逗号分隔")

        submit_button = QPushButton("提交")
        cancel_button = QPushButton("取消")

        layout.addWidget(QLabel("请输入物品类型名称"))
        layout.addWidget(self.new_item_type_name)
        layout.addWidget(QLabel("请输入属性列表"))
        layout.addWidget(self.new_item_type_attributes)
        layout.addWidget(submit_button)
        layout.addWidget(cancel_button)

        dialog.setLayout(layout)

        submit_button.clicked.connect(self.add_or_update_item_type)
        cancel_button.clicked.connect(dialog.reject)

        dialog.exec_()

    # 添加新的类型或更新新的类型
    def add_or_update_item_type(self):
        type_name = self.new_item_type_name.text()
        attributes = self.new_item_type_attributes.text().split(',')

        if not type_name or not attributes:
            QMessageBox.warning(self, "错误", "物品类型名称和属性不能为空!")
            return

        if type_name in self.item_manager.item_types:
            self.item_manager.update_item_type(type_name, attributes)
            QMessageBox.information(self, "成功", f"物品类型 {type_name} 已更新!")
        else:
            self.item_manager.add_item_type(type_name, attributes)
            QMessageBox.information(self, "成功", f"物品类型 {type_name} 已添加!")
            # 更新物品类型选择框
            self.item_type_combo.addItem(type_name)
        
        # 重新更新额外属性输入框
        self.update_extra_fields()

完整代码已按照教师要求上传到GitHub。

Code Review
代码复审
找到了一些小BUG,修改了返回字符串的措辞。不再赘述。

Test
测试(包括自测,修改代码,提交修改)
测试上述功能,发现BUG后再修改代码。

记录用时(Record Time Spent)

比预计的时间稍长一些,实际用30个小时完成代码的编写和GUI设计和测试,2个小时用于集成测试和微小修改。

测试报告(Test Report)

物品复活软件测试报告

项目概述

物品复活软件是一款使用 Python 和 PyQt5 库开发的应用程序,允许用户添加、删除、显示和查找物品。该软件通过用户友好的图形界面简化了物品管理的过程。

测试报告:物品复活软件

1. 测试目的

本次测试的目的是验证物品复活软件在不同操作场景下的功能和稳定性。通过模拟用户与系统交互,确保各个功能模块的正常运行,并确保用户界面(GUI)操作符合预期的行为和要求。

2. 测试环境

  • 操作系统:Windows 10
  • 软件版本:v1.0
  • 开发框架:PyQt5
  • 数据库:无(使用内存数据存储)
  • 测试工具:手动测试
  • 目标硬件:普通PC

3. 测试用例

以下是主要的GUI操作测试用例,涵盖了用户的常见操作流程,包括登录、注册、物品添加、查询、删除等。

3.1 登录操作

  • 步骤
    1. 用户点击“登录”按钮。
    2. 在弹出的登录窗口中,输入用户名和密码,点击“登录”。
  • 输入
    • 用户名:testuser
    • 密码:password123
  • 预期输出
    • 用户成功登录,进入主界面,显示“欢迎,testuser”。

3.2 注册操作

  • 步骤
    1. 用户点击“注册”按钮。
    2. 在弹出的注册窗口中,输入用户名、密码、地址、电话等信息,点击“注册”。
  • 输入
    • 用户名:newuser
    • 密码:newpassword123
    • 地址:Shanghai
    • 电话:123456789
    • 邮箱:newuser@example.com
  • 预期输出
    • 用户注册成功,提示“注册成功”,用户自动登录并进入主界面。

3.3 添加物品操作

  • 步骤
    1. 用户点击“添加物品”按钮。
    2. 在弹出的物品添加窗口中,输入物品名称、描述、联系方式等信息,点击“提交”。
  • 输入
    • 物品名称:旧书
    • 描述:一本二手书
    • 联系电话:987654321
    • 邮箱:oldbook@example.com
  • 预期输出
    • 物品添加成功,弹出提示框“物品添加成功”。

3.4 删除物品操作

  • 步骤
    1. 用户点击“删除物品”按钮。
    2. 在弹出的删除物品窗口中,选择要删除的物品,点击“删除”按钮。
  • 输入
    • 选择删除物品:旧书
  • 预期输出
    • 物品成功删除,弹出提示框“物品删除成功”。

3.5 查询物品操作

  • 步骤
    1. 用户点击“查询物品”按钮。
    2. 在弹出的查询窗口中,输入查询条件并点击“查询”按钮。
  • 输入
    • 查询条件:
  • 预期输出
    • 显示所有匹配条件的物品列表,如“旧书”。

3.6 展示所有物品操作

  • 步骤
    1. 用户点击“展示所有物品”按钮。
  • 输入
  • 预期输出
    • 显示所有物品的列表,包括所有已添加的物品。

3.7 注销账号操作

  • 步骤
    1. 用户点击“注销账号”按钮。
    2. 在弹出的确认框中,点击“确认注销”。
  • 输入
  • 预期输出
    • 用户账号成功注销,返回到注册/登录界面。

3.8 退出登录操作

  • 步骤
    1. 用户点击“退出登录”按钮。
  • 输入
  • 预期输出
    • 用户成功退出登录,返回到登录界面。

3.9 注册成为管理员

  • 步骤
    1. 用户点击“申请成为管理员”按钮。
    2. 输入邀请码并提交申请。
  • 输入
    • 邀请码:admininvite
  • 预期输出
    • 显示“管理员申请已提交,等待审批”。

4. 测试用例表

用例编号 操作 输入 预期输出
TC01 登录操作 用户名: testuser, 密码: password123 成功登录,显示“欢迎,testuser”
TC02 注册操作 用户名: newuser, 密码: newpassword123,地址: Shanghai, 电话: 123456789, 邮箱: newuser@example.com 注册成功,显示“注册成功”,并自动登录进入主界面
TC03 添加物品操作 物品名称: 旧书, 描述: 一本二手书, 电话: 987654321, 邮箱: oldbook@example.com 物品添加成功,弹出提示框“物品添加成功”
TC04 删除物品操作 物品名称: 旧书 物品删除成功,弹出提示框“物品删除成功”
TC05 查询物品操作 查询条件: 显示所有匹配条件的物品列表,如“旧书”
TC06 展示所有物品操作 显示所有物品的列表
TC07 注销账号操作 用户成功注销账号,返回到注册/登录界面
TC08 退出登录操作 用户成功退出登录,返回到登录界面
TC09 注册成为管理员操作 邀请码: admininvite 显示“管理员申请已提交,等待审批”

结论

所有测试用例均通过,物品复活软件功能正常,符合预期。Gradio 用户界面友好,所有输入与输出均能准确响应。

计算工作量(Size Measurement)

一共有5个模块:

  • MainWindow.py: 620行代码;
  • Item.py: 18行代码;
  • ItemManager.py: 49行代码;
  • User.py: 27行代码;
  • UserManager.py: 41行代码;

事后总结(Postmortem)

由于我是第一次使用软件工程课上学到的软件开发生命过程来开发软件的,所以即使这是一个很小的项目,依然显得不太熟练。在最一开始设计设计文档的时候总是图快捷图简便,想尽快上手写代码,导致我的设计文档其实最一开始是不全的。以至于很多功能明明在一开始设计的时候就能想到,但是一定要到测试的时候才能发现问题。

今后在设计的过程应该一定要记住,磨刀不误砍柴工。设计文档写的完全,那么在今后的工作量就会减少,不用反复的修改bug。

提出过程改进计划(Process Improvement Plan)

2.6 尚未解决的问题

  1. 数据持久化
  • 问题 :当前系统未涉及数据持久化,所有的用户、物品和设置数据仅在内存中存储。即便系统的功能设计完备,一旦系统关闭,所有数据都会丢失。

  • 解决方案:为了将系统转化为一个可用的应用,必须加入数据库支持,确保数据可以持久化存储。常见的解决方案包括使用关系型数据库(如MySQL或SQLite)来存储用户信息、物品信息等,或者使用文件系统存储(例如JSON、XML或CSV格式)。

  1. 用户验证与安全
  • 问题 :虽然有登录功能,但未涉及用户的密码加密、安全认证、账户安全等问题。普通用户和管理员权限的验证较为简单,且没有防止恶意用户滥用系统的安全措施。

  • 解决方案:应考虑实现用户密码加密存储(如使用哈希算法如SHA-256),以及更复杂的身份验证机制(如OAuth、双重认证等)。此外,还应加入账户锁定机制、防止SQL注入等安全防护措施,确保系统的安全性。

  1. 界面与用户体验
  • 问题 :系统当前的界面设计可能较为简陋,尤其是对于复杂操作和数据展示(如物品列表、用户管理等),用户的体验可能不佳。

  • 解决方案:需要进一步优化UI设计,提高系统的易用性。例如,可以为每个功能添加更多的提示信息和反馈消息,使用户能够更加方便地理解操作。此外,还可以考虑使用现代UI框架(如Qt、React等)来提升系统界面的美观度和响应性。

  1. 权限管理
  • 问题 :目前系统的管理员权限管理过于简单,管理员可以执行多种操作,但没有明确的权限控制机制(如分级管理员、特定功能权限等)。

  • 解决方案:应进一步细化权限管理,允许为不同的用户分配不同的权限。例如,可以设置不同级别的管理员权限,或者将某些操作权限限制在特定的用户组或角色上。可以采用基于角色的访问控制(RBAC)来实现这一功能。

  1. 异常处理与日志记录
  • 问题 :系统的异常处理不够完善,错误和异常没有足够的捕捉和反馈机制。此外,系统的操作日志和错误日志也未记录,这可能在发生问题时导致调试困难。

  • 解决方案:需要加入完善的异常处理机制,确保系统在出现问题时能够友好地反馈给用户,并避免系统崩溃。同时,需要实现日志记录功能,记录用户的操作行为和系统运行的异常,以便开发人员进行调试和维护。

  1. 系统扩展性与维护
  • 问题 :目前系统的架构看起来相对简单,可能在未来随着功能扩展而变得难以维护和扩展。例如,物品类型和属性的管理可能在后续增加新特性时显得不够灵活。

  • 解决方案:可以通过模块化设计提高系统的扩展性,确保系统能够随着需求变化进行灵活扩展。通过采用设计模式(如工厂模式、策略模式等)来管理不同功能模块,提升代码的可维护性和可扩展性。

  1. 性能优化
  • 问题 :随着用户量和物品量的增加,系统可能面临性能瓶颈,特别是在展示所有物品或查询物品时,若数据量过大,响应时间可能变长。

  • 解决方案:需要考虑数据库优化(如索引、查询缓存等)和前端性能优化(如分页加载、大数据量处理等)。还可以通过引入异步处理和并行计算来提高系统的响应速度。

  1. 测试与质量保障
  • 问题 :当前的系统并未体现出测试框架,系统的质量保障机制较弱,可能导致在开发过程中出现错误未被及时发现。

  • 解决方案:应增加单元测试、集成测试和系统测试等自动化测试手段,确保系统在不同条件下的可靠性。同时,应加入持续集成(CI)和持续交付(CD)机制,保证代码的高质量和稳定性。

  1. 跨平台支持
  • 问题 :当前系统是否可以在不同的操作系统上运行未提及。若是使用特定平台的技术(如Windows的某些库),可能导致系统无法在其他平台(如Linux或MacOS)上运行。

  • 解决方案:如果要实现跨平台支持,应选择适合跨平台开发的框架(如Qt或Electron)进行开发,或通过容器化技术(如Docker)确保系统在不同平台上的一致性。

  1. API与第三方集成
  • 问题 :目前系统没有提到与外部服务或API的集成,例如与支付系统、电子邮件发送服务或其他系统的集成。

  • 解决方案:可以在系统中加入与第三方服务的集成功能(如通过API与外部支付平台或通知系统进行通信),这将使系统功能更加丰富,提升用户体验。

posted @ 2025-01-06 08:35  Xiachou_Tai  阅读(25)  评论(0)    收藏  举报