commit 62560795af91f3f5df00479c1660bc84ad602416 Author: 冼耀文 <17608268+xyw109@user.noreply.gitee.com> Date: Mon Sep 21 19:12:31 2026 +0800 学生管理系统 diff --git a/.backend-log.tmp b/.backend-log.tmp new file mode 100644 index 0000000..b67d6cf Binary files /dev/null and b/.backend-log.tmp differ diff --git a/.backend-log2.tmp b/.backend-log2.tmp new file mode 100644 index 0000000..5c473d5 --- /dev/null +++ b/.backend-log2.tmp @@ -0,0 +1,80 @@ +docker : INFO: Started server process [1] +所在位置 行:445 字符: 1 ++ docker logs --tail 40 student-backend > D:\fastapi-app\.backend-log.t ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (INFO: Started server process + [1]:String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +INFO: Waiting for application startup. +ERROR: Traceback (most recent call last): + File "/usr/local/lib/python3.10/site-packages/starlette/routing.py", line 693 +, in lifespan + async with self.lifespan_context(app) as maybe_state: + File "/usr/local/lib/python3.10/contextlib.py", line 199, in __aenter__ + return await anext(self.gen) + File "/usr/local/lib/python3.10/site-packages/fastapi/routing.py", line 206, +in merged_lifespan + async with original_context(app) as maybe_original_state: + File "/usr/local/lib/python3.10/contextlib.py", line 199, in __aenter__ + return await anext(self.gen) + File "/usr/local/lib/python3.10/site-packages/tortoise/contrib/fastapi/__init +__.py", line 271, in orm_lifespan + async with RegisterTortoise( + File "/usr/local/lib/python3.10/site-packages/tortoise/contrib/fastapi/__init +__.py", line 177, in __aenter__ + await self.init_orm() + File "/usr/local/lib/python3.10/site-packages/tortoise/contrib/fastapi/__init +__.py", line 166, in init_orm + await Tortoise.generate_schemas() + File "/usr/local/lib/python3.10/site-packages/tortoise/__init__.py", line 593 +, in generate_schemas + await generate_schema_for_client(connection, safe) + File "/usr/local/lib/python3.10/site-packages/tortoise/utils.py", line 46, in + generate_schema_for_client + await generator.generate_from_string(schema) + File "/usr/local/lib/python3.10/site-packages/tortoise/backends/base/schema_g +enerator.py", line 504, in generate_from_string + await self.client.execute_script(creation_string) + File "/usr/local/lib/python3.10/site-packages/tortoise/backends/mysql/client. +py", line 50, in translate_exceptions_ + return await func(self, *args) + File "/usr/local/lib/python3.10/site-packages/tortoise/backends/mysql/client. +py", line 221, in execute_script + async with self.acquire_connection() as connection: + File "/usr/local/lib/python3.10/site-packages/tortoise/backends/base/client.p +y", line 366, in __aenter__ + await self.ensure_connection() + File "/usr/local/lib/python3.10/site-packages/tortoise/backends/base/client.p +y", line 363, in ensure_connection + await self.client.create_connection(with_db=True) + File "/usr/local/lib/python3.10/site-packages/tortoise/backends/mysql/client. +py", line 126, in create_connection + self._pool = await mysql.create_pool(password=self.password, **self._templa +te) + File "/usr/local/lib/python3.10/site-packages/aiomysql/pool.py", line 29, in +_create_pool + await pool._fill_free_pool(False) + File "/usr/local/lib/python3.10/site-packages/aiomysql/pool.py", line 182, in + _fill_free_pool + conn = await connect(echo=self._echo, loop=self._loop, +TypeError: connect() got an unexpected keyword argument 'serverTimezone' + +ERROR: Application startup failed. Exiting. +docker : mysql: [Warning] Using a password on the command line interface can be + insecure. +所在位置 行:445 字符: 79 ++ ... g.tmp 2>&1; docker exec student-mysql mysql -uroot -p123456 -e "SHOW ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (mysql: [Warning...an be insecure. + :String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +Database +information_schema +mysql +performance_schema +student_system +sys +@@global.time_zone @@system_time_zone @@global.system_time_zone +SYSTEM CST CST diff --git a/.dk1.tmp b/.dk1.tmp new file mode 100644 index 0000000..ccbd328 --- /dev/null +++ b/.dk1.tmp @@ -0,0 +1,52 @@ +student-frontend||student_system-frontend||Up 3 hours||3.13MB (virtual 72.3MB) +student-backend||student_system-backend||Up 4 minutes||381kB (virtual 223MB) +student-mysql||mysql:8.0||Up 5 hours (healthy)||36.9kB (virtual 852MB) +web||nginx||Exited (255) 6 hours ago||81.9kB (virtual 184MB) +redis-server||redis||Exited (0) 8 hours ago||12.3kB (virtual 155MB) +student-ms-mysql||mysql:8.0||Exited (137) 8 hours ago||24.6kB (virtual 852MB) +milvus-standalone||milvusdb/milvus:v2.6.1||Exited (137) 8 hours ago||49.2kB (virtual 2.31GB) +milvus-etcd||quay.io/coreos/etcd:v3.5.18||Exited (0) 8 hours ago||24.6kB (virtual 63.8MB) +milvus-minio||minio/minio:RELEASE.2024-05-28T17-19-04Z||Exited (0) 8 hours ago||28.7kB (virtual 163MB) +docker-nginx-1||d0d674272be3||Up 6 hours||94.2kB (virtual 184MB) +docker-worker-1||langgenius/dify-api:1.17.1||Up 6 hours||88.3MB (virtual 3.25GB) +docker-api-1||langgenius/dify-api:1.17.1||Up 6 hours (healthy)||80.5MB (virtual 3.24GB) +docker-agent_backend-1||langgenius/dify-agent-backend:1.17.1||Up 6 hours||4.1kB (virtual 1.16GB) +docker-api_websocket-1||langgenius/dify-api:1.17.1||Up 6 hours||81.9MB (virtual 3.24GB) +docker-worker_beat-1||langgenius/dify-api:1.17.1||Up 6 hours||84.8MB (virtual 3.24GB) +docker-plugin_daemon-1||langgenius/dify-plugin-daemon:0.6.10-local||Up 6 hours||28.7kB (virtual 1.68GB) +docker-agent_ssrf_proxy-1||ubuntu/squid:latest||Up 6 hours||45.1kB (virtual 233MB) +docker-ssrf_proxy-1||ubuntu/squid:latest||Up 6 hours||946kB (virtual 234MB) +docker-redis-1||redis:6-alpine||Up 6 hours (healthy)||4.1kB (virtual 29.9MB) +docker-web-1||langgenius/dify-web:1.17.1||Up 6 hours||4.1kB (virtual 703MB) +docker-db_postgres-1||postgres:15-alpine||Up 6 hours (healthy)||20.5kB (virtual 301MB) +docker-sandbox-1||langgenius/dify-sandbox:0.2.15||Up 6 hours (healthy)||362MB (virtual 991MB) +docker-local_sandbox-1||langgenius/dify-agent-local-sandbox:1.17.1||Up 6 hours (healthy)||4.1kB (virtual 587MB) +docker-init_permissions-1||busybox:latest||Exited (0) 28 hours ago||16.4kB (virtual 4.62MB) +docker-weaviate-1||cr.weaviate.io/semitechnologies/weaviate:1.39.2||Up 6 hours||20.5kB (virtual 209MB) +student_system-backend:latest||c4c1dbc5b269||296MB||3 hours ago +student_system-frontend:latest||7dbfeaac7bb2||96.2MB||5 hours ago +stu_test:latest||03f4f4d09323||296MB||6 hours ago +:||d0d674272be3||250MB||2 days ago +nginx:latest||cb29e33d254e||250MB||2 days ago +langgenius/dify-web:1.17.1||6353fe8e0448||904MB||8 days ago +langgenius/dify-agent-backend:1.17.1||a30c039bbead||1.49GB||8 days ago +langgenius/dify-api:1.17.1||ceede5b903af||4.08GB||8 days ago +langgenius/dify-agent-local-sandbox:1.17.1||f4d404c9dcdf||782MB||2 weeks ago +cr.weaviate.io/semitechnologies/weaviate:1.39.2||5c62e5cbce4c||300MB||3 weeks ago +redis:latest||298e5b3bc566||210MB||3 weeks ago +redis:6-alpine||d0c875bdacfb||41.3MB||4 weeks ago +postgres:15-alpine||fe0737ba566a||416MB||5 weeks ago +langgenius/dify-plugin-daemon:0.6.10-local||34412a22d1e1||2.28GB||6 weeks ago +busybox:latest||dc2d74b28e4c||6.81MB||4 months ago +mysql:8.0||7dcddc01f13b||1.09GB||4 months ago +langgenius/dify-sandbox:0.2.15||750e1111426e||848MB||4 months ago +ubuntu/squid:latest||6a097f68bae7||303MB||9 months ago +milvusdb/milvus:v2.6.1||4251cb095b89||3.24GB||12 months ago +quay.io/coreos/etcd:v3.5.18||d0a641d5fbcc||86.2MB||20 months ago +quay.io/minio/minio:RELEASE.2024-05-28T17-19-04Z||391d1d45fdbe||220MB||2 years ago +minio/minio:RELEASE.2024-05-28T17-19-04Z||391d1d45fdbe||220MB||2 years ago +TYPE TOTAL ACTIVE SIZE RECLAIMABLE +Images 21 20 17.84GB 1.821GB (10%) +Containers 25 18 702.4MB 237.6kB (0%) +Local Volumes 10 10 428.7MB 0B (0%) +Build Cache 38 0 710.8MB 710.8MB diff --git a/.dk2.tmp b/.dk2.tmp new file mode 100644 index 0000000..df8cc00 --- /dev/null +++ b/.dk2.tmp @@ -0,0 +1,25 @@ +student-frontend|||student_system-frontend|||Up 3 hours +student-backend|||student_system-backend|||Up 4 minutes +student-mysql|||mysql:8.0|||Up 5 hours (healthy) +web|||nginx|||Exited (255) 6 hours ago +redis-server|||redis|||Exited (0) 8 hours ago +student-ms-mysql|||mysql:8.0|||Exited (137) 8 hours ago +milvus-standalone|||milvusdb/milvus:v2.6.1|||Exited (137) 8 hours ago +milvus-etcd|||quay.io/coreos/etcd:v3.5.18|||Exited (0) 8 hours ago +milvus-minio|||minio/minio:RELEASE.2024-05-28T17-19-04Z|||Exited (0) 8 hours ago +docker-nginx-1|||d0d674272be3|||Up 6 hours +docker-worker-1|||langgenius/dify-api:1.17.1|||Up 6 hours +docker-api-1|||langgenius/dify-api:1.17.1|||Up 6 hours (healthy) +docker-agent_backend-1|||langgenius/dify-agent-backend:1.17.1|||Up 6 hours +docker-api_websocket-1|||langgenius/dify-api:1.17.1|||Up 6 hours +docker-worker_beat-1|||langgenius/dify-api:1.17.1|||Up 6 hours +docker-plugin_daemon-1|||langgenius/dify-plugin-daemon:0.6.10-local|||Up 6 hours +docker-agent_ssrf_proxy-1|||ubuntu/squid:latest|||Up 6 hours +docker-ssrf_proxy-1|||ubuntu/squid:latest|||Up 6 hours +docker-redis-1|||redis:6-alpine|||Up 6 hours (healthy) +docker-web-1|||langgenius/dify-web:1.17.1|||Up 6 hours +docker-db_postgres-1|||postgres:15-alpine|||Up 6 hours (healthy) +docker-sandbox-1|||langgenius/dify-sandbox:0.2.15|||Up 6 hours (healthy) +docker-local_sandbox-1|||langgenius/dify-agent-local-sandbox:1.17.1|||Up 6 hours (healthy) +docker-init_permissions-1|||busybox:latest|||Exited (0) 28 hours ago +docker-weaviate-1|||cr.weaviate.io/semitechnologies/weaviate:1.39.2|||Up 6 hours diff --git a/.dk3.tmp b/.dk3.tmp new file mode 100644 index 0000000..f31ee22 Binary files /dev/null and b/.dk3.tmp differ diff --git a/.dk3b.tmp b/.dk3b.tmp new file mode 100644 index 0000000..fab5d28 --- /dev/null +++ b/.dk3b.tmp @@ -0,0 +1,25 @@ +student-frontend:::com.docker.compose.config-hash=7e3a4ab0916640a8c977e1497e121b1e9ba64ad74d3a7093cc8b7a29362fb703,com.docker.compose.container-number=1,com.docker.compose.depends_on=backend:service_started:false,com.docker.compose.image=sha256:7dbfeaac7bb26320ddf7c61d73c7081c67a1b6426a2c401b582446c9dad75171,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system\docker-compose.yml,com.docker.compose.project.working_dir=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system,com.docker.compose.project=student_system,com.docker.compose.replace=student-frontend,com.docker.compose.service=frontend,com.docker.compose.version=2.40.2,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/80/tcp=:81,maintainer=NGINX Docker Maintainers +student-backend:::com.docker.compose.config-hash=bde3b007cab1d980b7691ffbac039f22c3348cae6323e29b6683063bee76f325,com.docker.compose.container-number=1,com.docker.compose.depends_on=mysql:service_healthy:false,com.docker.compose.image=sha256:c4c1dbc5b269cdc988baba6ada221a02e9bcdb61245e8bf03720d24067c79d58,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system\docker-compose.yml,com.docker.compose.project.working_dir=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system,com.docker.compose.project=student_system,com.docker.compose.replace=student-backend,com.docker.compose.service=backend,com.docker.compose.version=2.40.2,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/9090/tcp=:9090 +student-mysql:::com.docker.compose.config-hash=22789b5ae5e95f72355eb66fbce5c9d1faeb6430e5f9e2abbfee0885520c3a36,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:7dcddc01f13bab2f15cde676d44d01f61fc9f99fe7785e86196dfc07d358ae2b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system\docker-compose.yml,com.docker.compose.project.working_dir=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system,com.docker.compose.project=student_system,com.docker.compose.service=mysql,com.docker.compose.version=2.40.2,desktop.docker.io/binds/1/Source=C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system\student_system.sql,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/binds/1/Target=/docker-entrypoint-initdb.d/init.sql,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/3306/tcp=:3308 +web:::desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/80/tcp=:8083,maintainer=NGINX Docker Maintainers +redis-server:::desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/6379/tcp=:6379 +student-ms-mysql:::desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/3306/tcp=:3307 +milvus-standalone:::com.docker.compose.config-hash=c68054c684e9fbe54b8166a8beeba5fa1be2acd9c59aed9a8a733296a5deae75,com.docker.compose.container-number=1,com.docker.compose.depends_on=minio:service_started:false,etcd:service_started:false,com.docker.compose.image=sha256:4251cb095b89ddadaf6cde87ad96f773b8010613ae47d70e545db94dab8dd3ff,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\milvus-docker\docker-compose.yml,com.docker.compose.project.working_dir=D:\milvus-docker,com.docker.compose.project=milvus,com.docker.compose.service=standalone,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\milvus-docker\volumes\milvus,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/var/lib/milvus,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/19530/tcp=:19530,desktop.docker.io/ports/9091/tcp=:9091,org.opencontainers.image.ref.name=ubuntu,org.opencontainers.image.version=22.04 +milvus-etcd:::com.docker.compose.config-hash=c8a66161ae990bc77e0a0395bc139fdaba1a9358796a3dd80b84a671d70c5ded,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:d0a641d5fbcc89678c931a61b7de7b8a1cf097149f135c9c73bc81d076a1494b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\milvus-docker\docker-compose.yml,com.docker.compose.project.working_dir=D:\milvus-docker,com.docker.compose.project=milvus,com.docker.compose.service=etcd,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\milvus-docker\volumes\etcd,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etcd,desktop.docker.io/ports.scheme=v2,io.buildah.version=1.38.0 +milvus-minio:::architecture=x86_64,build-date=2024-05-23T13:47:39,com.docker.compose.config-hash=c59f0d52461302af6d5559b2814168db802cde0a25eb2160281cd98711cc23c7,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:391d1d45fdbe79944cb6de9337b073864bb9ee38c4c24280bfb39572e925af08,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\milvus-docker\docker-compose.yml,com.docker.compose.project.working_dir=D:\milvus-docker,com.docker.compose.project=milvus,com.docker.compose.service=minio,com.docker.compose.version=2.40.2,com.redhat.component=ubi9-micro-container,com.redhat.license_terms=https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI,description=MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads.,desktop.docker.io/binds/0/Source=D:\milvus-docker\volumes\minio,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/minio_data,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/9000/tcp=:9100,desktop.docker.io/ports/9001/tcp=:9101,distribution-scope=public,io.buildah.version=1.29.0,io.k8s.description=Very small image which doesn't install the package manager.,io.k8s.display-name=Ubi9-micro,io.openshift.expose-services=,maintainer=MinIO Inc ,name=MinIO,release=RELEASE.2024-05-28T17-19-04Z,summary=MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service.,url=https://access.redhat.com/containers/#/registry.access.redhat.com/ubi9/ubi-micro/images/9.4-6.1716471860,vcs-ref=433dec61d526247ac9533c1d9b97e98a1127c782,vcs-type=git,vendor=MinIO Inc ,version=RELEASE.2024-05-28T17-19-04Z +docker-nginx-1:::com.docker.compose.config-hash=d73307356547cb708e909997500d6414c294a19266c42f0a5b41b7ad4ce193f3,com.docker.compose.container-number=1,com.docker.compose.depends_on=api:service_started:false,web:service_started:false,com.docker.compose.image=sha256:d0d674272be3be36f9a13d79194fa0db5aa630ab3ede9bec459d12f67370aaef,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=nginx,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\nginx\proxy.conf.template,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/nginx/proxy.conf.template,desktop.docker.io/binds/1/Source=D:\dify\docker\nginx\conf.d,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/binds/1/Target=/etc/nginx/conf.d,desktop.docker.io/binds/2/Source=D:\dify\docker\nginx\docker-entrypoint.sh,desktop.docker.io/binds/2/SourceKind=hostFile,desktop.docker.io/binds/2/Target=/docker-entrypoint-mount.sh,desktop.docker.io/binds/3/Source=D:\dify\docker\nginx\ssl,desktop.docker.io/binds/3/SourceKind=hostFile,desktop.docker.io/binds/3/Target=/etc/ssl,desktop.docker.io/binds/4/Source=D:\dify\docker\volumes\certbot\conf\live,desktop.docker.io/binds/4/SourceKind=hostFile,desktop.docker.io/binds/4/Target=/etc/letsencrypt/live,desktop.docker.io/binds/5/Source=D:\dify\docker\volumes\certbot\conf,desktop.docker.io/binds/5/SourceKind=hostFile,desktop.docker.io/binds/5/Target=/etc/letsencrypt,desktop.docker.io/binds/6/Source=D:\dify\docker\volumes\certbot\www,desktop.docker.io/binds/6/SourceKind=hostFile,desktop.docker.io/binds/6/Target=/var/www/html,desktop.docker.io/binds/7/Source=D:\dify\docker\nginx\nginx.conf.template,desktop.docker.io/binds/7/SourceKind=hostFile,desktop.docker.io/binds/7/Target=/etc/nginx/nginx.conf.template,desktop.docker.io/binds/8/Source=D:\dify\docker\nginx\https.conf.template,desktop.docker.io/binds/8/SourceKind=hostFile,desktop.docker.io/binds/8/Target=/etc/nginx/https.conf.template,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/443/tcp=:443,desktop.docker.io/ports/80/tcp=:8080,maintainer=NGINX Docker Maintainers +docker-worker-1:::com.docker.compose.config-hash=4412edef04badc3619947d10437c099caa5cb30915f6037f67a2ad6a595e9746,com.docker.compose.container-number=1,com.docker.compose.depends_on=init_permissions:service_completed_successfully:false,db_postgres:service_healthy:false,redis:service_started:false,agent_backend:service_started:false,com.docker.compose.image=sha256:ceede5b903afaa20348f7ad80ebf847379dc56d886a99b9ec6a08913dacd7732,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=worker,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\app\storage,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/app/api/storage,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2026-09-10T09:25:34.752Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-api-1:::com.docker.compose.config-hash=b77b00300a29c53ee9c1a6023acfffb36bf3a25c2f506f3cad8f8adbd8f5b076,com.docker.compose.container-number=1,com.docker.compose.depends_on=redis:service_started:false,agent_backend:service_started:false,init_permissions:service_completed_successfully:false,db_postgres:service_healthy:false,com.docker.compose.image=sha256:ceede5b903afaa20348f7ad80ebf847379dc56d886a99b9ec6a08913dacd7732,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=api,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\app\storage,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/app/api/storage,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2026-09-10T09:25:34.752Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-agent_backend-1:::com.docker.compose.config-hash=26423c06ef873d0bb4366e89475755a19c5ccc37d3f43f0119dfda68a06b828e,com.docker.compose.container-number=1,com.docker.compose.depends_on=plugin_daemon:service_started:false,redis:service_started:false,com.docker.compose.image=sha256:a30c039bbead65d7fd8baf8c53fc867373f515c5721e46070ce03ed04c5f1e32,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=agent_backend,com.docker.compose.version=2.40.2,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2026-09-10T09:25:34.861Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-api_websocket-1:::com.docker.compose.config-hash=cc163e94b16b57494a7a1fa51fee5218a0ad6a78741524cec5f929756a57a2e1,com.docker.compose.container-number=1,com.docker.compose.depends_on=redis:service_started:false,init_permissions:service_completed_successfully:false,db_postgres:service_healthy:false,com.docker.compose.image=sha256:ceede5b903afaa20348f7ad80ebf847379dc56d886a99b9ec6a08913dacd7732,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=api_websocket,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\app\storage,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/app/api/storage,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2026-09-10T09:25:34.752Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-worker_beat-1:::com.docker.compose.config-hash=5e7692a1e927118235e9553cd991fcd55de84fc75faddbcb0f9c89199ce13949,com.docker.compose.container-number=1,com.docker.compose.depends_on=redis:service_started:false,init_permissions:service_completed_successfully:false,db_postgres:service_healthy:false,com.docker.compose.image=sha256:ceede5b903afaa20348f7ad80ebf847379dc56d886a99b9ec6a08913dacd7732,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=worker_beat,com.docker.compose.version=2.40.2,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2026-09-10T09:25:34.752Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-plugin_daemon-1:::com.docker.compose.config-hash=10cb7dfea81a05e3d4061fe4e552b23037b5c8f8e636a9263b22d9975b6005de,com.docker.compose.container-number=1,com.docker.compose.depends_on=db_postgres:service_healthy:false,com.docker.compose.image=sha256:34412a22d1e1d1a6c73727fef88cffbcdd9be7a21bf9ad3076b4bf5c1b88fd9b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=plugin_daemon,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\plugin_daemon,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/app/storage,desktop.docker.io/ports.scheme=v2,desktop.docker.io/ports/5003/tcp=:5003,org.opencontainers.image.version=24.04 +docker-agent_ssrf_proxy-1:::com.docker.compose.config-hash=41fae3bc8fb25d3d23555f9f6080adc96cd98167a050ea85422744f20fd53c94,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:6a097f68bae708cedbabd6188d68c7e2e7a38cedd05a176e1cc0ba29e3bbe029,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=agent_ssrf_proxy,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\ssrf_proxy\squid-agent.conf.template,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/squid/squid.conf.template,desktop.docker.io/binds/1/Source=D:\dify\docker\ssrf_proxy\squid-common.conf.template,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/binds/1/Target=/etc/squid/dify_common.conf.template,desktop.docker.io/binds/2/Source=D:\dify\docker\ssrf_proxy\docker-agent-entrypoint.sh,desktop.docker.io/binds/2/SourceKind=hostFile,desktop.docker.io/binds/2/Target=/docker-entrypoint-mount.sh,desktop.docker.io/ports.scheme=v2,maintainer=Ubuntu Server team ,org.opencontainers.image.ref.name=ubuntu,org.opencontainers.image.version=24.04 +docker-ssrf_proxy-1:::com.docker.compose.config-hash=ba85c225413ac30fb86c74cd2f9cc937fc072d6bc8445d6baace41bd2dbcc227,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:6a097f68bae708cedbabd6188d68c7e2e7a38cedd05a176e1cc0ba29e3bbe029,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=ssrf_proxy,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\ssrf_proxy\squid.conf.template,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/squid/squid.conf.template,desktop.docker.io/binds/1/Source=D:\dify\docker\ssrf_proxy\squid-common.conf.template,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/binds/1/Target=/etc/squid/dify_common.conf.template,desktop.docker.io/binds/2/Source=D:\dify\docker\ssrf_proxy\docker-entrypoint.sh,desktop.docker.io/binds/2/SourceKind=hostFile,desktop.docker.io/binds/2/Target=/docker-entrypoint-mount.sh,desktop.docker.io/ports.scheme=v2,maintainer=Ubuntu Server team ,org.opencontainers.image.ref.name=ubuntu,org.opencontainers.image.version=24.04 +docker-redis-1:::com.docker.compose.config-hash=4a212ed4f52a0456b3701c7805ac9af931ef4f26d09e9ac44b8e7a59368cea5a,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:d0c875bdacfb5c4d2c2d9124de3f53cee1dc9ceff8936bd459fabc135cb33015,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=redis,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\redis\data,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/data,desktop.docker.io/ports.scheme=v2 +docker-web-1:::com.docker.compose.config-hash=2db14d211559d081ea26c01e4913af412e08d8220e7d851f394082207a2658ed,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:6353fe8e04481aaef873ef7317507bfa9ac862572118fd037563a71bff9f4d08,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=web,com.docker.compose.version=2.40.2,desktop.docker.io/ports.scheme=v2,maintainer=takatost@gmail.com,org.opencontainers.image.created=2026-09-10T09:25:35.188Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-db_postgres-1:::com.docker.compose.config-hash=1375971312e8320731c68dfba90291173145556f5f77d9e2f11f0f00c035731d,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:fe0737ba566a2c5b2a28f34433c0a423261900ec17b9bf7ad115e1aae7e57f1b,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=db_postgres,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\db\data,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/var/lib/postgresql/data,desktop.docker.io/ports.scheme=v2 +docker-sandbox-1:::com.docker.compose.config-hash=bb1184c7e41dc8db83f9b3dd81aa6c66597e503fe49728f8dc5a81147ba3845d,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:750e1111426ef31a9217b81c98cccfb750f17b182af3221102e420afa9f0928e,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=sandbox,com.docker.compose.version=2.40.2,com.docker.dhi.chain-id=sha256:7efbdde9cf436e03e485e5dc10f52441b687fab78304e3aed7955f762ff2f096,com.docker.dhi.compliance=cis,com.docker.dhi.created=2026-04-25T22:28:26Z,com.docker.dhi.date.end-of-life=2030-10-31,com.docker.dhi.date.release=2025-10-07,com.docker.dhi.definition=image/python/debian-13/3.14-sfw-ent-dev,com.docker.dhi.distro=debian-13,com.docker.dhi.entitlement=public,com.docker.dhi.flavor=sfw-ent,com.docker.dhi.name=dhi/python,com.docker.dhi.package-manager=apt,com.docker.dhi.shell=bash,com.docker.dhi.title=Python 3.14.x / Socket Firewall Enterprise (sfw-ent, dev),com.docker.dhi.url=https://dhi.io/catalog/python,com.docker.dhi.variant=dev,com.docker.dhi.version=3.14.4-debian13-sfw-ent-dev,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\sandbox\dependencies,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/dependencies,desktop.docker.io/binds/1/Source=D:\dify\docker\volumes\sandbox\conf,desktop.docker.io/binds/1/SourceKind=hostFile,desktop.docker.io/binds/1/Target=/conf,desktop.docker.io/ports.scheme=v2 +docker-local_sandbox-1:::com.docker.compose.config-hash=8aa365e2bacfc29eac97af4b8bafef760792a23cf3949da1c249d5f84a2b2ad3,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:f4d404c9dcdf80bd72f65e84c3f2bbd1f3404fd4b363db837176da8aaf771b1d,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=local_sandbox,com.docker.compose.version=2.40.2,desktop.docker.io/ports.scheme=v2,org.opencontainers.image.created=2026-09-10T09:25:39.348Z,org.opencontainers.image.description=Build Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.,org.opencontainers.image.licenses=NOASSERTION,org.opencontainers.image.revision=8387590ace4a094de812b7847fc6a4c3a27cd52b,org.opencontainers.image.source=https://github.com/langgenius/dify,org.opencontainers.image.title=dify,org.opencontainers.image.url=https://github.com/langgenius/dify,org.opencontainers.image.version=1.17.1 +docker-init_permissions-1:::com.docker.compose.config-hash=e14f81175fcc6c63b8a1b8527af1c1ca88e4f6475fe5ae09b6d0390def0ccb03,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:dc2d74b28e4cf8984fa52af1f39bc7c3d9c73760b41a74d629f5d11b1ab28616,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=init_permissions,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\app\storage,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/app/api/storage,desktop.docker.io/ports.scheme=v2 +docker-weaviate-1:::com.docker.compose.config-hash=cf21bc907f3cbe7e0e5274e7797358ccd682753446c55070417fc76b81389b50,com.docker.compose.container-number=1,com.docker.compose.depends_on=,com.docker.compose.image=sha256:5c62e5cbce4c48fc770abcee71099d9de32aad3b44d12299611f8135ee412362,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=D:\dify\docker\docker-compose.yaml,com.docker.compose.project.working_dir=D:\dify\docker,com.docker.compose.project=docker,com.docker.compose.service=weaviate,com.docker.compose.version=2.40.2,desktop.docker.io/binds/0/Source=D:\dify\docker\volumes\weaviate,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/var/lib/weaviate,desktop.docker.io/ports.scheme=v2 diff --git a/.dk4.tmp b/.dk4.tmp new file mode 100644 index 0000000..d2c28f3 --- /dev/null +++ b/.dk4.tmp @@ -0,0 +1,10 @@ +1dd33c02109e0922440653e2e4ecde657690a6d451c3d348ab69514b25e70e79 +47c67ccd39be74cd3161f8f101f858d70dcaad6e879ff2e1579cb903126264a4 +280f7a11fd9b033f7bd538ccf286b8dcec3f17880249a4384f7c41624638ee5e +5143be92e6e9392c937f2df6fab5f443c95095bb57b0a9755d69b795fe61b2c7 +dd7d1e20da7d48ed54f6cc070dfe30be4c4f8738b8960de8c7afd3fdffa05fef +docker_dify_agent_local_sandbox_home +docker_dify_agent_local_sandbox_workspace +fd4494e8cb4e4916ddec7dde94a04125a2b518fb04980a8aeff1864585fd7835 +student_system_mysql_data +student_system_upload_files diff --git a/.docker-check.tmp b/.docker-check.tmp new file mode 100644 index 0000000..fec3794 --- /dev/null +++ b/.docker-check.tmp @@ -0,0 +1,18 @@ +student-frontend | student_system-frontend | Up 3 hours | 0.0.0.0:81->80/tcp, [::]:81->80/tcp +student-backend | student_system-backend | Restarting (3) 27 seconds ago | +student-mysql | mysql:8.0 | Up 5 hours (healthy) | 0.0.0.0:3308->3306/tcp, [::]:3308->3306/tcp +docker-nginx-1 | d0d674272be3 | Up 5 hours | 0.0.0.0:443->443/tcp, [::]:443->443/tcp, 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp +docker-worker-1 | langgenius/dify-api:1.17.1 | Up 5 hours | 5001/tcp +docker-api-1 | langgenius/dify-api:1.17.1 | Up 5 hours (healthy) | 5001/tcp +docker-agent_backend-1 | langgenius/dify-agent-backend:1.17.1 | Up 5 hours | 5050/tcp +docker-api_websocket-1 | langgenius/dify-api:1.17.1 | Up 5 hours | 5001/tcp +docker-worker_beat-1 | langgenius/dify-api:1.17.1 | Up 5 hours | 5001/tcp +docker-plugin_daemon-1 | langgenius/dify-plugin-daemon:0.6.10-local | Up 5 hours | 0.0.0.0:5003->5003/tcp, [::]:5003->5003/tcp +docker-agent_ssrf_proxy-1 | ubuntu/squid:latest | Up 5 hours | 3128/tcp +docker-ssrf_proxy-1 | ubuntu/squid:latest | Up 5 hours | 3128/tcp +docker-redis-1 | redis:6-alpine | Up 5 hours (healthy) | 6379/tcp +docker-web-1 | langgenius/dify-web:1.17.1 | Up 5 hours | 3000/tcp +docker-db_postgres-1 | postgres:15-alpine | Up 5 hours (healthy) | 5432/tcp +docker-sandbox-1 | langgenius/dify-sandbox:0.2.15 | Up 5 hours (healthy) | +docker-local_sandbox-1 | langgenius/dify-agent-local-sandbox:1.17.1 | Up 5 hours (healthy) | +docker-weaviate-1 | cr.weaviate.io/semitechnologies/weaviate:1.39.2 | Up 5 hours | diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/fastapi-app.iml b/.idea/fastapi-app.iml new file mode 100644 index 0000000..22f4cfa --- /dev/null +++ b/.idea/fastapi-app.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..f281ec3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..917f2f9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.insp.tmp b/.insp.tmp new file mode 100644 index 0000000..6e91a4a --- /dev/null +++ b/.insp.tmp @@ -0,0 +1 @@ +[{"Type":"volume","Name":"student_system_upload_files","Source":"/var/lib/docker/volumes/student_system_upload_files/_data","Destination":"/app/files","Driver":"local","Mode":"rw","RW":true,"Propagation":""}] diff --git a/.insp2.tmp b/.insp2.tmp new file mode 100644 index 0000000..6e91a4a --- /dev/null +++ b/.insp2.tmp @@ -0,0 +1 @@ +[{"Type":"volume","Name":"student_system_upload_files","Source":"/var/lib/docker/volumes/student_system_upload_files/_data","Destination":"/app/files","Driver":"local","Mode":"rw","RW":true,"Propagation":""}] diff --git a/.insp3.tmp b/.insp3.tmp new file mode 100644 index 0000000..eedd082 --- /dev/null +++ b/.insp3.tmp @@ -0,0 +1 @@ +{"com.docker.compose.config-hash":"bde3b007cab1d980b7691ffbac039f22c3348cae6323e29b6683063bee76f325","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"mysql:service_healthy:false","com.docker.compose.image":"sha256:c4c1dbc5b269cdc988baba6ada221a02e9bcdb61245e8bf03720d24067c79d58","com.docker.compose.oneoff":"False","com.docker.compose.project":"student_system","com.docker.compose.project.config_files":"C:\\Users\\Windows\\AppData\\Roaming\\Tencent\\WeMeet\\Global\\Data\\IM\\file\\2026-9\\3758211051474203869\\student_system\\student_system\\docker-compose.yml","com.docker.compose.project.working_dir":"C:\\Users\\Windows\\AppData\\Roaming\\Tencent\\WeMeet\\Global\\Data\\IM\\file\\2026-9\\3758211051474203869\\student_system\\student_system","com.docker.compose.replace":"student-backend","com.docker.compose.service":"backend","com.docker.compose.version":"2.40.2"} diff --git a/.net-check.tmp b/.net-check.tmp new file mode 100644 index 0000000..ccf1228 --- /dev/null +++ b/.net-check.tmp @@ -0,0 +1,7 @@ + TCP 0.0.0.0:3306 0.0.0.0:0 LISTENING 6024 + TCP 0.0.0.0:3308 0.0.0.0:0 LISTENING 3564 + TCP 0.0.0.0:33060 0.0.0.0:0 LISTENING 6024 + TCP [::]:3306 [::]:0 LISTENING 6024 + TCP [::]:3308 [::]:0 LISTENING 3564 + TCP [::]:33060 [::]:0 LISTENING 6024 + TCP [::1]:3308 [::]:0 LISTENING 6876 diff --git a/.nginx.tmp b/.nginx.tmp new file mode 100644 index 0000000..097978e Binary files /dev/null and b/.nginx.tmp differ diff --git a/.nginx2.tmp b/.nginx2.tmp new file mode 100644 index 0000000..ed8a592 --- /dev/null +++ b/.nginx2.tmp @@ -0,0 +1,35 @@ +server { + listen 80; + server_name localhost; + + # Vue 闈欐€佹枃浠? root /usr/share/nginx/html; + index index.html; + + # ========== 鍚庣 API 浠g悊 ========== + # 绮剧‘鍖归厤鎵€鏈夊悗绔?API 璺緞锛岃浆鍙戝埌 FastAPI + location ~ ^/(login|register|updatePassword|admin|student|major|clazz|course|grade|notice|studentCourse|files) { + proxy_pass http://backend:9090; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # ========== SPA 鍓嶇璺敱 ========== + # 鍏堝尮閰嶉潤鎬佹枃浠讹紝鎵句笉鍒板垯鍥為€€鍒?index.html锛堜氦鐢?Vue Router 澶勭悊锛? location / { + try_files $uri $uri/ /index.html; + } + + # 鏂囦欢涓婁紶澶у皬闄愬埗 + client_max_body_size 50m; +} +docker : Error response from daemon: Container bcbfa3c10130cb8d7241e89f2d0b2f85 +3c6f277fc39f845548bda96e66f5dda5 is restarting, wait until the container is run +ning +所在位置 行:445 字符: 148 ++ ... x.tmp 2>&1; docker exec student-backend cat /app/settings.py >> D:\fa ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (Error response ...iner is running + :String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + diff --git a/.proc-check.tmp b/.proc-check.tmp new file mode 100644 index 0000000..2bcedd7 --- /dev/null +++ b/.proc-check.tmp @@ -0,0 +1,12 @@ + +映像名称: mysqld.exe +PID: 6024 +会话名 : Services +会话# : 0 +内存使用 : 381,640 K + +映像名称: com.docker.backend.exe +PID: 3564 +会话名 : Console +会话# : 1 +内存使用 : 825,836 K diff --git a/.q1.tmp b/.q1.tmp new file mode 100644 index 0000000..e174284 Binary files /dev/null and b/.q1.tmp differ diff --git a/.q1b.tmp b/.q1b.tmp new file mode 100644 index 0000000..c45f0c9 --- /dev/null +++ b/.q1b.tmp @@ -0,0 +1,19 @@ +docker : template parsing error: template: :1: function "com" not defined +所在位置 行:445 字符: 1 ++ docker inspect student-frontend student-backend student-mysql --forma ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (template parsin...om" not defined + :String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + + +docker : Error: No such object: student-ms-mysql +所在位置 行:445 字符: 246 ++ ... 1.tmp 2>&1; docker inspect student-ms-mysql --format 'NAME={{.Name}} ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (Error: No such object: student-ms + -mysql:String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +NAME=/web MOUNTS=[] +NAME=/redis-server MOUNTS=[] diff --git a/.q2.tmp b/.q2.tmp new file mode 100644 index 0000000..05670ce Binary files /dev/null and b/.q2.tmp differ diff --git a/.q3.tmp b/.q3.tmp new file mode 100644 index 0000000..23150f6 Binary files /dev/null and b/.q3.tmp differ diff --git a/.q4.tmp b/.q4.tmp new file mode 100644 index 0000000..b69ea59 Binary files /dev/null and b/.q4.tmp differ diff --git a/.q5.tmp b/.q5.tmp new file mode 100644 index 0000000..469e4cb Binary files /dev/null and b/.q5.tmp differ diff --git a/.q6.tmp b/.q6.tmp new file mode 100644 index 0000000..e69de29 diff --git a/.q6b.tmp b/.q6b.tmp new file mode 100644 index 0000000..e69de29 diff --git a/.q7.tmp b/.q7.tmp new file mode 100644 index 0000000..e02abfc --- /dev/null +++ b/.q7.tmp @@ -0,0 +1 @@ + diff --git a/.q7b.tmp b/.q7b.tmp new file mode 100644 index 0000000..e02abfc --- /dev/null +++ b/.q7b.tmp @@ -0,0 +1 @@ + diff --git a/.q8.tmp b/.q8.tmp new file mode 100644 index 0000000..60b6da1 --- /dev/null +++ b/.q8.tmp @@ -0,0 +1 @@ +dangling_count=0 diff --git a/.status.tmp b/.status.tmp new file mode 100644 index 0000000..f4879ca --- /dev/null +++ b/.status.tmp @@ -0,0 +1,24 @@ +student-frontend | Up 3 hours +student-backend | Up 45 seconds +student-mysql | Up 5 hours (healthy) +docker : await pool._fill_free_pool(False) +所在位置 行:445 字符: 150 ++ ... mp -Encoding utf8; docker logs --tail 12 student-backend 2>&1 | Out-F ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: ( await pool._fill_free_pool(Fa + lse):String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + + File "/usr/local/lib/python3.10/site-packages/aiomysql/pool.py", line 182, in + _fill_free_pool + conn = await connect(echo=self._echo, loop=self._loop, +TypeError: connect() got an unexpected keyword argument 'serverTimezone' + +ERROR: Application startup failed. Exiting. +INFO: Started server process [1] +INFO: Waiting for application startup. +/usr/local/lib/python3.10/site-packages/aiomysql/cursors.py:158: Warning: Table + 'student_course' already exists + while (await self.nextset()): +INFO: Application startup complete. +INFO: Uvicorn running on http://0.0.0.0:9090 (Press CTRL+C to quit) diff --git a/.status2.tmp b/.status2.tmp new file mode 100644 index 0000000..f4879ca --- /dev/null +++ b/.status2.tmp @@ -0,0 +1,24 @@ +student-frontend | Up 3 hours +student-backend | Up 45 seconds +student-mysql | Up 5 hours (healthy) +docker : await pool._fill_free_pool(False) +所在位置 行:445 字符: 150 ++ ... mp -Encoding utf8; docker logs --tail 12 student-backend 2>&1 | Out-F ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: ( await pool._fill_free_pool(Fa + lse):String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + + File "/usr/local/lib/python3.10/site-packages/aiomysql/pool.py", line 182, in + _fill_free_pool + conn = await connect(echo=self._echo, loop=self._loop, +TypeError: connect() got an unexpected keyword argument 'serverTimezone' + +ERROR: Application startup failed. Exiting. +INFO: Started server process [1] +INFO: Waiting for application startup. +/usr/local/lib/python3.10/site-packages/aiomysql/cursors.py:158: Warning: Table + 'student_course' already exists + while (await self.nextset()): +INFO: Application startup complete. +INFO: Uvicorn running on http://0.0.0.0:9090 (Press CTRL+C to quit) diff --git a/.tz.tmp b/.tz.tmp new file mode 100644 index 0000000..a6bea59 --- /dev/null +++ b/.tz.tmp @@ -0,0 +1,11 @@ +docker : mysql: [Warning] Using a password on the command line interface can be + insecure. +所在位置 行:445 字符: 1 ++ docker exec student-mysql mysql -uroot -p123456 -e "SET GLOBAL time_z ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (mysql: [Warning...an be insecure. + :String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +@@global.time_zone ++08:00 diff --git a/.verify.tmp b/.verify.tmp new file mode 100644 index 0000000..d424867 --- /dev/null +++ b/.verify.tmp @@ -0,0 +1,28 @@ +HTTP 200: {"code":"200","msg":"请求成功","data":null} +docker : mysql: [Warning] Using a password on the command line interface can be + insecure. +所在位置 行:445 字符: 180 ++ ... oding utf8; docker exec student-mysql mysql -uroot -p123456 -e "USE s ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (mysql: [Warning...an be insecure. + :String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +Tables_in_student_system +admin +clazz +course +grade +major +notice +student +student_course +id username name role score +6 aaa ?? ?? 6 +7 bbb ?? ?? 3 +8 ccc ?? ?? 0 +9 ddd ?? ?? 0 +11 ggg ?? ?? 0 +12 kkk ?? ?? 0 +13 mmm mmm ?? 0 +14 123456 123456 ?? 0 diff --git a/.verify2.tmp b/.verify2.tmp new file mode 100644 index 0000000..d424867 --- /dev/null +++ b/.verify2.tmp @@ -0,0 +1,28 @@ +HTTP 200: {"code":"200","msg":"请求成功","data":null} +docker : mysql: [Warning] Using a password on the command line interface can be + insecure. +所在位置 行:445 字符: 180 ++ ... oding utf8; docker exec student-mysql mysql -uroot -p123456 -e "USE s ... ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + CategoryInfo : NotSpecified: (mysql: [Warning...an be insecure. + :String) [], RemoteException + + FullyQualifiedErrorId : NativeCommandError + +Tables_in_student_system +admin +clazz +course +grade +major +notice +student +student_course +id username name role score +6 aaa ?? ?? 6 +7 bbb ?? ?? 3 +8 ccc ?? ?? 0 +9 ddd ?? ?? 0 +11 ggg ?? ?? 0 +12 kkk ?? ?? 0 +13 mmm mmm ?? 0 +14 123456 123456 ?? 0 diff --git a/.workbuddy/memory/2026-09-18.md b/.workbuddy/memory/2026-09-18.md new file mode 100644 index 0000000..a90f4a8 --- /dev/null +++ b/.workbuddy/memory/2026-09-18.md @@ -0,0 +1,17 @@ +# 2026-09-18 + +- 对 FastAPI 学生管理系统做了整体代码审查(main/settings/models/api/*、Docker 配置),发现的问题:Dockerfile `COPY ../` 非法导致构建失败、files.py 路径遍历漏洞、grade.py selectAll 引用不存在的 name 字段、student.py selectPage 的 majorName 判断逻辑错误、登录/改密接口回传明文密码、全项目无鉴权且密码明文存储、register 接口可能把不存在的字段传给 Student.create 等。仅审查未修改代码。 + +## 排障:数据库连接与时区问题(已解决) + +- 现象:Navicat 连 3306 报「服务器返回无效的时区。需要设置 serverTimezone 属性」,且看不到项目数据库。 +- 根因1:本机另有 Windows 版 mysqld 占用 3306;Docker 的 student-mysql 实际映射 **3308→3306**,数据在 3308 那个实例里。 +- 根因2:MySQL system_time_zone=CST(歧义时区)导致客户端报时区错误。 +- 根因3:运行中的 backend(来自 WeMeet 接收目录 `C:\Users\Windows\AppData\Roaming\Tencent\WeMeet\Global\Data\IM\file\2026-9\3758211051474203869\student_system\student_system\`)的 settings.py 被加了 `serverTimezone` 参数,aiomysql 不支持 → 容器崩溃重启循环。 +- 修复:删除该参数并 docker cp 回容器+重启;MySQL 执行 `SET GLOBAL time_zone='+8:00'` 并在 compose 的 mysql command 中加 `--default-time-zone=+08:00` 持久化。已验证 backend 启动成功、接口 200、student_system 数据完整。 +- 注意:D:\fastapi-app 只是项目副本,真正在跑的栈在上述 WeMeet 目录;两份 settings.py 内容可能不同步。 + +## Docker 资产盘点 + +共 4 组来源:① student_system 项目(3 容器,来自 WeMeet 接收目录);② Dify 1.17.1 全家桶(16 容器,D:\dify\docker,占用最大 ~13GB 镜像);③ Milvus 向量库 3 件套(D:\milvus-docker,已停);④ 散装容器 web(nginx,:8083)/redis-server/student-ms-mysql(mysql:8.0,:3307,已停,疑为更早的测试遗留)。孤儿镜像:stu_test:latest、:d0d674272be3。镜像共 17.84GB + 构建缓存 710MB。未删除任何东西,仅盘点。 + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f43ab8b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# 使用官方 Python 精简镜像作为基础镜像 +FROM python:3.11-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量,防止 Python 生成 .pyc 文件及缓冲 stdout/stderr +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +# 安装系统依赖(构建 cryptography 等所需) +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libffi-dev \ + libssl-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# 先复制依赖文件并安装,利用 Docker 缓存加速构建 +COPY requirements_1.txt /app/requirements.txt +RUN pip install --upgrade pip && pip install -r /app/requirements.txt + +# 复制项目源代码到容器 +COPY ../ /app/ + +# 创建静态文件目录 +RUN mkdir -p /app/files + +# 暴露应用端口 +EXPOSE 9090 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \ + CMD curl -f http://localhost:9090/ || exit 1 + +# 启动 FastAPI 应用 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9090"] \ No newline at end of file diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..14e357c --- /dev/null +++ b/api/__init__.py @@ -0,0 +1,102 @@ +import importlib +import pkgutil + +from fastapi import APIRouter +from pydantic import BaseModel, ConfigDict + +from common.exception_handler import CustomException +from common.result import Result +from models import Admin, Student + + +class Account(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int | None = None + username: str | None = None + password: str | None = None + newPassword: str | None = None + role: str | None = None + name: str | None = None + avatar: str | None = None + clazzId: int | None = None + majorId: int | None = None + score: int | None = None + + +api_router = APIRouter() + + +# 登录 +@api_router.post("/login") +async def login(account: Account): + if account.role == '管理员': + admin = await Admin.get_or_none(username=account.username) + if admin is None: + raise CustomException("账号或密码错误") + if admin.password != account.password: + raise CustomException("账号或密码错误") + account = Account.model_validate(admin) + elif account.role == '学生': + student = await Student.get_or_none(username=account.username).prefetch_related("clazz__major") + if student is None: + raise CustomException("账号或密码错误") + if student.password != account.password: + raise CustomException("账号或密码错误") + account = Account.model_validate(student) + account.clazzId = student.clazz.id if student and student.clazz else None + account.majorId = student.clazz.major.id if student and student.clazz and student.clazz.major else None + else: + raise CustomException("角色错误") + return Result.success(account) + + +# 注册 +@api_router.post("/register") +async def register(account: Account): + if account.username is None: + raise CustomException("账号不能为空") + if account.password is None: + raise CustomException("密码不能为空") + # 设置默认的name + if account.name is None: + account.name = account.username + student = await Student.get_or_none(username=account.username) + if student is not None: + raise CustomException("账号已存在") + create_data = account.model_dump(exclude_unset=True, exclude={"id"}) + create_data['score'] = 0 + await Student.create(**create_data) + return Result.success() + + +# 修改密码 +@api_router.put("/updatePassword") +async def update_password(account: Account): + if account.role == '管理员': + admin = await Admin.get_or_none(id=account.id) + if admin is None: + raise CustomException("未找到用户") + if admin.password != account.password: + raise CustomException("原密码错误") + if admin.password == account.newPassword: + raise CustomException("新密码不能原密码跟相同") + await Admin.filter(id=admin.id).update(password=account.newPassword) + if account.role == '学生': + student = await Student.get_or_none(id=account.id) + if student is None: + raise CustomException("未找到用户") + if student.password != account.password: + raise CustomException("原密码错误") + if student.password == account.newPassword: + raise CustomException("新密码不能原密码跟相同") + await Student.filter(id=student.id).update(password=account.newPassword) + return Result.success(account) + + +# 自动导入当前目录下的所有模块 +for _, module_name, _ in pkgutil.iter_modules(__path__, __name__ + "."): + module = importlib.import_module(module_name) + if hasattr(module, "router"): + # 假设每个端点文件都有一个 router 变量 + api_router.include_router(module.router) diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..fa40b4b --- /dev/null +++ b/api/admin.py @@ -0,0 +1,91 @@ +from typing import List, Optional + +from fastapi import APIRouter +from pydantic import create_model, ConfigDict +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Admin + +router = APIRouter(prefix="/admin",tags=["管理员管理"]) +# 创建 pydantic 只读模型 +AdminPydantic = pydantic_model_creator(Admin) +# 自动生成所有字段为 Optional 的更新模型 +AdminCreatePydantic = create_model( + "AdminPydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in AdminPydantic.model_fields.items() + } +) + + +# 新增 +@router.post("/add") +async def add(admin_create_pydantic: AdminCreatePydantic): + admin = await Admin.get_or_none(username=admin_create_pydantic.username) + if admin is not None: + raise CustomException("账号重复") + if admin_create_pydantic.name is None: + admin_create_pydantic.name = admin_create_pydantic.username + if admin_create_pydantic.password is None: + admin_create_pydantic.password = "admin" + create_data = admin_create_pydantic.model_dump(exclude_unset=True, exclude={'id'}) + create_data['role'] = '管理员' + await Admin.create(**create_data) + return Result.success() + + +# 修改 +@router.put("/update") +async def update(admin_create_pydantic: AdminCreatePydantic): + update_data = admin_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Admin.filter(id=admin_create_pydantic.id).update(**update_data) + return Result.success() + + +# 删除 +@router.delete("/delete/{admin_id}") +async def delete(admin_id: int): + await Admin.filter(id=admin_id).delete() + return Result.success() + + +# 批量删除 +@router.delete("/deleteBatch") +async def delete_batch(ids: List[int]): + await Admin.filter(id__in=ids).delete() + return Result.success() + + +# 单个查询 +@router.get("/selectById/{admin_id}") +async def select_one(admin_id: int): + admin = await Admin.get(id=admin_id) + return Result.success(admin) + + +# 查询所有 +@router.get("/selectAll") +async def select_all(name: str = ""): + admin_list = await Admin.filter(name__contains=name) + return Result.success(admin_list) + + +# 分页查询 +@router.get("/selectPage") +async def select_page(name: str = "", pageNum: int = 1, pageSize: int = 10): + # 同时获取分页数据和总数 + query = Admin.filter(name__contains=name) + # 获取分页数据 + admin_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + admin_list = [ + AdminPydantic.model_validate(admin).model_dump() + for admin in admin_list + ] + # 计算总数 + total = await query.count() + # 封装分页数据 + pageinfo = PageInfo(total=total, list=admin_list) + return Result.success(pageinfo) diff --git a/api/clazz.py b/api/clazz.py new file mode 100644 index 0000000..e2186d4 --- /dev/null +++ b/api/clazz.py @@ -0,0 +1,88 @@ +from typing import Optional + +from fastapi import APIRouter +from pydantic import create_model, BaseModel, Field +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Clazz + +router = APIRouter(prefix="/clazz",tags=["班级管理"]) + +ClazzPydantic = pydantic_model_creator(Clazz) + +ClazzCreatePydantic = create_model( + "ClazzCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in ClazzPydantic.model_fields.items() + }, + major_id=(Optional[int], Field(None, alias="majorId")) +) + + +# 新增 +@router.post("/add") +async def add(clazz_create_pydantic: ClazzCreatePydantic): + db_clazz = await Clazz.get_or_none(no=clazz_create_pydantic.no) + if db_clazz is not None: + raise CustomException("班级编号重复") + # 将参数转换成 字典数据 + create_data = clazz_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Clazz.create(**create_data) # no=xxx,name=xxx,college=xxx + return Result.success() + + +# 更新 +@router.put("/update") +async def add(clazz_create_pydantic: ClazzCreatePydantic): + if clazz_create_pydantic.id is None: + raise CustomException("缺少参数ID") + # 将参数转换成 字典数据 + update_data = clazz_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Clazz.filter(id=clazz_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{clazz_id}') +async def delete(clazz_id: int): + await Clazz.filter(id=clazz_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{clazz_id}') +async def select_by_id(clazz_id: int): + clazz = await Clazz.get_or_none(id=clazz_id) + return Result.success(clazz) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(name: str = ""): + clazz_list = await Clazz.filter(name__contains=name) # name__contains表示根据name进行模糊查询 + return Result.success(clazz_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(name: str = "", pageNum: int = 1, pageSize: int = 10): + # name__contains表示根据name进行模糊查询 prefetch_related 关联查询到 major模块的数据 + query = Clazz.filter(name__contains=name).prefetch_related("major") + clazz_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + # clazz_list 转成字典数据 + # majorName 怎么返回?? + # {id=xxx, name=xxx, no=xxx} + clazz_dict_list = [ + { + **ClazzPydantic.model_validate(clazz).model_dump(), # id=xxx,no=xxx,name=xxx + "majorId": clazz.major.id if clazz.major else None, + "majorName": clazz.major.name if clazz.major else None + } + for clazz in clazz_list + ] + page_info = PageInfo(list=clazz_dict_list, total=total) + return Result.success(page_info) diff --git a/api/course.py b/api/course.py new file mode 100644 index 0000000..e98e97d --- /dev/null +++ b/api/course.py @@ -0,0 +1,90 @@ +from typing import Optional + +from fastapi import APIRouter +from pydantic import create_model, BaseModel, Field +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Course + +router = APIRouter(prefix="/course",tags=['课程管理']) + +CoursePydantic = pydantic_model_creator(Course) + +CourseCreatePydantic = create_model( + "CourseCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in CoursePydantic.model_fields.items() + }, + major_id=(Optional[int], Field(None, alias="majorId")) +) + + +# 新增 +@router.post("/add") +async def add(course_create_pydantic: CourseCreatePydantic): + db_course = await Course.get_or_none(no=course_create_pydantic.no) + if db_course is not None: + raise CustomException("课程编号重复") + # 将参数转换成 字典数据 + create_data = course_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Course.create(**create_data) # no=xxx,name=xxx,college=xxx + return Result.success() + + +# 更新 +@router.put("/update") +async def add(course_create_pydantic: CourseCreatePydantic): + if course_create_pydantic.id is None: + raise CustomException("缺少参数ID") + # 将参数转换成 字典数据 + update_data = course_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Course.filter(id=course_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{course_id}') +async def delete(course_id: int): + await Course.filter(id=course_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{course_id}') +async def select_by_id(course_id: int): + course = await Course.get_or_none(id=course_id) + return Result.success(course) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(name: str = "", majorId: int = 0): + query = Course.all() + if majorId > 0: + query = query.prefetch_related("major").filter(major__id=majorId) + course_list = await query.filter(name__contains=name) + return Result.success(course_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(name: str = "", no: str = "", teacher: str = "", majorId: int = 0, pageNum: int = 1, pageSize: int = 10): + # name__contains表示根据name进行模糊查询 prefetch_related 关联查询到 major模块的数据 + query = (Course.filter(name__contains=name).filter(no__contains=no).filter(teacher__contains=teacher) + .prefetch_related("major")) + if majorId and majorId > 0: + query = query.filter(major__id=majorId) + course_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + course_dict_list = [ + { + **CoursePydantic.model_validate(course).model_dump(), # id=xxx,no=xxx,name=xxx + "majorName": course.major.name if course.major else None + } + for course in course_list + ] + page_info = PageInfo(list=course_dict_list, total=total) + return Result.success(page_info) diff --git a/api/files.py b/api/files.py new file mode 100644 index 0000000..90a8869 --- /dev/null +++ b/api/files.py @@ -0,0 +1,45 @@ +# 文件上传和下载 +import os +import shutil + +from fastapi import APIRouter, UploadFile, File +from starlette.responses import FileResponse + +from common.exception_handler import CustomException +from common.result import Result + +UPLOAD_DIR = "files" +router = APIRouter(prefix="/files", tags=["文件上传和下载"]) + + +# 文件上传 +@router.post("/upload") +async def upload_file(file: UploadFile = File(...)): + """ + 上传单个文件 + """ + # 获取文件信息 + file_location = os.path.join(UPLOAD_DIR, file.filename) + + # 保存文件 + with open(file_location, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + return Result.success("/files/download/" + file.filename) + + +# 文件下载 +@router.get("/download/{filename}") +async def download_file(filename: str): + """下载文件""" + file_path = os.path.join(UPLOAD_DIR, filename) + + if not os.path.exists(file_path): + raise CustomException("文件不存在") + + # 更新下载计数 + return FileResponse( + path=file_path, + filename=filename, + media_type='application/octet-stream' + ) diff --git a/api/grade.py b/api/grade.py new file mode 100644 index 0000000..0de1350 --- /dev/null +++ b/api/grade.py @@ -0,0 +1,105 @@ +from datetime import datetime +from typing import Optional + +from fastapi import APIRouter +from pydantic import create_model, BaseModel, Field +from tortoise.contrib.pydantic import pydantic_model_creator +from tortoise.expressions import F + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Grade, Student, Course + +router = APIRouter(prefix="/grade", tags=["成绩管理"]) + +GradePydantic = pydantic_model_creator(Grade) + +GradeCreatePydantic = create_model( + "GradeCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in GradePydantic.model_fields.items() + }, + student_id=(Optional[int], Field(None, alias="studentId")), + course_id=(Optional[int], Field(None, alias="courseId")) +) + + +# 新增 +@router.post("/add") +async def add(grade_create_pydantic: GradeCreatePydantic): + # 当前的这个学生是否有已选的课程 + db_grade = await (Grade.filter(student_id=grade_create_pydantic.student_id) + .filter(course_id=grade_create_pydantic.course_id) + .first()) + if db_grade is not None: + raise CustomException("该学生课程成绩已被录入") + grade_create_pydantic.time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # 将参数转换成 字典数据 + create_data = grade_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Grade.create(**create_data) # no=xxx,name=xxx,college=xxx + if grade_create_pydantic.ispass == '是': + # 查询课程的学分 + course = await Course.filter(id=grade_create_pydantic.course_id).first() + add_score = course.score + # 设置学分 + await Student.filter(id=grade_create_pydantic.student_id).update(score=F('score') + add_score) + return Result.success() + + +# 更新 +@router.put("/update") +async def add(grade_create_pydantic: GradeCreatePydantic): + if grade_create_pydantic.id is None: + raise CustomException("缺少参数ID") + # 将参数转换成 字典数据 + update_data = grade_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Grade.filter(id=grade_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{grade_id}') +async def delete(grade_id: int): + await Grade.filter(id=grade_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{grade_id}') +async def select_by_id(grade_id: int): + grade = await Grade.get_or_none(id=grade_id) + return Result.success(grade) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(name: str = ""): + query = Grade.all() + grade_list = await query.filter(name__contains=name) + return Result.success(grade_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(studentId: int = 0, studentName: str = "", courseName: str = "", pageNum: int = 1, pageSize: int = 10): + # name__contains表示根据name进行模糊查询 prefetch_related 关联查询到 major模块的数据 + query = Grade.all().prefetch_related("student", "course") + if studentId > 0: + query = query.filter(student__id=studentId) + if studentName != "": + query = query.filter(student__name__contains=studentName) + if courseName != "": + query = query.filter(course__name__contains=courseName) + grade_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + grade_dict_list = [ + { + **GradePydantic.model_validate(grade).model_dump(), # id=xxx,no=xxx,name=xxx + "studentName": grade.student.name if grade.student else None, + "courseName": grade.course.name if grade.course else None + } + for grade in grade_list + ] + page_info = PageInfo(list=grade_dict_list, total=total) + return Result.success(page_info) diff --git a/api/major.py b/api/major.py new file mode 100644 index 0000000..4d6f104 --- /dev/null +++ b/api/major.py @@ -0,0 +1,80 @@ +from typing import Optional + +from fastapi import APIRouter +from pydantic import create_model, BaseModel +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Major + +router = APIRouter(prefix="/major",tags=["专业"]) + +MajorPydantic = pydantic_model_creator(Major) + +MajorCreatePydantic = create_model( + "MajorCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in MajorPydantic.model_fields.items() + } +) + + +# 新增 +@router.post("/add") +async def add(major_create_pydantic: MajorCreatePydantic): + db_major = await Major.get_or_none(no=major_create_pydantic.no) + if db_major is not None: + raise CustomException("专业代码重复") + # 将参数转换成 字典数据 + create_data = major_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Major.create(**create_data) # no=xxx,name=xxx,college=xxx + return Result.success() + + +# 更新 +@router.put("/update") +async def add(major_create_pydantic: MajorCreatePydantic): + if major_create_pydantic.id is None: + raise CustomException("缺少参数ID") + # 将参数转换成 字典数据 + update_data = major_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Major.filter(id=major_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{major_id}') +async def delete(major_id: int): + await Major.filter(id=major_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{major_id}') +async def select_by_id(major_id: int): + major = await Major.get_or_none(id=major_id) + return Result.success(major) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(name: str = ""): + major_list = await Major.filter(name__contains=name) # name__contains表示根据name进行模糊查询 + return Result.success(major_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(name: str = "", pageNum: int = 1, pageSize: int = 10): + query = Major.filter(name__contains=name) # name__contains表示根据name进行模糊查询 + major_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + # major_list 转成字典数据 + major_dict_list = [ + MajorPydantic.model_validate(major).model_dump() + for major in major_list + ] + page_info = PageInfo(list=major_dict_list, total=total) + return Result.success(page_info) diff --git a/api/notice.py b/api/notice.py new file mode 100644 index 0000000..a88d098 --- /dev/null +++ b/api/notice.py @@ -0,0 +1,79 @@ +from typing import Optional + +from datetime import datetime +from fastapi import APIRouter +from pydantic import create_model, BaseModel +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Notice + +router = APIRouter(prefix="/notice", tags=["公告管理"]) + +NoticePydantic = pydantic_model_creator(Notice) + +NoticeCreatePydantic = create_model( + "NoticeCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in NoticePydantic.model_fields.items() + } +) + + +# 新增 +@router.post("/add") +async def add(notice_create_pydantic: NoticeCreatePydantic): + notice_create_pydantic.time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # 将参数转换成 字典数据 + create_data = notice_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Notice.create(**create_data) # no=xxx,name=xxx,college=xxx + return Result.success() + + +# 更新 +@router.put("/update") +async def add(notice_create_pydantic: NoticeCreatePydantic): + if notice_create_pydantic.id is None: + raise CustomException("缺少参数ID") + # 将参数转换成 字典数据 + update_data = notice_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Notice.filter(id=notice_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{notice_id}') +async def delete(notice_id: int): + await Notice.filter(id=notice_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{notice_id}') +async def select_by_id(notice_id: int): + notice = await Notice.get_or_none(id=notice_id) + return Result.success(notice) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(title: str = ""): + notice_list = await Notice.filter(title__contains=title).order_by("-id") # title__contains表示根据name进行模糊查询 + return Result.success(notice_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(title: str = "", pageNum: int = 1, pageSize: int = 10): + query = Notice.filter(title__contains=title) # title__contains表示根据name进行模糊查询 + notice_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + # notice_list 转成字典数据 + notice_dict_list = [ + NoticePydantic.model_validate(notice).model_dump() + for notice in notice_list + ] + page_info = PageInfo(list=notice_dict_list, total=total) + return Result.success(page_info) diff --git a/api/student.py b/api/student.py new file mode 100644 index 0000000..2ec5a48 --- /dev/null +++ b/api/student.py @@ -0,0 +1,97 @@ +from typing import Optional + +from fastapi import APIRouter +from pydantic import create_model, Field +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import Student, Clazz + +router = APIRouter(prefix="/student", tags=["学生管理"]) + +StudentPydantic = pydantic_model_creator(Student) + +StudentCreatePydantic = create_model( + "StudentCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in StudentPydantic.model_fields.items() + }, + clazz_id=(Optional[int], Field(None, alias="clazzId")) +) + + +# 新增 +@router.post("/add") +async def add(student_create_pydantic: StudentCreatePydantic): + db_student = await Student.get_or_none(username=student_create_pydantic.username) + if db_student is not None: + raise CustomException("账号重复") + student_create_pydantic.score = 0 + # 将参数转换成 字典数据 + create_data = student_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + create_data['role'] = '学生' # 设置默认的角色 + create_data['password'] = '123' # 设置默认的密码 + await Student.create(**create_data) # no=xxx,name=xxx,college=xxx + return Result.success() + + +# 更新 +@router.put("/update") +async def add(student_create_pydantic: StudentCreatePydantic): + if student_create_pydantic.id is None: + raise CustomException("缺少参数ID") + # 将参数转换成 字典数据 + update_data = student_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await Student.filter(id=student_create_pydantic.id).update(**update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{student_id}') +async def delete(student_id: int): + await Student.filter(id=student_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{student_id}') +async def select_by_id(student_id: int): + student = await Student.get_or_none(id=student_id) + return Result.success(student) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(name: str = ""): + student_list = await Student.filter(name__contains=name) # name__contains表示根据name进行模糊查询 + return Result.success(student_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(name: str = "", clazzName: str = "", majorName: str = "", pageNum: int = 1, pageSize: int = 10): + # name__contains表示根据name进行模糊查询 prefetch_related 关联查询到 major模块的数据 + query = Student.filter(name__contains=name) + if clazzName and clazzName != "": + query = query.filter(clazz__name__contains=clazzName) + if majorName and clazzName != "": + query = query.filter(clazz__major__name__contains=majorName) + query = query.prefetch_related("clazz__major") + student_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + # student_list 转成字典数据 + # majorName 怎么返回?? + # {id=xxx, name=xxx, no=xxx} + student_dict_list = [ + { + **StudentPydantic.model_validate(student).model_dump(), # id=xxx,no=xxx,name=xxx + "clazzId": student.clazz.id if student.clazz else None, + "clazzName": student.clazz.name if student.clazz else None, + "majorName": student.clazz.major.name if student.clazz and student.clazz.major else None + } + for student in student_list + ] + page_info = PageInfo(list=student_dict_list, total=total) + return Result.success(page_info) diff --git a/api/student_course.py b/api/student_course.py new file mode 100644 index 0000000..e504144 --- /dev/null +++ b/api/student_course.py @@ -0,0 +1,124 @@ +from datetime import datetime +from typing import Optional + +from fastapi import APIRouter +from pydantic import create_model, BaseModel, Field +from tortoise.contrib.pydantic import pydantic_model_creator + +from common.exception_handler import CustomException +from common.result import Result, PageInfo +from models import StudentCourse, Grade + +router = APIRouter(prefix="/studentCourse", tags=["学生选课"]) + +StudentCoursePydantic = pydantic_model_creator(StudentCourse) + +StudentCourseCreatePydantic = create_model( + "StudentCourseCreatePydantic", + **{ + name: (Optional[field.annotation], None) + for name, field in StudentCoursePydantic.model_fields.items() + }, + student_id=(Optional[int], Field(None, alias="studentId")), + course_id=(Optional[int], Field(None, alias="courseId")), +) + + +# 新增 +@router.post("/add") +async def add(student_course_create_pydantic: StudentCourseCreatePydantic): + # 当前的这个学生是否有已选的课程 + db_student_course = await (StudentCourse.filter(student_id=student_course_create_pydantic.student_id) + .filter(course_id=student_course_create_pydantic.course_id) + .filter(status__not='已退').filter(status__not='未选中') + .first()) + if db_student_course is not None: + raise CustomException("该课程已被选") + student_course_create_pydantic.time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # 将参数转换成 字典数据 + create_data = student_course_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await StudentCourse.create(**create_data) # no=xxx,name=xxx,college=xxx + return Result.success() + + +# 更新 +@router.put("/update") +async def add(student_course_create_pydantic: StudentCourseCreatePydantic): + if student_course_create_pydantic.id is None: + raise CustomException("缺少参数ID") + if student_course_create_pydantic.status == '已退': + grade = await (Grade.filter(student_id=student_course_create_pydantic.student_id) + .filter(course_id=student_course_create_pydantic.course_id).first()) + if grade is not None: + raise CustomException('当前课程已打分,无法退课') + if student_course_create_pydantic.checkStatus == '通过': + student_course_create_pydantic.status = '已选' + elif student_course_create_pydantic.checkStatus == '拒绝': + student_course_create_pydantic.status = '未选中' + # 将参数转换成 字典数据 + update_data = student_course_create_pydantic.model_dump(exclude_unset=True, exclude={"id"}) + await StudentCourse.filter(id=student_course_create_pydantic.id).update( + **update_data) # no=xxx,name=xxx,college=xxx where id = xxx + return Result.success() + + +# 删除 +@router.delete('/delete/{student_course_id}') +async def delete(student_course_id: int): + await StudentCourse.filter(id=student_course_id).delete() + return Result.success() + + +# 单个查询 +@router.get('/selectById/{student_course_id}') +async def select_by_id(student_course_id: int): + studentCourse = await StudentCourse.get_or_none(id=student_course_id) + return Result.success(studentCourse) + + +# 查询所有数据 +@router.get('/selectAll') +async def select_all(studentId: int = 0, status: str = ""): + query = StudentCourse.all().prefetch_related("course") + if studentId > 0: + query = query.filter(student__id=studentId) + if status != "": + query = query.filter(status=status) + student_course_list = await query + student_course_dict_list = [ + { + **StudentCoursePydantic.model_validate(student_course).model_dump(), + "courseId": student_course.course.id if student_course.course else None, + "courseName": student_course.course.name if student_course.course else None + } + for student_course in student_course_list + ] + return Result.success(student_course_dict_list) + + +# 分页查询数据 +@router.get('/selectPage') +async def select_page(studentName: str = "", courseName: str = "", studentId: int = 0, pageNum: int = 1, + pageSize: int = 10): + # prefetch_related 关联查询到 major模块的数据 + query = StudentCourse.all().prefetch_related("course", "student") + if studentName != "": + query = query.filter(student__name__contains=studentName) + if courseName != "": + query = query.filter(course__name__contains=courseName) + if studentId > 0: + query = query.filter(student__id=studentId) + student_course_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize) + total = await query.count() + student_course_dict_list = [ + { + **StudentCoursePydantic.model_validate(student_course).model_dump(), # id=xxx,no=xxx,name=xxx + "studentId": student_course.student.id if student_course.student else None, + "courseId": student_course.course.id if student_course.course else None, + "studentName": student_course.student.name if student_course.student else None, + "courseName": student_course.course.name if student_course.course else None + } + for student_course in student_course_list + ] + page_info = PageInfo(list=student_course_dict_list, total=total) + return Result.success(page_info) diff --git a/common/exception_handler.py b/common/exception_handler.py new file mode 100644 index 0000000..71a1764 --- /dev/null +++ b/common/exception_handler.py @@ -0,0 +1,44 @@ +from fastapi import FastAPI, Request +from fastapi.exceptions import RequestValidationError +from starlette import status +from starlette.responses import JSONResponse + + +# 自定义异常类 +class CustomException(Exception): + def __init__(self, message: str): + self.message = message + + +def setup_exceptions(app: FastAPI): + @app.exception_handler(CustomException) + async def custom_exception_handler(request: Request, exc: CustomException): + return JSONResponse( + status_code=status.HTTP_200_OK, # http总是返回200 + content={"code": "500", "msg": exc.message} + ) + + @app.exception_handler(RequestValidationError) + async def validate_exception_handler(request: Request, exc: RequestValidationError): + print("捕获到422错误:", exc.errors()) + """处理422异常""" + # 返回统一格式 + return JSONResponse( + status_code=status.HTTP_200_OK, # http总是返回200 + content={"code": "500", "msg": "请求参数错误"} + ) + + @app.exception_handler(Exception) + async def global_exception_handler(request: Request, exc: Exception): + print("捕获到系统错误:", repr(exc)) + """处理所有异常""" + # 返回统一格式 + response = JSONResponse( + status_code=status.HTTP_200_OK, # http总是返回200 + content={"code": "500", "msg": "系统错误"} + ) + # 允许所有源(与你的 CORS 配置一致) + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Methods"] = "*" + response.headers["Access-Control-Allow-Headers"] = "*" + return response diff --git a/common/result.py b/common/result.py new file mode 100644 index 0000000..da3f46d --- /dev/null +++ b/common/result.py @@ -0,0 +1,26 @@ +from typing import Any, List + +from fastapi.encoders import jsonable_encoder +from pydantic import BaseModel + + +class Result(BaseModel): + code: str + msg: str + data: Any = None + + @staticmethod + def success(data: Any = None): + json_data = None + if data is not None: + json_data = jsonable_encoder(data) + return Result(code="200", msg="请求成功", data=json_data) + + @staticmethod + def error(msg: str = "请求失败"): + return Result(code="500", msg=msg) + + +class PageInfo(BaseModel): + total: int = 0 + list: List[Any] = [] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..118ac6b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,66 @@ +version: "3.9" + +services: + # MySQL 数据库服务 + mysql: + image: mysql:8.0 + container_name: student-mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: "123456" + MYSQL_DATABASE: "student_system" + MYSQL_CHARSET: "utf8mb4" + MYSQL_COLLATION: "utf8mb4_unicode_ci" + TZ: "Asia/Shanghai" + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./mysql/conf:/etc/mysql/conf.d + - ./mysql/init:/docker-entrypoint-initdb.d + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --default-authentication-plugin=mysql_native_password + networks: + - student-net + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p123456"] + interval: 10s + timeout: 5s + retries: 10 + + # FastAPI 应用服务 + api: + build: + context: . + dockerfile: Dockerfile + container_name: student-api + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + environment: + DB_HOST: "mysql" + DB_PORT: "3306" + DB_NAME: "student_system" + DB_USER: "root" + DB_PASSWORD: "123456" + TZ: "Asia/Shanghai" + ports: + - "9090:9090" + volumes: + - .:/app + - ./files:/app/files + networks: + - student-net + +# 数据卷(持久化 MySQL 数据) +volumes: + mysql_data: + driver: local + +# 自定义网络(服务间通信) +networks: + student-net: + driver: bridge diff --git a/files/1.png b/files/1.png new file mode 100644 index 0000000..3163128 Binary files /dev/null and b/files/1.png differ diff --git a/files/111.jpeg b/files/111.jpeg new file mode 100644 index 0000000..7a638f6 Binary files /dev/null and b/files/111.jpeg differ diff --git a/files/2.png b/files/2.png new file mode 100644 index 0000000..e4278ad Binary files /dev/null and b/files/2.png differ diff --git a/files/3.jpeg b/files/3.jpeg new file mode 100644 index 0000000..ec1ef01 Binary files /dev/null and b/files/3.jpeg differ diff --git a/files/3.png b/files/3.png new file mode 100644 index 0000000..a3bcfbc Binary files /dev/null and b/files/3.png differ diff --git a/files/4.png b/files/4.png new file mode 100644 index 0000000..dd03812 Binary files /dev/null and b/files/4.png differ diff --git a/files/5.png b/files/5.png new file mode 100644 index 0000000..8d24115 Binary files /dev/null and b/files/5.png differ diff --git a/files/QQ图片20230505235047.jpg b/files/QQ图片20230505235047.jpg new file mode 100644 index 0000000..e0a7629 Binary files /dev/null and b/files/QQ图片20230505235047.jpg differ diff --git a/files/Snipaste_2026-03-23_16-07-18.png b/files/Snipaste_2026-03-23_16-07-18.png new file mode 100644 index 0000000..0641453 Binary files /dev/null and b/files/Snipaste_2026-03-23_16-07-18.png differ diff --git a/files/a04e8d82ce39fd976acf2a81f6127136.jpg b/files/a04e8d82ce39fd976acf2a81f6127136.jpg new file mode 100644 index 0000000..941a294 Binary files /dev/null and b/files/a04e8d82ce39fd976acf2a81f6127136.jpg differ diff --git a/files/cdafb95be62f733daabf36fd635e2f85.jpg b/files/cdafb95be62f733daabf36fd635e2f85.jpg new file mode 100644 index 0000000..2deb2a9 Binary files /dev/null and b/files/cdafb95be62f733daabf36fd635e2f85.jpg differ diff --git a/files/cyf.png b/files/cyf.png new file mode 100644 index 0000000..94d0609 Binary files /dev/null and b/files/cyf.png differ diff --git a/files/ric.png b/files/ric.png new file mode 100644 index 0000000..d78c7e7 Binary files /dev/null and b/files/ric.png differ diff --git a/files/swf.jpg b/files/swf.jpg new file mode 100644 index 0000000..b44a270 Binary files /dev/null and b/files/swf.jpg differ diff --git a/files/swk.jpg b/files/swk.jpg new file mode 100644 index 0000000..e6535ed Binary files /dev/null and b/files/swk.jpg differ diff --git a/files/yzn.png b/files/yzn.png new file mode 100644 index 0000000..a31a5f1 Binary files /dev/null and b/files/yzn.png differ diff --git a/files/企业微信截图_1771778897103.png b/files/企业微信截图_1771778897103.png new file mode 100644 index 0000000..fc2f9bf Binary files /dev/null and b/files/企业微信截图_1771778897103.png differ diff --git a/files/微信图片_2026-03-12_162905_158.jpg b/files/微信图片_2026-03-12_162905_158.jpg new file mode 100644 index 0000000..321d8c5 Binary files /dev/null and b/files/微信图片_2026-03-12_162905_158.jpg differ diff --git a/files/酷.png b/files/酷.png new file mode 100644 index 0000000..ae7f916 Binary files /dev/null and b/files/酷.png differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..9ca262f --- /dev/null +++ b/main.py @@ -0,0 +1,38 @@ +from fastapi import FastAPI +import uvicorn +from starlette.middleware.cors import CORSMiddleware +from tortoise.contrib.fastapi import register_tortoise + +from api import api_router +from common.exception_handler import setup_exceptions + +from common.result import Result +from settings import TORTOISE_ORM + +app = FastAPI() + +# 跨域配置 CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 允许所有源 + allow_methods=["*"], # 允许所有 HTTP 方法 + allow_headers=["*"], # 允许所有请求头 +) + + +# 配置路由 +app.include_router(api_router) +# 注册orm +register_tortoise(app, config=TORTOISE_ORM, add_exception_handlers=True, generate_schemas=True) +# 注册异常处理器 +setup_exceptions(app) + + +@app.get("/") +async def root(): + return Result.success() + + +if __name__ == "__main__": + uvicorn.run("main:app", host='0.0.0.0',reload=True, port=9090) + diff --git a/models.py b/models.py new file mode 100644 index 0000000..c316673 --- /dev/null +++ b/models.py @@ -0,0 +1,102 @@ +from tortoise.models import Model +from tortoise import fields + + +class Admin(Model): + """管理员模块""" + id = fields.IntField(pk=True, null=False) + username = fields.CharField(max_length=255, null=True) + password = fields.CharField(max_length=255, null=True) + name = fields.CharField(max_length=255, null=True) + avatar = fields.CharField(max_length=255, null=True) + role = fields.CharField(max_length=255, null=True) + + class Meta: + table = 'admin' + + +class Major(Model): + """专业模块""" + id = fields.IntField(pk=True, null=False) + no = fields.CharField(max_length=255, null=True) + name = fields.CharField(max_length=255, null=True) + college = fields.CharField(max_length=255, null=True) + + class Meta: + table = 'major' + + +class Clazz(Model): + """班级模块""" + id = fields.IntField(pk=True, null=False) + no = fields.CharField(max_length=255, null=True) + name = fields.CharField(max_length=255, null=True) + major = fields.ForeignKeyField('models.Major', null=True) + teacher = fields.CharField(max_length=255, null=True) + + class Meta: + table = 'clazz' + + +class Student(Model): + """学生模块""" + id = fields.IntField(pk=True, null=False) + username = fields.CharField(max_length=255, null=True) + password = fields.CharField(max_length=255, null=True) + name = fields.CharField(max_length=255, null=True) + avatar = fields.CharField(max_length=255, null=True) + role = fields.CharField(max_length=255, null=True) + score = fields.IntField(max_length=11, null=True) + clazz = fields.ForeignKeyField('models.Clazz', null=True) + + class Meta: + table = 'student' + + +class Course(Model): + """课程模块""" + id = fields.IntField(pk=True, null=False) + no = fields.CharField(max_length=255, null=True) + name = fields.CharField(max_length=255, null=True) + score = fields.IntField(max_length=11, null=True) + teacher = fields.CharField(max_length=255, null=True) + major = fields.ForeignKeyField('models.Major', null=True) + + class Meta: + table = 'course' + + +class StudentCourse(Model): + """选课信息""" + id = fields.IntField(pk=True, null=False) + student = fields.ForeignKeyField('models.Student', null=True) + course = fields.ForeignKeyField('models.Course', null=True) + year = fields.CharField(max_length=255, null=True) + status = fields.CharField(max_length=255, null=True) + checkStatus = fields.CharField(max_length=255, null=True, source_field="check_status") + time = fields.CharField(max_length=255, null=True) + + class Meta: + table = 'student_course' + + +class Grade(Model): + id = fields.IntField(pk=True, null=False) + student = fields.ForeignKeyField('models.Student', null=True) + course = fields.ForeignKeyField('models.Course', null=True) + score = fields.IntField(max_length=11, null=True) + ispass = fields.CharField(max_length=255, null=True) + time = fields.CharField(max_length=255, null=True) + + class Meta: + table = 'grade' + + +class Notice(Model): + id = fields.IntField(pk=True, null=False) + title = fields.CharField(max_length=255, null=True) + content = fields.CharField(max_length=255, null=True) + time = fields.CharField(max_length=255, null=True) + + class Meta: + table = 'notice' diff --git a/requirements_1.txt b/requirements_1.txt new file mode 100644 index 0000000..6b7c026 --- /dev/null +++ b/requirements_1.txt @@ -0,0 +1,16 @@ +# FastAPI 核心框架(保留你指定的版本) +fastapi~=0.124.2 +# ASGI 服务器(带standard扩展,适配最新版) +uvicorn[standard]~=0.38.0 +# Tortoise-ORM(0.25.1 适配Pydantic v2,删除重复的tortoise依赖) +tortoise-orm~=0.25.1 +# 表单/文件上传处理 +python-multipart~=0.0.20 +# Starlette(匹配FastAPI 0.124.x的要求:≥0.40.0 <0.51.0) +starlette~=0.40.0 +# MySQL异步驱动 +aiomysql~=0.3.2 +# Pydantic v2(兼容Tortoise-ORM 0.25.1) +pydantic~=2.12.5 +# 加密包(指定稳定版本) +cryptography>=42.0.0 \ No newline at end of file diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..e57852f --- /dev/null +++ b/settings.py @@ -0,0 +1,29 @@ +import os + +TORTOISE_ORM = { + "connections": { + "default": { + "engine": "tortoise.backends.mysql", + "credentials": { + "host": os.getenv("DB_HOST", "localhost"), + "port": int(os.getenv("DB_PORT", "3306")), + "database": os.getenv("DB_NAME", "student_system"), + "user": os.getenv("DB_USER", "root"), + "password": os.getenv("DB_PASSWORD", "123456"), + "minsize": 1, + "maxsize": 10, + "charset": "utf8mb4", + "echo": True + } + }, + }, + "apps": { + "models": { + "models": ["models"], + "default_connection": "default", + } + }, + "use_tz": True, + "timezone": "Asia/Shanghai" +} +