HNU個人項目互評

来源:https://www.cnblogs.com/meaning-of-fragrance/archive/2023/09/20/17718734.html
-Advertisement-
Play Games

一、前言 這篇博客是對軟體工程導論的個人項目進行互評,項目要求實現一個簡單的中小學數學卷子自動生成程式。我的搭檔謝先衍同學使用Python完成了項目,而我則是使用java。儘管語言不同增加了一定的閱讀成本,但是接觸到另一種新語言並體會編程者發揮語言特性獨特的心得,確實是拓展了眼界。一個項目,最終歸結 ...


一、前言

這篇博客是對軟體工程導論的個人項目進行互評,項目要求實現一個簡單的中小學數學卷子自動生成程式。我的搭檔謝先衍同學使用Python完成了項目,而我則是使用java。儘管語言不同增加了一定的閱讀成本,但是接觸到另一種新語言並體會編程者發揮語言特性獨特的心得,確實是拓展了眼界。一個項目,最終歸結到不同問題,無論用什麼語言,面臨的問題都是一致的,但是語言的特性和編程者的思想卻是和而不同,由此給人以啟發

二、要求

用戶:

小學、初中和高中數學老師。

功能:

1、命令行輸入用戶名和密碼,兩者之間用空格隔開(程式預設小學、初中和高中各三個賬號,具體見附表),如果用戶名和密碼都正確,將根據賬戶類型顯示“當前選擇為XX出題”,XX為小學、初中和高中三個選項中的一個。否則提示“請輸入正確的用戶名、密碼”,重新輸入用戶名、密碼;

2、登錄後,系統提示“準備生成XX數學題目,請輸入生成題目數量(輸入-1將退出當前用戶,重新登錄):”,XX為小學、初中和高中三個選項中的一個,用戶輸入所需出的卷子的題目數量,系統預設將根據賬號類型進行出題。每道題目的操作數在1-5個之間,操作數取值範圍為1-100;

3、題目數量的有效輸入範圍是“10-30”(含10,30,或-1退出登錄),程式根據輸入的題目數量生成符合小學、初中和高中難度的題目的卷子(具體要求見附表)。同一個老師的卷子中的題目不能與以前的已生成的卷子中的題目重覆(以指定文件夾下存在的文件為準,見5);

4、在登錄狀態下,如果用戶需要切換類型選項,命令行輸入“切換為XX”,XX為小學、初中和高中三個選項中的一個,輸入項不符合要求時,程式控制台提示“請輸入小學、初中和高中三個選項中的一個”;輸入正確後,顯示“”系統提示“準備生成XX數學題目,請輸入生成題目數量”,用戶輸入所需出的卷子的題目數量,系統新設置的類型進行出題;

5、生成的題目將以“年-月-日-時-分-秒.txt”的形式保存,每個賬號一個文件夾。每道題目有題號,每題之間空一行;

個人項目9月17日晚上10點以前提交至創新課程管理系統。提交方式:工程文件打包,壓縮包名為“幾班+姓名.rar”。遲交2天及以內者扣分,每天扣20%。遲交2天及以上者0分。

附表-1:賬號密碼

賬戶類型 賬戶 密碼 備註
小學 張三1 123
張三2 123
張三3 123
初中 李四1 123
李四2 123
李四3 123
高中 王五1 123
王五2 123
王五3 123

附表-2:小學、初中、高中題目難度要求

小學 初中 高中
難度要求 +,-,*./ 平方,開根號 sin,cos,tan
備註 只能有+,-,*./和() 題目中至少有一個平方或開根號的運算符 題目中至少有一個sin,cos或tan的運算符

三、功能檢查

背景

環境:Python 3.11.5

軟體:VScode

登錄

命令行輸入用戶名和密碼,兩者之間用空格隔開(程式預設小學、初中和高中各三個賬號,具體見附表),如果用戶名和密碼都正確,將根據賬戶類型顯示“當前選擇為XX出題”,XX為小學、初中和高中三個選項中的一個。否則提示“請輸入正確的用戶名、密碼”,重新輸入用戶名、密碼

成功登錄的情況

測試:錯誤的賬號密碼

在錯誤的情況下,提示“請輸入正確的用戶名、密碼”

測試:不符合格式的賬號密碼

在輸入含多個空格的輸入後,判斷格式錯誤

出題

登錄後,系統提示“準備生成XX數學題目,請輸入生成題目數量(輸入-1將退出當前用戶,重新登錄):”,XX為小學、初中和高中三個選項中的一個,用戶輸入所需出的卷子的題目數量,系統預設將根據賬號類型進行出題。每道題目的操作數在1-5個之間,操作數取值範圍為1-100;

題目數量的有效輸入範圍是“10-30”(含10,30,或-1退出登錄),程式根據輸入的題目數量生成符合小學、初中和高中難度的題目的卷子(具體要求見附表)。同一個老師的卷子中的題目不能與以前的已生成的卷子中的題目重覆

生成的題目將以“年-月-日-時-分-秒.txt”的形式保存,每個賬號一個文件夾。每道題目有題號,每題之間空一行;

成功出題


成功按指定數目出題,並且附帶題號,題與題之間空有一行,在對應的用戶文件夾之下生成以時間為名的題目文件

退出

成功退出到上一頁面

測試:不在範圍內的輸入

不在範圍內的輸入會提示範圍

測試:不規範的輸入

不和規範的輸入會被認為是字元串,進而判斷是否是切換選項

測試:間隔少於1s的快速輸入

存放生成題目的文件是按時間命名的,最低單位是秒。如果快速輸入,1秒中輸入多次呢?


結果是生成了第一次的文件

切換

成功切換

生成了不同類型的題目,併成功存入對應的目錄之下

測試:錯誤輸入

匹配的字元串只有三種,此外的字元串都會觸發提示

總結

功能的測試完備,符合文檔的需求,可以說作者在編寫代碼的時候非常嫻熟精準,落實到了文檔的每一個功能實現之中。雖然在一個測試中出現了點小問題,但考慮到並非文檔指明的需求,無傷大雅。

四、代碼分析

代碼

account.py

#!/usr/bin/env python3.10.9
# -*- coding: utf-8 -*-

import json


class Account(object):
    """Account class.
  
    Attributes:
        account: The account, such as '張三1'.
        password: The password of the account.
        grade: The corresponding grade of the account, to generate exam with
            different difficuty.
    """

    def __init__(self, account, password, grade) -> None:
        """Init the account."""
        self.account = account
        self.password = password
        self.grade = grade


class Accounts(object):
    """Accounts class, to manage accounts.
  
    Attributes:
        accounts: The accounts list, read from accounts.json.
    """

    def __init__(self) -> None:
        """Read accounts from accounts.json to init the accounts list."""
        self.accounts = []
        with open("accounts.json", "r", encoding="utf-8") as f:
            accounts_dir = json.loads(f.read())
        for key in accounts_dir.keys():
            for account in accounts_dir[key]:
                self.accounts.append(
                    Account(account["account"], account["password"], key))

    def check_account(self, account, password) -> str:
        for acc in self.accounts:
            if acc.account == account and acc.password == password:
                return acc.grade
        return None

    def login(self) -> (str, str):
        """Login to the system.
      
        Returns:
            account: The account try to login in.
            grade: The corresponding grade of the account.
        """
        while True:
            account_passwd = input("請輸入用戶名和密碼,兩者之間用空格隔開: ")
            try:
                account, passwd = account_passwd.split(" ")
            except:
                print("格式錯誤!")
                continue
            grade = self.check_account(account, passwd)
            if grade is None:
                print("請輸入正確的用戶名、密碼!")
            else:
                return account, grade

優點

  1. 清晰的代碼結構: 代碼使用了面向對象的方法,使用類來組織數據和功能,使代碼具有良好的結構。
  2. 良好的註釋和文檔字元串: 代碼中有註釋和文檔字元串,解釋了類和方法的功能,這有助於其他開發人員理解代碼。
  3. 封裝性: 帳戶數據和操作被封裝在 Account​ 和 Accounts​ 類中,提高了代碼的可維護性和可擴展性。

缺點

  1. 代碼耦合度高: Accounts​ 類直接依賴於文件 I/O 和 JSON 解析,這導致了代碼的耦合度較高。最好將這些依賴項解耦,以便更容易進行單元測試和擴展。

examgenerator.py

#!/usr/bin/env python3.10.9
# -*- coding: utf-8 -*-

from abc import ABC
from abc import abstractmethod
import os
import random
import re
import time


class ExamGenerator(ABC):
    """Abstract class for exam generator.
  
    Attributes:
        operators: The operators to use.
    """

    def __init__(self) -> None:
        self.operators = ["+", "-", "*", "/"]

    @abstractmethod
    def generate(self, num_range: tuple) -> str:
        """
        Generate a math problem.
        """

    def unary_op(self, op: str, obj: str) -> str:
        """Unary operator such as ^2, sqrt, sin, cos, tan.

        If the operator is ^2, then the operator is behind the object, if the 
        operator is sqrt, sin, cos or tan, then the operator is before the
        object. Otherwise, the operator is not used.
              
        Arguments:
            op: The unary operator to use.
            obj: The object to use the unary operator.
          
        Returns:
            the result str of the object with the unary operator.
        """
        if op == "^2":
            return f"({obj})^2"
        elif op in ["sqrt", "sin", "cos", "tan"]:
            return f"{op}({obj})"
        else:
            return obj
      
    def reverse(self, prob: str) -> str:
        """Reverse the prob str if it's operators number is 2.
      
        Arguments:
            prob: The prob str to reverse.
          
        Returns:
            the reversed prob str.
        """
        pattern = r'\d+'  # Match the number using regular expression
        prob = prob.replace("^2", "^")  # To avoid the ^2 operator being matched
        matches = re.findall(pattern, prob)
        if len(matches) == 2:
            prob = prob.replace(matches[1], matches[0])
            prob = prob.replace(matches[0], matches[1], 1)
        prob = prob.replace("^", "^2")
        return prob

    def check_repeat(self, account: str, prob: str) -> bool:
        """Check if the prob is repeated.
      
        Arguments:
            account: The account using the program.
            prob: The prob str to check.
          
        Returns:
            True if the prob is repeated, otherwise False.
        """
        if os.path.exists(f"exams/{account}"):
            for file in os.listdir(f"exams/{account}"):
                with open(f"exams/{account}/{file}", "r") as f:
                    lines = f.readlines()[:-1:2]  # Remove the '\n'
                    for line in lines:
                        if (prob == line[4:-1] or  # Remove the prob index and space and '\n'
                            self.reverse(prob) == line[4:-1]):
                            return True
        return False

    def save_probs(self, account: str, probs_num: int) -> None:
        """Save the probs to the file.
      
        Arguments:
            account: The account using the program.
            probs_num: The number of probs to generate.
          
        Returns:
            None.
        """
        probs = []
        for i in range(probs_num):
            while True:
                prob = f"{self.generate()}="
                if ((prob not in probs) and
                    (not self.check_repeat(account, prob))):
                    break
            probs.append(prob)
        if not os.path.exists(f"exams/{account}"):
            os.mkdir(f"exams/{account}")
        # filename: 年-月-日-時-分-秒.txt
        filename = f"{time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime())}.txt"
        with open(f"exams/{account}/{filename}", "w") as f:
            index = 1
            for prob in probs:
                space = "  " if index < 10 else " "  # Add space to align the index
                f.write(f"{index}.{space}{prob}\n\n")
                index += 1


class ExamGenerator1(ExamGenerator):
    """Exam generator for primary school."""

    def __init__(self) -> None:
        super().__init__()

    def generate(self, nums_range=(1, 5)) -> str:
        op_num = random.randint(nums_range[0], nums_range[1])
        op = random.choice(self.operators)
        if op_num == 1:
            if nums_range[1] == 5:
                return self.generate((1, 5))  # If the result is only 1 number, generate again
            else:
                return str(random.randint(1, 100))
        elif op_num == 2:
            left_op = self.generate((1, 1))
            right_op = self.generate((1, 1))
            if random.random() < 0.45 and nums_range[1] != 5:
                return f"({left_op}{op}{right_op})"  # 45% to add brackets
            else:
                return f"{left_op}{op}{right_op}"
        else:
            left_op = self.generate((1, op_num // 2))
            right_op = self.generate((1, op_num - op_num // 2))
            if random.random() < 0.45 and nums_range[1] != 5:
                return f"({left_op}{op}{right_op})"
            else:
                return f"{left_op}{op}{right_op}"


class ExamGenerator2(ExamGenerator):
    """Exam generator for junior high school."""

    def __init__(self) -> None:
        super().__init__()
        self.operators.extend(["^2", "sqrt"])

    def generate(self, nums_range=(1, 5)) -> str:
        op_num = random.randint(nums_range[0], nums_range[1])
        op = random.choice(self.operators)
        op1 = random.choice(self.operators[:4])
        if op_num == 1:
            if op in self.operators[4:] and random.random() < 0.66:
                result = self.unary_op(op, str(random.randint(1, 100)))
            else:
                result = str(random.randint(1, 100))
        elif op_num == 2:
            left_op = self.generate((1, 1))
            right_op = self.generate((1, 1))
            if op in ["^2", "sqrt"]:
                return self.unary_op(op, f"{left_op}{op1}{right_op}")
            else:
                result = f"{left_op}{op}{right_op}"
        else:
            left_op = self.generate((1, op_num // 2))
            right_op = self.generate((1, op_num - op_num // 2))
            if op in ["^2", "sqrt"]:
                result = self.unary_op(op, f"{left_op}{op1}{right_op}")
            else:
                result = f"{left_op}{op}{right_op}"
        if (nums_range[1] == 5 and result.find("sqrt") == -1 and 
            result.find("^2") == -1):  # If the result don't contain sqrt or ^2, generate again
            return self.generate((1, 5))
        return result


class ExamGenerator3(ExamGenerator):
    """Exam generator for senior high school."""

    def __init__(self) -> None:
        super().__init__()
        self.operators.extend(["^2", "sqrt", "sin", "cos", "tan"])

    def generate(self, nums_range=(1, 5)) -> str:
        op_num = random.randint(nums_range[0], nums_range[1])
        op = random.choice(self.operators)
        op1 = random.choice(self.operators[:4])
        if op_num == 1:
            if op in self.operators[4:] and random.random() < 0.66:
                result = self.unary_op(op, str(random.randint(1, 100)))
            else:
                result = str(random.randint(1, 100))
        elif op_num == 2:
            left_op = self.generate((1, 1))
            right_op = self.generate((1, 1))
            if op in self.operators[4:]:
                result = self.unary_op(op, f"{left_op}{op1}{right_op}")
            else:
                result = f"{left_op}{op}{right_op}"
        else:
            left_op = self.generate((1, op_num // 2))
            right_op = self.generate((1, op_num - op_num // 2))
            if op in self.operators[4:]:
                result = self.unary_op(op, f"{left_op}{op1}{right_op}")
            else:
                result = f"{left_op}{op}{right_op}"
        if (nums_range[1] == 5 and result.find("sin") == -1 and 
            result.find("cos") == -1 and result.find("tan") == -1):  # If the result don't contain sin, cos or tan, generate again
            return self.generate((1, 5))
        return result

優點

1 抽象類和多態: ExamGenerator​ 是一個抽象基類,定義了一個抽象方法 generate​,並且在子類中進行了實現。這利用了Python的多態性,允許不同子類提供不同的實現

缺點

  1. 部分魔法數值: 代碼中出現了一些魔法數值,如 0.45、0.66 等,這些值沒有明確的解釋和註釋,可能會導致代碼的可讀性和可維護性降低。最好將這些數值提取為常量,並提供相關註釋。

  2. 文件操作錯誤處理不足: 代碼中的文件操作沒有足夠的錯誤處理機制,如果文件無法創建或寫入,代碼會引發異常而無法處理。

  3. 生成題目的方法命名不一致: 不同級別的生成器子類中的 generate​ 方法簽名不一致,這可能會導致混淆和錯誤。最好統一方法名。

  4. 未考慮邊界情況: 代碼中未考慮一些邊界情況,如生成的數值範圍、一元運算符的頻率等,這可能導致生成的題目不夠多樣化或有問題。

main.py

#!/usr/bin/env python3.10.9
# -*- coding: utf-8 -*-

from account import Accounts
from examgenerator import ExamGenerator
from examgenerator import ExamGenerator1
from examgenerator import ExamGenerator2
from examgenerator import ExamGenerator3


def Exam_generator(grade: str) -> ExamGenerator:
    """Return the corresponding ExamGenerator according to the grade.
  
    Arguments:
        grade: The grade of the account.
      
    Returns:
        The corresponding ExamGenerator.
    """
    if grade == "小學":
        return ExamGenerator1()
    elif grade == "初中":
        return ExamGenerator2()
    elif grade == "高中":
        return ExamGenerator3()
    else:
        return ExamGenerator()


def main():
    """Main function of the program."""
    accounts = Accounts()
    account, grade = accounts.login()

    exam_generator = Exam_generator(grade)

    prob_num = 0
    while True:
        try:
            prob_num = input(
                f"準備生成{grade}數學題目,請輸入生成題目數量(輸入-1將退出當前用戶重新登錄,輸入切換為XX可以切換身份): ")
            if prob_num.startswith("切換為"):
                if prob_num[3:] in ["小學", "初中", "高中"]:
                    grade = prob_num[3:]
                    exam_generator = Exam_generator(grade)
                else:
                    print("請輸入小學、初中和高中三個選項中的一個!")
            elif int(prob_num) >= 10 and int(prob_num) <= 30:
                exam_generator.save_probs(account, int(prob_num))
                print(f"{grade}數學題目生成完畢,已保存到exams/{account}目錄下!")
            elif int(prob_num) == -1:
                account, grade = accounts.login()
                exam_generator = Exam_generator(grade)
            else:
                print("請輸入10-30之間的數字")
        except ValueError:
            print("請輸入10-30之間的數字或切換為小學、初中和高中三個選項中的一個!")


if __name__ == '__main__':
    main()

accounts.json

{
    "小學": [
        {
            "account": "張三1",
            "password": "123"
        },
        {
            "account": "張三2",
            "password": "123"
        },
        {
            "account": "張三3",
            "password": "123"
        }
    ],
    "初中": [
        {
            "account": "李四1",
            "password": "123"
        },
        {
            "account": "李四2",
            "password": "123"
        },
        {
            "account": "李四3",
            "password": "123"
        }
    ],
    "高中": [
        {
            "account": "王五1",
            "password": "123"
        },
        {
            "account": "王五2",
            "password": "123"
        },
        {
            "account": "王五3",
            "password": "123"
        }
    ]
}

優點

模塊化設計: 代碼使用了模塊化的設計,將不同功能的代碼分別放在了不同的模塊(account​ 和 examgenerator​)中,提高了代碼的可維護性和可重用性。

Google代碼規範

大體遵守了Google Python代碼規範:

1. 模塊和函數命名

代碼中的模塊和函數命名在大多數情況下是清晰和符合規範的。例如,Accounts​ 類和 ExamGenerator​ 抽象類的命名是符合規範的。

2. 函數參數類型註釋

在一些方法中,使用了參數類型的註釋,這有助於理解參數的預期類型。這是符合Google代碼規範的一項實踐。

def generate(self, num_range: tuple) -> str:
    """
    Generate a math problem.
    """
    ...

def check_repeat(self, account: str, prob: str) -> bool:
    """Check if the prob is repeated.
  
    Arguments:
        account: The account using the program.
        prob: The prob str to check.
    
    Returns:
        True if the prob is repeated, otherwise False.
    """
    ...

3. 異常處理

碼中有一些異常處理,這是符合Google代碼規範的一項實踐。異常處理有助於處理潛在的錯誤情況。

try:
    # 異常處理代碼
except ValueError:
    print("請輸入10-30之間的數字或切換為小學、初中和高中三個選項中的一個!")

也有值得改進的地方:

文檔字元串(Docstrings)

代碼中缺少文檔字元串(Docstrings)。文檔字元串是對模塊、類、函數和方法功能的詳細描述,以及參數和返回值的說明。這是Google代碼規範強烈鼓勵的一項實踐,有助於提高代碼的可讀性和可維護性。

class Account(object):
    """Account class.
  
    Attributes:
        account: The account, such as '張三1'.
        password: The password of the account.
        grade: The corresponding grade of the account, to generate exam with
            different difficuty.
    """
    ...

class Accounts(object):
    """Accounts class, to manage accounts.
  
    Attributes:
        accounts: The accounts list, read from accounts.json.
    """
    ...

五、總結

第一次寫博客評價他人的項目,我深切感受到了寫代碼還得是一個團隊活動,人和人之間交流彼此的意見和經驗,以求共同進步,這樣的學習方式更加高效。搭檔的項目寫得很好,相比我寫的java而言,代碼更簡練優美,希望大家有所啟發。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 查詢SQL語句執行頻率 查詢 mysql 服務啟動時長 SHOW STATUS LIKE 'uptime'; 下列輸出表示服務啟動了276324秒 + + + | Variable_name | Value | + + + | Uptime | 276324 | + + + 查詢全局SQL執行的頻率 ...
  • 一、前言 MySQL的服務實現通過後臺多個線程、記憶體池、文件交互來實現對外服務的,不同線程實現不同的資源操作,各個線程相互協助,共同來完成資料庫的服務。MySQL常用的後臺線程概括如下,分為Master Thread,IO Thread,Purge Thread,Page Cleaner Threa ...
  • 眾所周知,在現實世界中,每一個資源都有其提供能力的最大上限,當單一資源達到最大上限後就得讓多個資源同時提供其能力來滿足使用方的需求。同理,在電腦世界中,單一資料庫資源不能滿足使用需求時,我們也會考慮使用多個資料庫同時提供服務來滿足需求。當使用了多個資料庫來提供服務時,最為關鍵的點是如何讓每一個數據 ...
  • web前端JavaScript交互 點擊事件 意義: JavaScript中的點擊事件是指當用戶在頁面上點擊某個元素時觸發的事件。這個事件可以用於執行各種操作,如改變元素的樣式、修改頁面內容等。這是Web應用程式中最常用 的交互方式之一,允許用戶與網頁進行交互,提高用戶體驗。 案例: 隨機點名器 知 ...
  • 工作中經常遇到按照指定格式的時間進行展示。可參考以下腳本邏輯滿足需求 Date.prototype.PtTimeByFormat = function (fmt){ var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 可選鏈運算符(?.),大家都很熟悉了,直接看個例子: const result = obj?.a?.b?.c?.d 很簡單例子,上面代碼?前面的屬性如果是空值(null或undefined),則result值是undefined,反 ...
  • import React, { useEffect, useState } from 'react'; hook 是react 16.8的新增特性 ,他可以讓你不在編寫class的情況下shiystate以及react的特性 Hooks的出現,首先解決了以下問題: 告別了令人疑惑的生命周期 告別類組 ...
  • 設計模式 學習推薦設計模式目錄:22種設計模式 (refactoringguru.cn) 圖說設計模式 — Graphic Design Patterns (design-patterns.readthedocs.io) UML類圖初見 什麼是統一建模語言(UML)? (visual-paradig ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...