commit f1773af0ced18623d989f99bb5e129db43b97cb8
Author: sonorry <951452955@qq.com>
Date: Fri Oct 11 23:44:20 2024 +0800
ReadMe
diff --git a/backend/pick_student/.idea/.gitignore b/backend/pick_student/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/backend/pick_student/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/backend/pick_student/.idea/encodings.xml b/backend/pick_student/.idea/encodings.xml
new file mode 100644
index 0000000..0391594
--- /dev/null
+++ b/backend/pick_student/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pick_student/.idea/inspectionProfiles/Project_Default.xml b/backend/pick_student/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..337a9bd
--- /dev/null
+++ b/backend/pick_student/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pick_student/.idea/inspectionProfiles/profiles_settings.xml b/backend/pick_student/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/backend/pick_student/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pick_student/.idea/misc.xml b/backend/pick_student/.idea/misc.xml
new file mode 100644
index 0000000..213c1b6
--- /dev/null
+++ b/backend/pick_student/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pick_student/.idea/modules.xml b/backend/pick_student/.idea/modules.xml
new file mode 100644
index 0000000..ba025ab
--- /dev/null
+++ b/backend/pick_student/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pick_student/.idea/pick_student.iml b/backend/pick_student/.idea/pick_student.iml
new file mode 100644
index 0000000..d0876a7
--- /dev/null
+++ b/backend/pick_student/.idea/pick_student.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pick_student/__pycache__/analysis.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/analysis.cpython-38-pytest-8.3.3.pyc
new file mode 100644
index 0000000..5edc656
Binary files /dev/null and b/backend/pick_student/__pycache__/analysis.cpython-38-pytest-8.3.3.pyc differ
diff --git a/backend/pick_student/__pycache__/locustfile.cpython-38.pyc b/backend/pick_student/__pycache__/locustfile.cpython-38.pyc
new file mode 100644
index 0000000..baec1b8
Binary files /dev/null and b/backend/pick_student/__pycache__/locustfile.cpython-38.pyc differ
diff --git a/backend/pick_student/__pycache__/run.cpython-310.pyc b/backend/pick_student/__pycache__/run.cpython-310.pyc
new file mode 100644
index 0000000..4428e2b
Binary files /dev/null and b/backend/pick_student/__pycache__/run.cpython-310.pyc differ
diff --git a/backend/pick_student/__pycache__/run.cpython-38.pyc b/backend/pick_student/__pycache__/run.cpython-38.pyc
new file mode 100644
index 0000000..cae1a78
Binary files /dev/null and b/backend/pick_student/__pycache__/run.cpython-38.pyc differ
diff --git a/backend/pick_student/__pycache__/test.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/test.cpython-38-pytest-8.3.3.pyc
new file mode 100644
index 0000000..04fcc6e
Binary files /dev/null and b/backend/pick_student/__pycache__/test.cpython-38-pytest-8.3.3.pyc differ
diff --git a/backend/pick_student/__pycache__/test_login.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/test_login.cpython-38-pytest-8.3.3.pyc
new file mode 100644
index 0000000..6c67ab4
Binary files /dev/null and b/backend/pick_student/__pycache__/test_login.cpython-38-pytest-8.3.3.pyc differ
diff --git a/backend/pick_student/__pycache__/test_main.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/test_main.cpython-38-pytest-8.3.3.pyc
new file mode 100644
index 0000000..dbcf607
Binary files /dev/null and b/backend/pick_student/__pycache__/test_main.cpython-38-pytest-8.3.3.pyc differ
diff --git a/backend/pick_student/__pycache__/test_main.cpython-38.pyc b/backend/pick_student/__pycache__/test_main.cpython-38.pyc
new file mode 100644
index 0000000..1f54c12
Binary files /dev/null and b/backend/pick_student/__pycache__/test_main.cpython-38.pyc differ
diff --git a/backend/pick_student/locustfile.py b/backend/pick_student/locustfile.py
new file mode 100644
index 0000000..d61be5b
--- /dev/null
+++ b/backend/pick_student/locustfile.py
@@ -0,0 +1,65 @@
+# locustfile.py
+import random
+import string
+
+from locust import HttpUser, TaskSet, task, between
+
+class UserBehavior(TaskSet):
+
+ def on_start(self):
+ """在测试开始时执行,进行登录并存储 token 和 username"""
+ # 假设有一个登录端点,可以获取 token 和 username
+ response = self.client.post("http://127.0.0.1:8000/teacher/jwt/token", data={
+ "username": "starhun",
+ "password": "123456"
+ })
+ if response.status_code == 200:
+ self.token = response.json()["access_token"]
+ self.username = response.json().get("username")
+ else:
+ self.token = None
+ self.username = None
+
+ @task(1)
+ def get_classes(self):
+ """测试获取班级列表"""
+ if self.token:
+ with self.client.get("http://127.0.0.1:8000/teacher/pick_student/getclasses", headers={"Authorization": f"Bearer {self.token}"}) as response:
+ if response.status_code != 200:
+ response.failure(f"Failed to get classes: {response.text}")
+
+ @task(2)
+ def create_class(self):
+ characters = string.ascii_letters + string.digits
+ # 使用 random.choice() 从字符集合中随机选择字符
+ random_string = ''.join(random.choice(characters) for i in range(10))
+ """测试创建班级"""
+ if self.token:
+ with self.client.post("http://127.0.0.1:8000/teacher/pick_student/create_class", params={"class_name": random_string,"class_time":"周三 5-6节"}, headers={"Authorization": f"Bearer {self.token}"}, catch_response=True) as response:
+ if response.status_code != 200:
+ response.failure(f"Failed to create class: {response.text}")
+
+ @task(3)
+ def random_pick(self):
+ """测试随机点名"""
+ if self.token:
+ params = {"class_name": "aa"}
+ with self.client.get("http://127.0.0.1:8000/teacher/pick_student/random_pick/", params=params, headers={"Authorization": f"Bearer {self.token}"}, catch_response=True) as response:
+ if response.status_code != 200:
+ response.failure(f"Failed to random pick: {response.text}")
+
+ @task(4)
+ def upload_students(self):
+ """测试上传学生信息"""
+ if self.token:
+ files = {
+ "file": ("stduents.xlsx", open("D:\python\code\pick_student\stduents.xlsx", "rb"), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+ }
+ data = {"class_name": "班级1"}
+ with self.client.post("http://127.0.0.1:8000/teacher/pick_student/upload_students/", files=files, params=data, headers={"Authorization": f"Bearer {self.token}"}, catch_response=True) as response:
+ if response.status_code != 200:
+ response.failure(f"Failed to upload students: {response.text}")
+
+class WebsiteUser(HttpUser):
+ tasks = [UserBehavior]
+ wait_time = between(1, 5)
diff --git a/backend/pick_student/pick_student/__init__.py b/backend/pick_student/pick_student/__init__.py
new file mode 100644
index 0000000..cee49d3
--- /dev/null
+++ b/backend/pick_student/pick_student/__init__.py
@@ -0,0 +1,2 @@
+from .main import app1
+from .login import app2
\ No newline at end of file
diff --git a/backend/pick_student/pick_student/__pycache__/__init__.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..1068e95
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/__init__.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/__init__.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000..49d22d1
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/__init__.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/crud.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/crud.cpython-310.pyc
new file mode 100644
index 0000000..732bbfc
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/crud.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/crud.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/crud.cpython-38.pyc
new file mode 100644
index 0000000..51defc5
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/crud.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/database.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/database.cpython-310.pyc
new file mode 100644
index 0000000..31c22ca
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/database.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/database.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/database.cpython-38.pyc
new file mode 100644
index 0000000..4bca33b
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/database.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/login.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/login.cpython-310.pyc
new file mode 100644
index 0000000..efe9f84
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/login.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/login.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/login.cpython-38.pyc
new file mode 100644
index 0000000..271bca0
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/login.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/main.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/main.cpython-310.pyc
new file mode 100644
index 0000000..6279a92
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/main.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/main.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/main.cpython-38.pyc
new file mode 100644
index 0000000..6d92dfd
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/main.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/models.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/models.cpython-310.pyc
new file mode 100644
index 0000000..0615bd8
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/models.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/models.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/models.cpython-38.pyc
new file mode 100644
index 0000000..db6f7b5
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/models.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/progress_function.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-310.pyc
new file mode 100644
index 0000000..7d43189
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/progress_function.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-38.pyc
new file mode 100644
index 0000000..bbd0adc
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/schemas.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/schemas.cpython-310.pyc
new file mode 100644
index 0000000..50d0bce
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/schemas.cpython-310.pyc differ
diff --git a/backend/pick_student/pick_student/__pycache__/schemas.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/schemas.cpython-38.pyc
new file mode 100644
index 0000000..f8a6fc4
Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/schemas.cpython-38.pyc differ
diff --git a/backend/pick_student/pick_student/crud.py b/backend/pick_student/pick_student/crud.py
new file mode 100644
index 0000000..df4bcf5
--- /dev/null
+++ b/backend/pick_student/pick_student/crud.py
@@ -0,0 +1,68 @@
+from sqlalchemy.orm import Session
+from pick_student import models,schemas
+from pick_student.schemas import read_class
+
+
+def get_student_by_id(db: Session, stduent_id: int,class_name: str,user_name: str):
+ return db.query(models.Student).filter(models.Student.student_id == stduent_id).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).first()
+
+def get_student_by_name(db: Session, name: str):
+ return db.query(models.Student).filter(models.Student.name == name).first()
+def get_students(db: Session, class_name: str, user_name: str,skip: int = 0, limit: int = 100):
+ return db.query(models.Student).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).offset(skip).limit(limit).all()
+
+def create_student(db: Session, student: schemas.create_student):
+ db_student = models.Student(**student.dict())
+ db.add(db_student)
+ db.commit()
+ db.refresh(db_student)
+ return db_student
+def update_student(db: Session, student: schemas.update_student,class_name: str, user_name: str):
+ db_student = get_student_by_id(db, student.student_id, class_name,user_name)
+ db_student.scores = student.scores
+ db_student.consecutive_calls=student.consecutive_calls
+ db_student.is_master=student.is_master
+ db_student.updated_at=student.updated_at
+ db_student.master_uses=student.master_uses
+ db.commit()
+ db.refresh(db_student)
+ return db_student
+
+
+def delete_all_students(db: Session,class_name: str,user_name: str):
+ db.query(models.Student).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).delete()
+ db.commit()
+
+def get_class_by_id(db: Session, class_id: int):
+ return db.query(models.Class).filter(models.Class.id == class_id).first()
+def create_class(db: Session,cclass: schemas.create_class):
+ db_class = models.Class(**cclass.dict())
+ db.add(db_class)
+ db.commit()
+ db.refresh(db_class)
+ return db_class
+
+def get_class_by_name(db: Session, class_name: str,user_name: str):
+ return db.query(models.Class).filter(models.Class.class_name == class_name).filter(models.Class.user_name == user_name).first()
+
+def get_user(db: Session, username:str):
+ user= db.query(models.Teacher).filter(models.Teacher.user_name == username).first()
+ return user
+
+
+def create_teacher(db: Session, user_name: str, password_hash: str):
+ db_teacher = models.Teacher(user_name=user_name, password_hash=password_hash)
+ db.add(db_teacher)
+ db.commit()
+ db.refresh(db_teacher)
+ return db_teacher
+def get_classes_by_teacher_username(db: Session, user_name: str):
+ db_classes = db.query(models.Class).filter(models.Class.user_name == user_name).all()
+ return db_classes
+def delete_class(db: Session, class_name: str, user_name: str):
+
+ db.query(models.Student).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).delete()
+ db.commit()
+ deleted_rows=db.query(models.Class).filter(models.Class.class_name == class_name).filter(models.Class.user_name == user_name).delete()
+ print(f"Deleted rows: {deleted_rows}")
+ db.commit()
\ No newline at end of file
diff --git a/backend/pick_student/pick_student/database.py b/backend/pick_student/pick_student/database.py
new file mode 100644
index 0000000..93b47ee
--- /dev/null
+++ b/backend/pick_student/pick_student/database.py
@@ -0,0 +1,18 @@
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker,declarative_base
+SQLALCHEMY_DATABASE_URL = 'sqlite:///./pick_student.sqlite3'
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, echo=True,connect_args={'check_same_thread': False}
+)
+
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine,expire_on_commit=True)
+Base = declarative_base()
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ except Exception as e:
+ db.close()
+ raise e # 重新抛出异常
+ finally:
+ db.close()
diff --git a/backend/pick_student/pick_student/login.py b/backend/pick_student/pick_student/login.py
new file mode 100644
index 0000000..0eb9177
--- /dev/null
+++ b/backend/pick_student/pick_student/login.py
@@ -0,0 +1,94 @@
+import secrets
+from datetime import datetime, timedelta
+
+from fastapi import APIRouter, Depends, HTTPException
+from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
+from jose import jwt, JWTError
+from passlib.context import CryptContext
+from pydantic import BaseModel
+from sqlalchemy.orm import Session
+
+from pick_student.database import SessionLocal, get_db
+from pick_student.crud import get_user, create_teacher
+
+app2=APIRouter()
+
+SECRET_KEY = secrets.token_urlsafe()
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
+def verify_password(plain_password: str, hashed_password:str):
+ return pwd_context.verify(plain_password, hashed_password)
+
+def get_password_hash(password: str):
+ return pwd_context.hash(password)
+def jwt_authenticate_user(db: Session, username: str, password: str):
+ user = get_user(db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.password_hash):
+ return False
+ return user
+def create_access_token(data: dict, expires_delta=None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+@app2.post("/jwt/token", response_model=Token)
+def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
+ user = jwt_authenticate_user(db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"})
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.user_name}, expires_delta=access_token_expires
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app2.post("/jwt/register", response_model=Token)
+def register_user(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
+ # 1. 检查用户名是否已经存在
+ existing_user = get_user(db, form_data.username)
+ if existing_user:
+ raise HTTPException(status_code=400, detail="Username already registered")
+ # 2. 对密码进行哈希处理
+ hashed_password = get_password_hash(form_data.password)
+
+ # 3. 创建新的用户并存储到数据库
+ user = create_teacher(db=db, user_name=form_data.username, password_hash=hashed_password)
+
+ # 4. 注册成功后生成 access_token
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.user_name}, expires_delta=access_token_expires
+ )
+
+ # 5. 返回 access_token 和 token_type
+ return {"access_token": access_token, "token_type": "bearer","user_name": user.user_name}
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/teacher/jwt/token")
+def get_current_teacher(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
+ credentials_exception = HTTPException(
+ status_code=401,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ except JWTError:
+ raise credentials_exception
+
+ user = get_user(db, username=username)
+ if user is None:
+ raise credentials_exception
+ return user
\ No newline at end of file
diff --git a/backend/pick_student/pick_student/main.py b/backend/pick_student/pick_student/main.py
new file mode 100644
index 0000000..7f89498
--- /dev/null
+++ b/backend/pick_student/pick_student/main.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+from typing import List, Optional
+
+from cachetools import TTLCache
+from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Query, APIRouter
+import pandas as pd
+import io
+import uvicorn
+from sqlalchemy.orm import Session
+
+from pick_student import crud, models
+from pick_student.database import SessionLocal, Base, engine, get_db
+from pick_student import schemas
+from pick_student.login import get_current_teacher
+from pick_student.progress_function import call_student, weighted_random_selection
+
+
+app1 = APIRouter()
+flag_cache = TTLCache(maxsize=1, ttl=86400)
+flag_cache["is_reverse_day"] = False
+Base.metadata.create_all(bind=engine)
+
+
+
+@app1.get("/")
+def read_root():
+ return {"message": "Welcome to the Student Management System!"}
+
+
+@app1.post("/create_class", description="创建一个新班级")
+def create_classroom(class_time:str,class_name: str, db: Session = Depends(get_db),
+ current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ # 检查班级是否已经存在
+ existing_class = crud.get_class_by_name(db, class_name,current_teacher.user_name)
+ if existing_class:
+ raise HTTPException(status_code=400, detail="班级名称已存在")
+
+ # 创建班级
+ cclass = schemas.create_class(class_name=class_name, user_name=current_teacher.user_name, class_time=class_time)
+ crud.create_class(db, cclass)
+ return {"message": "班级创建成功"}
+@app1.post("/upload_students/",description="上传学生信息,包括姓名和学号,文件格式为Excel。")
+async def upload_students(class_name: str,file: UploadFile = File(...),db: Session=Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ if file.content_type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
+ raise HTTPException(status_code=400, detail="Invalid file type. Please upload an Excel file.")
+ # stdudents=crud.get_students(db, class_name)
+ # if stdudents:
+ # raise HTTPException(status_code=400, detail="Student list already exists")
+ # 读取上传的 Excel 文件
+ content = await file.read()
+ df_new = pd.read_excel(io.BytesIO(content))
+ df_new['class_name']= class_name
+ df_new['user_name']=current_teacher.user_name
+ # 确保包含所需列
+ if not {'name', 'student_id'}.issubset(df_new.columns):
+ raise HTTPException(status_code=400, detail="Excel file must contain 'name' and 'student_id' columns.")
+ for _, row in df_new.iterrows():
+ student_data = schemas.create_student(**row.to_dict())
+ crud.create_student(db,student_data)
+ # 更新全局 df
+
+ return {"message": "Student list uploaded successfully."}
+@app1.get("/students/{id}",response_model=schemas.read_student,description="根据id获取学生信息")
+def get_student(id: int,class_name: str = Query(..., description="班级名称", example="1班"), db: Session=Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ db_student = crud.get_student_by_id(db, id, class_name,user_name=current_teacher.user_name)
+ if db_student is None:
+ raise HTTPException(status_code=404, detail="Student not found")
+ db_student.updated_at = db_student.updated_at.strftime('%Y-%m-%d %H:%M:%S')
+ return db_student
+@app1.get("/get_students/",response_model=List[schemas.read_student],description="获取学生列表,默认返回前100名学生")
+def get_students(class_name: str = Query(..., description="班级名称", example="1班"),skip: int = 0, limit: int = 100, db: Session=Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ students = crud.get_students(db, class_name,skip=skip, limit=limit,user_name=current_teacher.user_name)
+ if not students:
+ raise HTTPException(status_code=404, detail="No students found or class not exit")
+ for student in students:
+ student.updated_at = student.updated_at.strftime('%Y-%m-%d %H:%M:%S')
+ return students
+
+@app1.put("/is_reverse_day/",description="是否是反转日(低分学生被点到概率翻倍,回答问题得分1.5倍)")
+def is_reverse_day(flag:Optional[bool]=False):
+ """判断今天是否是反转日"""
+ # 这里可以根据日期或其他条件决定反转日
+ # 例如每周四为反转日,或者根据某个条件触发
+ # return datetime.today().weekday() == 3 # 每周四是反转日
+ flag_cache["is_reverse_day"] = flag
+ return {"message": f"Flag has been set {flag_cache['is_reverse_day']}"}
+
+
+@app1.get("/random_pick/",description="从对应的班级随机选取一名学生,分数越高概率越低")
+def random_pick(class_name: str = Query(..., description="班级名称", example="1班"),db: Session = Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ # 获取学生列表
+ students = crud.get_students(db,class_name=class_name,user_name=current_teacher.user_name,skip=0,limit=1000)
+ if not students:
+ raise HTTPException(status_code=404, detail="No students found or class not exit")
+
+ # 随机选择学生
+ selected_student = weighted_random_selection(students)
+
+ return {"name": selected_student.name, "student_id": selected_student.student_id,"scores": selected_student.scores}
+
+
+@app1.put("/update_score",description="更新学生的积分信息,依据到达课堂、问题回答的正确性和是否重复回答进行调整。在学生的数据库表中添加一个字段 consecutive_calls 记录学生连续被点名的次数。当某个学生连续三次被点名并且都答对了(分数大于2),就会进入“知识大师”状态,降低未来三次被点名的概率,如果被点到增加1分的得分。如果今天是反转日,分数低的学生双倍概率被抽到,并得到1.5倍的回答分数")
+def update_score(class_name: str = Query(..., description="班级名称", example="1班"),
+ student_id: int = Query(..., description="学生的学号", example=102201415),
+ arrival: bool = Query(..., description="学生是否到达课堂", example=True),
+ question_repeated: bool = Query(..., description="问题是否重复回答", example=False),
+ question_correct: float = Query(..., description="问题回答的正确性评分,介于0.5到3之间", ge=0.5, le=3, example=2.5),
+ db: Session = Depends(get_db),
+ current_teacher: schemas.create_teacher = Depends(get_current_teacher)
+ ):
+ db_student = crud.get_student_by_id(db, student_id, class_name,user_name=current_teacher.user_name)
+ if db_student is None:
+ raise HTTPException(status_code=404, detail="Student not found")
+ # 更新到达课堂积分
+ if arrival:
+ db_student.scores += 1
+ # 更新回答问题积分
+ if question_repeated:
+ db_student.scores += 0.5
+ else:
+ db_student.scores -= 1
+ if question_correct>3.0 or question_correct<0.5:
+ raise HTTPException(status_code=412, detail="请输入0.5到3之间的数据")
+ else:
+ if db_student.is_master:
+ db_student.scores += 1
+ if flag_cache["is_reverse_day"]:
+ question_correct*=1.5
+ db_student.scores += question_correct
+
+ db_student=call_student(db_student,question_correct>2)
+ # 根据回答正确性加分
+ crud.update_student(db, db_student,class_name,current_teacher.user_name)
+ return {"message": "Score updated", "scores": db_student.scores,"consecutive_calls":db_student.consecutive_calls, "is_master": db_student.is_master,"master_uses":db_student.master_uses,"is_reverse_day":flag_cache["is_reverse_day"]}
+
+@app1.get("/getclasses",response_model=List[schemas.read_class],description="根据教师用户名获取全部班级")
+def get_classes(db: Session = Depends(get_db),user: schemas.create_teacher= Depends(get_current_teacher)):
+ classes = crud.get_classes_by_teacher_username(db, user.user_name)
+ if not classes:
+ raise HTTPException(status_code=404, detail="No classes found,please create class")
+ for class_ in classes:
+ class_.updated_at = class_.updated_at.strftime('%Y-%m-%d %H:%M:%S')
+ return classes
+@app1.delete("/delete_students/", description="删除所有学生的记录")
+def delete_all_students(db: Session = Depends(get_db),class_name: str = Query(..., description="班级名称", example="1班"),current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ # 调用 crud 删除所有学生函数
+ crud.delete_all_students(db, class_name,current_teacher.user_name)
+
+ return {"message": f"All students in {class_name} have been deleted."}
+@app1.delete("/delete_class/", description="删除班级")
+def delete_class(class_name: str = Query(..., description="班级名称", example="1班"),db: Session = Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)):
+ # 调用 crud 删除所有学生函数
+ crud.delete_class(db, class_name,current_teacher.user_name)
+ return {"message": f"Class {class_name} has been deleted."}
+# if __name__ == "__main__":
+# uvicorn.run(app, host="127.0.0.1", port=8000)
diff --git a/backend/pick_student/pick_student/models.py b/backend/pick_student/pick_student/models.py
new file mode 100644
index 0000000..ee57b34
--- /dev/null
+++ b/backend/pick_student/pick_student/models.py
@@ -0,0 +1,43 @@
+from sqlalchemy import func
+from sqlalchemy.orm import relationship
+from sqlalchemy.sql.schema import Column, ForeignKey
+from sqlalchemy.sql.sqltypes import Integer, String, FLOAT, DateTime, Boolean
+from pick_student.database import Base, engine
+
+class Student(Base): # 类名使用大写
+ __tablename__ = "student"
+ id = Column(Integer, primary_key=True, index=True, autoincrement=True)
+ student_id = Column(Integer, nullable=False, comment='学号')
+ name = Column(String, nullable=False, comment='姓名')
+ scores = Column(FLOAT, comment='得分', default=0)
+ consecutive_calls = Column(Integer, default=0) # 记录连续被点名的次数
+ is_master = Column(Boolean, default=False) # 是否进入知识大师状态
+ master_uses = Column(Integer, default=0) # 知识大师状态下的剩余次数
+ class_name = Column(String, ForeignKey('class.class_name'), nullable=False)
+ user_name = Column(String, ForeignKey('teacher.user_name'), nullable=False)# 外键引用修正
+ class_ = relationship("Class", back_populates="students")
+ created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
+ updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')
+ __mapped_args__ = {"order_by": id}
+
+class Teacher(Base):
+ __tablename__ = "teacher"
+ id = Column(Integer, primary_key=True, index=True, autoincrement=True)
+ user_name = Column(String, nullable=False, comment='教师用户名')
+ password_hash = Column(String, nullable=False, comment='密码')
+ created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
+ updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')
+ classes = relationship("Class", back_populates="teacher")
+
+
+# 班级表
+class Class(Base):
+ __tablename__ = "class"
+ id = Column(Integer, primary_key=True, index=True, autoincrement=True)
+ class_name = Column(String, nullable=False, comment='班级名称')
+ class_time = Column(String, nullable=False, comment='开班时间')
+ user_name = Column(String, ForeignKey('teacher.user_name'), nullable=False)
+ teacher = relationship("Teacher", back_populates="classes")
+ students = relationship("Student", back_populates="class_") # 使用 class_ 避免与类名冲突
+ created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
+ updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')
diff --git a/backend/pick_student/pick_student/progress_function.py b/backend/pick_student/pick_student/progress_function.py
new file mode 100644
index 0000000..a72faea
--- /dev/null
+++ b/backend/pick_student/pick_student/progress_function.py
@@ -0,0 +1,40 @@
+import random
+
+
+def call_student(student, correct: bool):
+ if student.is_master:
+ student.master_uses -= 1
+ if student.master_uses <= 0:
+ student.is_master = False # 退出知识大师状态
+
+ if correct:
+ student.consecutive_calls += 1
+ if student.consecutive_calls >= 3:
+ student.consecutive_calls = 0
+ student.is_master = True
+ student.master_uses = 3 # 未来三次被点名概率降低
+ else:
+ student.consecutive_calls = 0
+
+ return student
+def weighted_random_selection(students,reverse=False):
+ total_score = sum([student.scores for student in students])
+ # 计算每个学生的权重
+ probabilities = []
+ for student in students:
+ if student.is_master and student.master_uses > 0:
+ # 如果是知识大师,概率大幅降低
+ weight = (total_score - student.scores + 1) / (total_score + 1) * 0.1
+ elif reverse and student.scores <= 5:
+ # 反转机制:低分学生被点名概率增加
+ weight = (total_score - student.scores + 1) / (total_score + 1) * 2 # 概率翻倍
+ else:
+ # 正常情况下的加权选择,分数越高概率越低
+ weight = (total_score - student.scores + 1) / (total_score + 1)
+ probabilities.append(weight)
+ # 使用随机加权选择
+ selected = random.choices(students, weights=probabilities, k=1)[0]
+ return selected
+
+
+
diff --git a/backend/pick_student/pick_student/schemas.py b/backend/pick_student/pick_student/schemas.py
new file mode 100644
index 0000000..a824a01
--- /dev/null
+++ b/backend/pick_student/pick_student/schemas.py
@@ -0,0 +1,45 @@
+from datetime import datetime
+
+from pydantic import BaseModel
+class create_student(BaseModel):
+ student_id: int
+ name: str
+ class_name: str
+ user_name: str
+class update_student(create_student):
+ student_id : int
+ name: str
+ class_name: str
+ scores: float
+ consecutive_calls: int
+ master_uses: int
+ is_master: bool
+class read_student(create_student):
+ id: int
+ student_id: int
+ name: str
+ scores: float
+ updated_at: str
+ consecutive_calls: int
+ is_master:bool
+ master_uses: int
+ class Config:
+ from_attributes = True
+class create_class(BaseModel):
+ user_name: str
+ class_name: str
+ class_time: str
+ class Config:
+ from_attributes = True # 使模型与 SQLAlchemy ORM 模型兼容
+class create_teacher(BaseModel):
+ user_name: str
+ password: str
+
+
+class read_class(BaseModel):
+ id: int
+ class_name: str
+ updated_at: str
+ class_time: str
+ class Config:
+ from_attributes = True
\ No newline at end of file
diff --git a/backend/pick_student/run.py b/backend/pick_student/run.py
new file mode 100644
index 0000000..d16b5db
--- /dev/null
+++ b/backend/pick_student/run.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+import uvicorn
+from starlette.middleware.cors import CORSMiddleware
+
+from pick_student import app1,app2
+
+app=FastAPI(
+ title="学生点名系统",
+ description="学生点名系统",
+ version="1.0.0",
+)
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"], # 允许所有方法
+ allow_headers=["*"],
+)
+app.include_router(app1, prefix="/teacher/pick_student",tags=["学生点名系统"])
+app.include_router(app2, prefix="/teacher",tags=["教师登录系统"])
+if __name__=='__main__':
+ uvicorn.run('run:app',host='127.0.0.1',port=8000,reload=True)
diff --git a/backend/pick_student/stduents.xlsx b/backend/pick_student/stduents.xlsx
new file mode 100644
index 0000000..9612b78
Binary files /dev/null and b/backend/pick_student/stduents.xlsx differ
diff --git a/backend/pick_student/test_login.py b/backend/pick_student/test_login.py
new file mode 100644
index 0000000..7a9f8c6
--- /dev/null
+++ b/backend/pick_student/test_login.py
@@ -0,0 +1,86 @@
+import unittest
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from pick_student.database import Base
+from pick_student.login import get_password_hash
+from pick_student.main import get_db
+from run import app # 从 run.py 导入主 app
+from pick_student.crud import create_teacher
+
+# 设置测试数据库
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test_test.db" # 使用不同的数据库文件名
+engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+# 创建所有表
+Base.metadata.create_all(bind=engine)
+
+# 重写 get_db 函数用于测试
+def override_get_db():
+ db = TestingSessionLocal()
+ try:
+ yield db
+ except Exception as e:
+ db.close()
+ raise e # 重新抛出异常
+ finally:
+ db.close() # 确保数据库连接被正确关闭
+
+# 覆盖 app 中的依赖项
+app.dependency_overrides[get_db] = override_get_db
+
+# 创建测试客户端
+client = TestClient(app)
+
+class TestAuthRoutes(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ # 在测试开始时设置初始数据库状态
+ db = TestingSessionLocal()
+ hashed_password = get_password_hash("password") # 假设已经生成一个哈希密码
+ create_teacher(db, user_name="test_teacher", password_hash=hashed_password)
+ db.close()
+
+ def test_login_success(self):
+ # 测试登录成功的情况
+ response = client.post(
+ "/teacher/jwt/token",
+ data={"username": "test_teacher", "password": "password"},
+ )
+ print(response.json())
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertIn("access_token", data)
+ self.assertIn("token_type", data)
+
+ def test_login_failure(self):
+ # 测试登录失败的情况
+ response = client.post(
+ "/teacher/jwt/token",
+ data={"username": "wrong_teacher", "password": "wrong_password"},
+ )
+ self.assertEqual(response.status_code, 401)
+ data = response.json()
+ self.assertEqual(data["detail"], "Incorrect username or password")
+
+ def test_register_user(self):
+ # 测试注册新用户
+ response = client.post(
+ "/teacher/jwt/register",
+ data={"username": "new_teacher", "password": "new_password"},
+ )
+ print(response.json())
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertIn("access_token", data)
+ self.assertIn("token_type", data)
+
+ @classmethod
+ def tearDownClass(cls):
+ # 清理测试数据库
+ Base.metadata.drop_all(bind=engine)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/backend/pick_student/test_main.py b/backend/pick_student/test_main.py
new file mode 100644
index 0000000..d93b342
--- /dev/null
+++ b/backend/pick_student/test_main.py
@@ -0,0 +1,153 @@
+import io
+import unittest
+
+import pandas as pd
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from pick_student import crud, schemas
+from pick_student.database import Base
+from pick_student.login import get_password_hash
+from pick_student.main import get_db
+from run import app # 从 run.py 导入主 app
+from pick_student.crud import create_teacher, create_class
+
+# 设置测试数据库
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test_test.db" # 使用不同的数据库文件名
+engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+# 创建所有表
+Base.metadata.create_all(bind=engine)
+
+# 重写 get_db 函数用于测试
+def override_get_db():
+ db = TestingSessionLocal()
+ try:
+ yield db
+ except Exception as e:
+ db.close()
+ raise e # 重新抛出异常
+ finally:
+ db.close() # 确保数据库连接被正确关闭
+
+# 覆盖 app 中的依赖项
+app.dependency_overrides[get_db] = override_get_db
+
+# 创建测试客户端
+client = TestClient(app)
+class TestMainRoutes(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ # 在测试开始时设置初始数据库状态
+ db = TestingSessionLocal()
+ hashed_password = get_password_hash("password") # 假设已经生成一个哈希密码
+ create_teacher(db, user_name="test_teacher", password_hash=hashed_password)
+ crud.create_class(db, cclass=schemas.create_class(class_name="2班", user_name="test_teacher",class_time="周三 5-6节"))
+ crud.create_class(db, cclass=schemas.create_class(class_name="3班", user_name="test_teacher",class_time="周三 5-6节"))
+ crud.create_student(db, student=schemas.create_student(class_name="2班", name="Alice", student_id=101, user_name="test_teacher"))
+ crud.create_student(db, student=schemas.create_student(class_name="2班", name="Bob", student_id=102,user_name="test_teacher"))
+ crud.create_student(db, student=schemas.create_student(class_name="2班", name="Charlie", student_id=103, user_name="test_teacher"))
+ crud.create_student(db, student=schemas.create_student(class_name="3班", name="David", student_id=104, user_name="test_teacher"))
+ db.close()
+
+ def test_create_classroom(self):
+ # 测试创建班级的成功情况
+ response = client.post(
+ "/teacher/pick_student/create_class/",
+ params={"class_name": "1班","class_time":"周三 5-6节"},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json()["message"], "班级创建成功")
+
+ def test_upload_students(self):
+ # 测试上传学生信息的成功情况
+ class_name = "1班"
+ data = {
+ 'name': ['Alice', 'Bob'],
+ 'student_id': [101, 102]
+ , 'user_name': ['test_teacher', 'test_teacher']
+ }
+ df = pd.DataFrame(data)
+ excel_buffer = io.BytesIO()
+ df.to_excel(excel_buffer, index=False)
+ excel_buffer.seek(0)
+
+ response = client.post(
+ "/teacher/pick_student/upload_students/",
+ files={"file": ("students.xlsx", excel_buffer, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")},
+ params={"class_name": class_name},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json()["message"], "Student list uploaded successfully.")
+ def test_get_students(self):
+ response = client.get(
+ "/teacher/pick_student/get_students/",
+ params={"class_name": "2班"},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ self.assertEqual(response.status_code, 200)
+ def test_update_score(self):
+ response = client.put(
+ "/teacher/pick_student/update_score/",
+ params={"class_name": "2班", "student_id": 101, "arrival": True, "question_repeated": False, "question_correct": 2.5},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ print(response.json())
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("scores", response.json())
+ def test_is_reverse_day(self):
+ response = client.put(
+ "/teacher/pick_student/is_reverse_day/",
+ params={"flag": True},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ print(response.json())
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), {"message": "Flag has been set True"})
+ def test_random_pick(self):
+ # 测试随机点名的功能
+ response = client.get(
+ "/teacher/pick_student/random_pick/",
+ params={"class_name": "2班"},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ print(response.json())
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertIn("name", data)
+ self.assertIn("student_id", data)
+
+ def test_get_classes(self):
+ response = client.get(
+ "/teacher/pick_student/getclasses/",
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ def test_delete_class(self):
+ response = client.delete(
+ "/teacher/pick_student/delete_class/",
+ params={"class_name": "3班"},
+ headers={"Authorization": "Bearer " + self.get_access_token()}
+ )
+ self.assertEqual(response.status_code, 200)
+ def get_access_token(self):
+ # 获取访问令牌
+ response = client.post(
+ "/teacher/jwt/token",
+ data={"username": "test_teacher", "password": "password"},
+ )
+ return response.json()["access_token"]
+
+ @classmethod
+ def tearDownClass(cls):
+ # 清理测试数据库
+ Base.metadata.drop_all(bind=engine)
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/backend/pick_student/test_test.db b/backend/pick_student/test_test.db
new file mode 100644
index 0000000..b2ad5db
Binary files /dev/null and b/backend/pick_student/test_test.db differ
diff --git a/frontend/roll-book/.gitignore b/frontend/roll-book/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/frontend/roll-book/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/frontend/roll-book/.vscode/extensions.json b/frontend/roll-book/.vscode/extensions.json
new file mode 100644
index 0000000..a7cea0b
--- /dev/null
+++ b/frontend/roll-book/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["Vue.volar"]
+}
diff --git a/frontend/roll-book/README.md b/frontend/roll-book/README.md
new file mode 100644
index 0000000..1511959
--- /dev/null
+++ b/frontend/roll-book/README.md
@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `
+