Spaces:
Sleeping
Sleeping
master
#1
by eithney - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
- .gitattributes +0 -7
- .gitignore +0 -92
- .vscode/PythonImportHelper-v2-Completion.json +0 -1277
- Dockerfile +0 -42
- README.md +5 -327
- app.py +0 -1068
- books.db +0 -3
- data/10005_一年级上册/audios/page_002_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_003_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_02.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_03.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_04.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_05.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_06.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_07.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_08.mp3 +0 -3
- data/10005_一年级上册/audios/page_004_piece_09.mp3 +0 -3
- data/10005_一年级上册/audios/page_005_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_005_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_005_piece_02.mp3 +0 -3
- data/10005_一年级上册/audios/page_005_piece_03.mp3 +0 -3
- data/10005_一年级上册/audios/page_005_piece_04.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_02.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_03.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_04.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_05.mp3 +0 -3
- data/10005_一年级上册/audios/page_006_piece_06.mp3 +0 -3
- data/10005_一年级上册/audios/page_007_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_007_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_007_piece_02.mp3 +0 -3
- data/10005_一年级上册/audios/page_009_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_009_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_009_piece_02.mp3 +0 -3
- data/10005_一年级上册/audios/page_009_piece_03.mp3 +0 -3
- data/10005_一年级上册/audios/page_009_piece_04.mp3 +0 -3
- data/10005_一年级上册/audios/page_009_piece_05.mp3 +0 -3
- data/10005_一年级上册/audios/page_010_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_010_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_00.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_01.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_02.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_03.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_04.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_05.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_06.mp3 +0 -3
- data/10005_一年级上册/audios/page_011_piece_07.mp3 +0 -3
.gitattributes
CHANGED
|
@@ -33,10 +33,3 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
-
FZKT_GBK.woff filter=lfs diff=lfs merge=lfs -text
|
| 37 |
-
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
-
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
-
*.woff filter=lfs diff=lfs merge=lfs -text
|
| 40 |
-
*.ttf filter=lfs diff=lfs merge=lfs -text
|
| 41 |
-
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
| 42 |
-
*.db filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
@@ -1,92 +0,0 @@
|
|
| 1 |
-
# Python 编译文件
|
| 2 |
-
*.pyc
|
| 3 |
-
*.pyo
|
| 4 |
-
*.pyd
|
| 5 |
-
__pycache__/
|
| 6 |
-
*.py[cod]
|
| 7 |
-
*$py.class
|
| 8 |
-
|
| 9 |
-
# C 扩展
|
| 10 |
-
*.so
|
| 11 |
-
|
| 12 |
-
# 分发/打包
|
| 13 |
-
.Python
|
| 14 |
-
build/
|
| 15 |
-
develop-eggs/
|
| 16 |
-
dist/
|
| 17 |
-
downloads/
|
| 18 |
-
eggs/
|
| 19 |
-
.eggs/
|
| 20 |
-
lib/
|
| 21 |
-
lib64/
|
| 22 |
-
parts/
|
| 23 |
-
sdist/
|
| 24 |
-
var/
|
| 25 |
-
wheels/
|
| 26 |
-
*.egg-info/
|
| 27 |
-
.installed.cfg
|
| 28 |
-
*.egg
|
| 29 |
-
|
| 30 |
-
# PyInstaller
|
| 31 |
-
*.manifest
|
| 32 |
-
*.spec
|
| 33 |
-
|
| 34 |
-
# 单元测试 / 覆盖率
|
| 35 |
-
htmlcov/
|
| 36 |
-
.tox/
|
| 37 |
-
.coverage
|
| 38 |
-
.coverage.*
|
| 39 |
-
.cache
|
| 40 |
-
nosetests.xml
|
| 41 |
-
coverage.xml
|
| 42 |
-
*.cover
|
| 43 |
-
.hypothesis/
|
| 44 |
-
.pytest_cache/
|
| 45 |
-
|
| 46 |
-
# 虚拟环境
|
| 47 |
-
venv/
|
| 48 |
-
ENV/
|
| 49 |
-
env/
|
| 50 |
-
.venv
|
| 51 |
-
|
| 52 |
-
# Flask
|
| 53 |
-
instance/
|
| 54 |
-
.webassets-cache
|
| 55 |
-
*.db
|
| 56 |
-
|
| 57 |
-
# 日志文件
|
| 58 |
-
logs/
|
| 59 |
-
*.log
|
| 60 |
-
|
| 61 |
-
# 环境变量
|
| 62 |
-
.env
|
| 63 |
-
.env.local
|
| 64 |
-
|
| 65 |
-
# IDE
|
| 66 |
-
.vscode/
|
| 67 |
-
.idea/
|
| 68 |
-
*.swp
|
| 69 |
-
*.swo
|
| 70 |
-
*~
|
| 71 |
-
.DS_Store
|
| 72 |
-
|
| 73 |
-
# 备份文件
|
| 74 |
-
*.backup
|
| 75 |
-
*.bak
|
| 76 |
-
*.old
|
| 77 |
-
|
| 78 |
-
# Jupyter Notebook
|
| 79 |
-
.ipynb_checkpoints
|
| 80 |
-
|
| 81 |
-
# pyenv
|
| 82 |
-
.python-version
|
| 83 |
-
|
| 84 |
-
# pipenv
|
| 85 |
-
Pipfile.lock
|
| 86 |
-
|
| 87 |
-
# poetry
|
| 88 |
-
poetry.lock
|
| 89 |
-
|
| 90 |
-
# 项目特定
|
| 91 |
-
# 如果不想提交某些大文件或临时文件,在这里添加
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.vscode/PythonImportHelper-v2-Completion.json
DELETED
|
@@ -1,1277 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{
|
| 3 |
-
"label": "os",
|
| 4 |
-
"kind": 6,
|
| 5 |
-
"isExtraImport": true,
|
| 6 |
-
"importPath": "os",
|
| 7 |
-
"description": "os",
|
| 8 |
-
"detail": "os",
|
| 9 |
-
"documentation": {}
|
| 10 |
-
},
|
| 11 |
-
{
|
| 12 |
-
"label": "sys",
|
| 13 |
-
"kind": 6,
|
| 14 |
-
"isExtraImport": true,
|
| 15 |
-
"importPath": "sys",
|
| 16 |
-
"description": "sys",
|
| 17 |
-
"detail": "sys",
|
| 18 |
-
"documentation": {}
|
| 19 |
-
},
|
| 20 |
-
{
|
| 21 |
-
"label": "json",
|
| 22 |
-
"kind": 6,
|
| 23 |
-
"isExtraImport": true,
|
| 24 |
-
"importPath": "json",
|
| 25 |
-
"description": "json",
|
| 26 |
-
"detail": "json",
|
| 27 |
-
"documentation": {}
|
| 28 |
-
},
|
| 29 |
-
{
|
| 30 |
-
"label": "time",
|
| 31 |
-
"kind": 6,
|
| 32 |
-
"isExtraImport": true,
|
| 33 |
-
"importPath": "time",
|
| 34 |
-
"description": "time",
|
| 35 |
-
"detail": "time",
|
| 36 |
-
"documentation": {}
|
| 37 |
-
},
|
| 38 |
-
{
|
| 39 |
-
"label": "datetime",
|
| 40 |
-
"importPath": "datetime",
|
| 41 |
-
"description": "datetime",
|
| 42 |
-
"isExtraImport": true,
|
| 43 |
-
"detail": "datetime",
|
| 44 |
-
"documentation": {}
|
| 45 |
-
},
|
| 46 |
-
{
|
| 47 |
-
"label": "datetime",
|
| 48 |
-
"importPath": "datetime",
|
| 49 |
-
"description": "datetime",
|
| 50 |
-
"isExtraImport": true,
|
| 51 |
-
"detail": "datetime",
|
| 52 |
-
"documentation": {}
|
| 53 |
-
},
|
| 54 |
-
{
|
| 55 |
-
"label": "Path",
|
| 56 |
-
"importPath": "pathlib",
|
| 57 |
-
"description": "pathlib",
|
| 58 |
-
"isExtraImport": true,
|
| 59 |
-
"detail": "pathlib",
|
| 60 |
-
"documentation": {}
|
| 61 |
-
},
|
| 62 |
-
{
|
| 63 |
-
"label": "Path",
|
| 64 |
-
"importPath": "pathlib",
|
| 65 |
-
"description": "pathlib",
|
| 66 |
-
"isExtraImport": true,
|
| 67 |
-
"detail": "pathlib",
|
| 68 |
-
"documentation": {}
|
| 69 |
-
},
|
| 70 |
-
{
|
| 71 |
-
"label": "Path",
|
| 72 |
-
"importPath": "pathlib",
|
| 73 |
-
"description": "pathlib",
|
| 74 |
-
"isExtraImport": true,
|
| 75 |
-
"detail": "pathlib",
|
| 76 |
-
"documentation": {}
|
| 77 |
-
},
|
| 78 |
-
{
|
| 79 |
-
"label": "Path",
|
| 80 |
-
"importPath": "pathlib",
|
| 81 |
-
"description": "pathlib",
|
| 82 |
-
"isExtraImport": true,
|
| 83 |
-
"detail": "pathlib",
|
| 84 |
-
"documentation": {}
|
| 85 |
-
},
|
| 86 |
-
{
|
| 87 |
-
"label": "Path",
|
| 88 |
-
"importPath": "pathlib",
|
| 89 |
-
"description": "pathlib",
|
| 90 |
-
"isExtraImport": true,
|
| 91 |
-
"detail": "pathlib",
|
| 92 |
-
"documentation": {}
|
| 93 |
-
},
|
| 94 |
-
{
|
| 95 |
-
"label": "Path",
|
| 96 |
-
"importPath": "pathlib",
|
| 97 |
-
"description": "pathlib",
|
| 98 |
-
"isExtraImport": true,
|
| 99 |
-
"detail": "pathlib",
|
| 100 |
-
"documentation": {}
|
| 101 |
-
},
|
| 102 |
-
{
|
| 103 |
-
"label": "Path",
|
| 104 |
-
"importPath": "pathlib",
|
| 105 |
-
"description": "pathlib",
|
| 106 |
-
"isExtraImport": true,
|
| 107 |
-
"detail": "pathlib",
|
| 108 |
-
"documentation": {}
|
| 109 |
-
},
|
| 110 |
-
{
|
| 111 |
-
"label": "Flask",
|
| 112 |
-
"importPath": "flask",
|
| 113 |
-
"description": "flask",
|
| 114 |
-
"isExtraImport": true,
|
| 115 |
-
"detail": "flask",
|
| 116 |
-
"documentation": {}
|
| 117 |
-
},
|
| 118 |
-
{
|
| 119 |
-
"label": "render_template",
|
| 120 |
-
"importPath": "flask",
|
| 121 |
-
"description": "flask",
|
| 122 |
-
"isExtraImport": true,
|
| 123 |
-
"detail": "flask",
|
| 124 |
-
"documentation": {}
|
| 125 |
-
},
|
| 126 |
-
{
|
| 127 |
-
"label": "send_from_directory",
|
| 128 |
-
"importPath": "flask",
|
| 129 |
-
"description": "flask",
|
| 130 |
-
"isExtraImport": true,
|
| 131 |
-
"detail": "flask",
|
| 132 |
-
"documentation": {}
|
| 133 |
-
},
|
| 134 |
-
{
|
| 135 |
-
"label": "jsonify",
|
| 136 |
-
"importPath": "flask",
|
| 137 |
-
"description": "flask",
|
| 138 |
-
"isExtraImport": true,
|
| 139 |
-
"detail": "flask",
|
| 140 |
-
"documentation": {}
|
| 141 |
-
},
|
| 142 |
-
{
|
| 143 |
-
"label": "request",
|
| 144 |
-
"importPath": "flask",
|
| 145 |
-
"description": "flask",
|
| 146 |
-
"isExtraImport": true,
|
| 147 |
-
"detail": "flask",
|
| 148 |
-
"documentation": {}
|
| 149 |
-
},
|
| 150 |
-
{
|
| 151 |
-
"label": "session",
|
| 152 |
-
"importPath": "flask",
|
| 153 |
-
"description": "flask",
|
| 154 |
-
"isExtraImport": true,
|
| 155 |
-
"detail": "flask",
|
| 156 |
-
"documentation": {}
|
| 157 |
-
},
|
| 158 |
-
{
|
| 159 |
-
"label": "CORS",
|
| 160 |
-
"importPath": "flask_cors",
|
| 161 |
-
"description": "flask_cors",
|
| 162 |
-
"isExtraImport": true,
|
| 163 |
-
"detail": "flask_cors",
|
| 164 |
-
"documentation": {}
|
| 165 |
-
},
|
| 166 |
-
{
|
| 167 |
-
"label": "logging",
|
| 168 |
-
"kind": 6,
|
| 169 |
-
"isExtraImport": true,
|
| 170 |
-
"importPath": "logging",
|
| 171 |
-
"description": "logging",
|
| 172 |
-
"detail": "logging",
|
| 173 |
-
"documentation": {}
|
| 174 |
-
},
|
| 175 |
-
{
|
| 176 |
-
"label": "RotatingFileHandler",
|
| 177 |
-
"importPath": "logging.handlers",
|
| 178 |
-
"description": "logging.handlers",
|
| 179 |
-
"isExtraImport": true,
|
| 180 |
-
"detail": "logging.handlers",
|
| 181 |
-
"documentation": {}
|
| 182 |
-
},
|
| 183 |
-
{
|
| 184 |
-
"label": "get_db_instance",
|
| 185 |
-
"importPath": "db_manager",
|
| 186 |
-
"description": "db_manager",
|
| 187 |
-
"isExtraImport": true,
|
| 188 |
-
"detail": "db_manager",
|
| 189 |
-
"documentation": {}
|
| 190 |
-
},
|
| 191 |
-
{
|
| 192 |
-
"label": "re",
|
| 193 |
-
"kind": 6,
|
| 194 |
-
"isExtraImport": true,
|
| 195 |
-
"importPath": "re",
|
| 196 |
-
"description": "re",
|
| 197 |
-
"detail": "re",
|
| 198 |
-
"documentation": {}
|
| 199 |
-
},
|
| 200 |
-
{
|
| 201 |
-
"label": "subprocess",
|
| 202 |
-
"kind": 6,
|
| 203 |
-
"isExtraImport": true,
|
| 204 |
-
"importPath": "subprocess",
|
| 205 |
-
"description": "subprocess",
|
| 206 |
-
"detail": "subprocess",
|
| 207 |
-
"documentation": {}
|
| 208 |
-
},
|
| 209 |
-
{
|
| 210 |
-
"label": "urllib.parse",
|
| 211 |
-
"kind": 6,
|
| 212 |
-
"isExtraImport": true,
|
| 213 |
-
"importPath": "urllib.parse",
|
| 214 |
-
"description": "urllib.parse",
|
| 215 |
-
"detail": "urllib.parse",
|
| 216 |
-
"documentation": {}
|
| 217 |
-
},
|
| 218 |
-
{
|
| 219 |
-
"label": "urlparse",
|
| 220 |
-
"importPath": "urllib.parse",
|
| 221 |
-
"description": "urllib.parse",
|
| 222 |
-
"isExtraImport": true,
|
| 223 |
-
"detail": "urllib.parse",
|
| 224 |
-
"documentation": {}
|
| 225 |
-
},
|
| 226 |
-
{
|
| 227 |
-
"label": "unquote",
|
| 228 |
-
"importPath": "urllib.parse",
|
| 229 |
-
"description": "urllib.parse",
|
| 230 |
-
"isExtraImport": true,
|
| 231 |
-
"detail": "urllib.parse",
|
| 232 |
-
"documentation": {}
|
| 233 |
-
},
|
| 234 |
-
{
|
| 235 |
-
"label": "List",
|
| 236 |
-
"importPath": "typing",
|
| 237 |
-
"description": "typing",
|
| 238 |
-
"isExtraImport": true,
|
| 239 |
-
"detail": "typing",
|
| 240 |
-
"documentation": {}
|
| 241 |
-
},
|
| 242 |
-
{
|
| 243 |
-
"label": "Dict",
|
| 244 |
-
"importPath": "typing",
|
| 245 |
-
"description": "typing",
|
| 246 |
-
"isExtraImport": true,
|
| 247 |
-
"detail": "typing",
|
| 248 |
-
"documentation": {}
|
| 249 |
-
},
|
| 250 |
-
{
|
| 251 |
-
"label": "Set",
|
| 252 |
-
"importPath": "typing",
|
| 253 |
-
"description": "typing",
|
| 254 |
-
"isExtraImport": true,
|
| 255 |
-
"detail": "typing",
|
| 256 |
-
"documentation": {}
|
| 257 |
-
},
|
| 258 |
-
{
|
| 259 |
-
"label": "Dict",
|
| 260 |
-
"importPath": "typing",
|
| 261 |
-
"description": "typing",
|
| 262 |
-
"isExtraImport": true,
|
| 263 |
-
"detail": "typing",
|
| 264 |
-
"documentation": {}
|
| 265 |
-
},
|
| 266 |
-
{
|
| 267 |
-
"label": "List",
|
| 268 |
-
"importPath": "typing",
|
| 269 |
-
"description": "typing",
|
| 270 |
-
"isExtraImport": true,
|
| 271 |
-
"detail": "typing",
|
| 272 |
-
"documentation": {}
|
| 273 |
-
},
|
| 274 |
-
{
|
| 275 |
-
"label": "Optional",
|
| 276 |
-
"importPath": "typing",
|
| 277 |
-
"description": "typing",
|
| 278 |
-
"isExtraImport": true,
|
| 279 |
-
"detail": "typing",
|
| 280 |
-
"documentation": {}
|
| 281 |
-
},
|
| 282 |
-
{
|
| 283 |
-
"label": "Tuple",
|
| 284 |
-
"importPath": "typing",
|
| 285 |
-
"description": "typing",
|
| 286 |
-
"isExtraImport": true,
|
| 287 |
-
"detail": "typing",
|
| 288 |
-
"documentation": {}
|
| 289 |
-
},
|
| 290 |
-
{
|
| 291 |
-
"label": "hashlib",
|
| 292 |
-
"kind": 6,
|
| 293 |
-
"isExtraImport": true,
|
| 294 |
-
"importPath": "hashlib",
|
| 295 |
-
"description": "hashlib",
|
| 296 |
-
"detail": "hashlib",
|
| 297 |
-
"documentation": {}
|
| 298 |
-
},
|
| 299 |
-
{
|
| 300 |
-
"label": "sqlite3",
|
| 301 |
-
"kind": 6,
|
| 302 |
-
"isExtraImport": true,
|
| 303 |
-
"importPath": "sqlite3",
|
| 304 |
-
"description": "sqlite3",
|
| 305 |
-
"detail": "sqlite3",
|
| 306 |
-
"documentation": {}
|
| 307 |
-
},
|
| 308 |
-
{
|
| 309 |
-
"label": "requests",
|
| 310 |
-
"kind": 6,
|
| 311 |
-
"isExtraImport": true,
|
| 312 |
-
"importPath": "requests",
|
| 313 |
-
"description": "requests",
|
| 314 |
-
"detail": "requests",
|
| 315 |
-
"documentation": {}
|
| 316 |
-
},
|
| 317 |
-
{
|
| 318 |
-
"label": "multiprocessing",
|
| 319 |
-
"kind": 6,
|
| 320 |
-
"isExtraImport": true,
|
| 321 |
-
"importPath": "multiprocessing",
|
| 322 |
-
"description": "multiprocessing",
|
| 323 |
-
"detail": "multiprocessing",
|
| 324 |
-
"documentation": {}
|
| 325 |
-
},
|
| 326 |
-
{
|
| 327 |
-
"label": "OpenSearch",
|
| 328 |
-
"importPath": "opensearchpy",
|
| 329 |
-
"description": "opensearchpy",
|
| 330 |
-
"isExtraImport": true,
|
| 331 |
-
"detail": "opensearchpy",
|
| 332 |
-
"documentation": {}
|
| 333 |
-
},
|
| 334 |
-
{
|
| 335 |
-
"label": "RequestsHttpConnection",
|
| 336 |
-
"importPath": "opensearchpy",
|
| 337 |
-
"description": "opensearchpy",
|
| 338 |
-
"isExtraImport": true,
|
| 339 |
-
"detail": "opensearchpy",
|
| 340 |
-
"documentation": {}
|
| 341 |
-
},
|
| 342 |
-
{
|
| 343 |
-
"label": "OpenSearchException",
|
| 344 |
-
"importPath": "opensearchpy.exceptions",
|
| 345 |
-
"description": "opensearchpy.exceptions",
|
| 346 |
-
"isExtraImport": true,
|
| 347 |
-
"detail": "opensearchpy.exceptions",
|
| 348 |
-
"documentation": {}
|
| 349 |
-
},
|
| 350 |
-
{
|
| 351 |
-
"label": "setup_logging",
|
| 352 |
-
"kind": 2,
|
| 353 |
-
"importPath": "app",
|
| 354 |
-
"description": "app",
|
| 355 |
-
"peekOfCode": "def setup_logging():\n \"\"\"配置日志系统\"\"\"\n if not app.debug:\n # 创建日志目录\n log_dir = Path('logs')\n log_dir.mkdir(exist_ok=True)\n # 文件日志处理器(每个文件最大10MB,保留10个备份)\n file_handler = RotatingFileHandler(\n log_dir / 'app.log',\n maxBytes=10 * 1024 * 1024,",
|
| 356 |
-
"detail": "app",
|
| 357 |
-
"documentation": {}
|
| 358 |
-
},
|
| 359 |
-
{
|
| 360 |
-
"label": "get_client_ip",
|
| 361 |
-
"kind": 2,
|
| 362 |
-
"importPath": "app",
|
| 363 |
-
"description": "app",
|
| 364 |
-
"peekOfCode": "def get_client_ip():\n \"\"\"\n 获取客户端真实 IP 地址\n 在代理服务器(如 Hugging Face Spaces)后面时,需要检查代理头部\n \"\"\"\n # 按优先级检查各种代理头部\n headers_to_check = [\n 'X-Forwarded-For',\n 'X-Real-IP',\n 'CF-Connecting-IP', # Cloudflare",
|
| 365 |
-
"detail": "app",
|
| 366 |
-
"documentation": {}
|
| 367 |
-
},
|
| 368 |
-
{
|
| 369 |
-
"label": "load_book_data",
|
| 370 |
-
"kind": 2,
|
| 371 |
-
"importPath": "app",
|
| 372 |
-
"description": "app",
|
| 373 |
-
"peekOfCode": "def load_book_data():\n \"\"\"\n 加载书籍数据(已废弃,保留是为了兼容性)\n 现在使用数据库接口,不再加载JSON文件\n \"\"\"\n global BOOK_DATA\n app.logger.info('ℹ️ load_book_data 已废弃,使用数据库接口')\n # 设置为空字典表示已初始化,但不再使用\n BOOK_DATA = {}\n return True",
|
| 374 |
-
"detail": "app",
|
| 375 |
-
"documentation": {}
|
| 376 |
-
},
|
| 377 |
-
{
|
| 378 |
-
"label": "init_database",
|
| 379 |
-
"kind": 2,
|
| 380 |
-
"importPath": "app",
|
| 381 |
-
"description": "app",
|
| 382 |
-
"peekOfCode": "def init_database():\n \"\"\"初始化数据库连接\"\"\"\n global DB\n try:\n DB = get_db_instance('books.db')\n app.logger.info('✅ 数据库连接初始化成功')\n return True\n except Exception as e:\n app.logger.error(f'❌ 数据库初始化失败: {e}')\n return False",
|
| 383 |
-
"detail": "app",
|
| 384 |
-
"documentation": {}
|
| 385 |
-
},
|
| 386 |
-
{
|
| 387 |
-
"label": "index",
|
| 388 |
-
"kind": 2,
|
| 389 |
-
"importPath": "app",
|
| 390 |
-
"description": "app",
|
| 391 |
-
"peekOfCode": "def index():\n \"\"\"主页 - 书籍目录\"\"\"\n return send_from_directory('.', 'index.html')\n@app.route('/reader')\ndef reader():\n \"\"\"阅读页面\"\"\"\n return send_from_directory('.', 'reader.html')\n@app.route('/<path:filename>')\ndef serve_static(filename):\n \"\"\"提供根目录下的静态文件(如 style.css, script.js)\"\"\"",
|
| 392 |
-
"detail": "app",
|
| 393 |
-
"documentation": {}
|
| 394 |
-
},
|
| 395 |
-
{
|
| 396 |
-
"label": "reader",
|
| 397 |
-
"kind": 2,
|
| 398 |
-
"importPath": "app",
|
| 399 |
-
"description": "app",
|
| 400 |
-
"peekOfCode": "def reader():\n \"\"\"阅读页面\"\"\"\n return send_from_directory('.', 'reader.html')\n@app.route('/<path:filename>')\ndef serve_static(filename):\n \"\"\"提供根目录下的静态文件(如 style.css, script.js)\"\"\"\n # 避免与API路由冲突\n if filename.startswith('api/'):\n return jsonify({'error': '接口不存在'}), 404\n return send_from_directory('.', filename)",
|
| 401 |
-
"detail": "app",
|
| 402 |
-
"documentation": {}
|
| 403 |
-
},
|
| 404 |
-
{
|
| 405 |
-
"label": "serve_static",
|
| 406 |
-
"kind": 2,
|
| 407 |
-
"importPath": "app",
|
| 408 |
-
"description": "app",
|
| 409 |
-
"peekOfCode": "def serve_static(filename):\n \"\"\"提供根目录下的静态文件(如 style.css, script.js)\"\"\"\n # 避免与API路由冲突\n if filename.startswith('api/'):\n return jsonify({'error': '接口不存在'}), 404\n return send_from_directory('.', filename)\n# ============================================================================\n# 路由:API 端点\n# ============================================================================\n@app.route('/api/health')",
|
| 410 |
-
"detail": "app",
|
| 411 |
-
"documentation": {}
|
| 412 |
-
},
|
| 413 |
-
{
|
| 414 |
-
"label": "health_check",
|
| 415 |
-
"kind": 2,
|
| 416 |
-
"importPath": "app",
|
| 417 |
-
"description": "app",
|
| 418 |
-
"peekOfCode": "def health_check():\n \"\"\"健康检查端点\"\"\"\n return jsonify({\n 'status': 'healthy',\n 'timestamp': datetime.now().isoformat(),\n 'version': '2.0.0-flask'\n })\n@app.route('/api/book/info')\ndef get_book_info():\n \"\"\"",
|
| 419 |
-
"detail": "app",
|
| 420 |
-
"documentation": {}
|
| 421 |
-
},
|
| 422 |
-
{
|
| 423 |
-
"label": "get_book_info",
|
| 424 |
-
"kind": 2,
|
| 425 |
-
"importPath": "app",
|
| 426 |
-
"description": "app",
|
| 427 |
-
"peekOfCode": "def get_book_info():\n \"\"\"\n 获取书籍信息(旧接口,保留兼容性)\n 推荐使用: /api/v2/books/<book_id>\n \"\"\"\n try:\n if not DB:\n return jsonify({'error': '数据库未初始化'}), 500\n # 默认获取第一本书的信息(为了兼容旧版本)\n books = DB.get_all_books()",
|
| 428 |
-
"detail": "app",
|
| 429 |
-
"documentation": {}
|
| 430 |
-
},
|
| 431 |
-
{
|
| 432 |
-
"label": "get_page_content",
|
| 433 |
-
"kind": 2,
|
| 434 |
-
"importPath": "app",
|
| 435 |
-
"description": "app",
|
| 436 |
-
"peekOfCode": "def get_page_content(page_num):\n \"\"\"\n 获取指定页面内容(旧接口,保留兼容性)\n 推荐使用: /api/v2/books/<book_id>/pages/<page_num>\n \"\"\"\n try:\n if not DB:\n return jsonify({'error': '数据库未初始化'}), 500\n # 获取最后导入的书籍\n books = DB.get_all_books()",
|
| 437 |
-
"detail": "app",
|
| 438 |
-
"documentation": {}
|
| 439 |
-
},
|
| 440 |
-
{
|
| 441 |
-
"label": "save_progress",
|
| 442 |
-
"kind": 2,
|
| 443 |
-
"importPath": "app",
|
| 444 |
-
"description": "app",
|
| 445 |
-
"peekOfCode": "def save_progress():\n \"\"\"保存学习进度\"\"\"\n try:\n data = request.get_json()\n # 这里可以保存到数据库,现在先存到 session\n if 'progress' not in session:\n session['progress'] = {}\n session['progress'].update({\n 'current_page': data.get('current_page', 0),\n 'bookmarks': data.get('bookmarks', []),",
|
| 446 |
-
"detail": "app",
|
| 447 |
-
"documentation": {}
|
| 448 |
-
},
|
| 449 |
-
{
|
| 450 |
-
"label": "load_progress",
|
| 451 |
-
"kind": 2,
|
| 452 |
-
"importPath": "app",
|
| 453 |
-
"description": "app",
|
| 454 |
-
"peekOfCode": "def load_progress():\n \"\"\"加载学习进度\"\"\"\n progress = session.get('progress', {\n 'current_page': 0,\n 'bookmarks': [],\n 'settings': {}\n })\n return jsonify(progress)\n@app.route('/api/search', methods=['POST'])\ndef search_content():",
|
| 455 |
-
"detail": "app",
|
| 456 |
-
"documentation": {}
|
| 457 |
-
},
|
| 458 |
-
{
|
| 459 |
-
"label": "search_content",
|
| 460 |
-
"kind": 2,
|
| 461 |
-
"importPath": "app",
|
| 462 |
-
"description": "app",
|
| 463 |
-
"peekOfCode": "def search_content():\n \"\"\"搜索内容(未来可集成 OpenSearch)\"\"\"\n try:\n data = request.get_json()\n keyword = data.get('keyword', '').strip()\n if not keyword:\n return jsonify({'error': '搜索关键词不能为空'}), 400\n # 简单搜索实现(在书籍数据中搜索)\n if not BOOK_DATA:\n return jsonify({'error': '书籍数据未加载'}), 500",
|
| 464 |
-
"detail": "app",
|
| 465 |
-
"documentation": {}
|
| 466 |
-
},
|
| 467 |
-
{
|
| 468 |
-
"label": "opensearch_status",
|
| 469 |
-
"kind": 2,
|
| 470 |
-
"importPath": "app",
|
| 471 |
-
"description": "app",
|
| 472 |
-
"peekOfCode": "def opensearch_status():\n \"\"\"检查 OpenSearch 连接状态(示例)\"\"\"\n try:\n # 这里可以实际连接 OpenSearch\n # from opensearchpy import OpenSearch\n # client = OpenSearch([{'host': OPENSEARCH_CONFIG['host'], 'port': OPENSEARCH_CONFIG['port']}])\n # info = client.info()\n return jsonify({\n 'configured': True,\n 'host': OPENSEARCH_CONFIG['host'],",
|
| 473 |
-
"detail": "app",
|
| 474 |
-
"documentation": {}
|
| 475 |
-
},
|
| 476 |
-
{
|
| 477 |
-
"label": "get_stats",
|
| 478 |
-
"kind": 2,
|
| 479 |
-
"importPath": "app",
|
| 480 |
-
"description": "app",
|
| 481 |
-
"peekOfCode": "def get_stats():\n \"\"\"获取学习统计数据\"\"\"\n # 从 session 或数据库获取统计数据\n progress = session.get('progress', {})\n return jsonify({\n 'current_page': progress.get('current_page', 0),\n 'total_bookmarks': len(progress.get('bookmarks', [])),\n 'last_visit': progress.get('last_updated', None),\n 'total_pages': len(BOOK_DATA.get('pages', [])) if BOOK_DATA else 0\n })",
|
| 482 |
-
"detail": "app",
|
| 483 |
-
"documentation": {}
|
| 484 |
-
},
|
| 485 |
-
{
|
| 486 |
-
"label": "get_books",
|
| 487 |
-
"kind": 2,
|
| 488 |
-
"importPath": "app",
|
| 489 |
-
"description": "app",
|
| 490 |
-
"peekOfCode": "def get_books():\n \"\"\"\n 获取所有书籍列表\n Query Parameters:\n grade_id: 年级ID(可选)\n Returns:\n {\n \"success\": true,\n \"count\": 10,\n \"books\": [",
|
| 491 |
-
"detail": "app",
|
| 492 |
-
"documentation": {}
|
| 493 |
-
},
|
| 494 |
-
{
|
| 495 |
-
"label": "get_book_info_v2",
|
| 496 |
-
"kind": 2,
|
| 497 |
-
"importPath": "app",
|
| 498 |
-
"description": "app",
|
| 499 |
-
"peekOfCode": "def get_book_info_v2(book_id):\n \"\"\"\n 获取指定书籍的详细信息\n Args:\n book_id: 书籍ID\n Returns:\n {\n \"success\": true,\n \"book\": {\n \"book_id\": 1,",
|
| 500 |
-
"detail": "app",
|
| 501 |
-
"documentation": {}
|
| 502 |
-
},
|
| 503 |
-
{
|
| 504 |
-
"label": "get_book_pages",
|
| 505 |
-
"kind": 2,
|
| 506 |
-
"importPath": "app",
|
| 507 |
-
"description": "app",
|
| 508 |
-
"peekOfCode": "def get_book_pages(book_id):\n \"\"\"\n 获取书籍的所有页面列表(不含片段内容)\n Args:\n book_id: 书籍ID\n Returns:\n {\n \"success\": true,\n \"book_id\": 168,\n \"book_name\": \"一年级上册\",",
|
| 509 |
-
"detail": "app",
|
| 510 |
-
"documentation": {}
|
| 511 |
-
},
|
| 512 |
-
{
|
| 513 |
-
"label": "get_book_catalog",
|
| 514 |
-
"kind": 2,
|
| 515 |
-
"importPath": "app",
|
| 516 |
-
"description": "app",
|
| 517 |
-
"peekOfCode": "def get_book_catalog(book_id):\n \"\"\"\n 获取书籍目录结构(章节目录)\n Args:\n book_id: 书籍ID\n Returns:\n {\n \"success\": true,\n \"book_id\": 168,\n \"book_name\": \"一年级上册\",",
|
| 518 |
-
"detail": "app",
|
| 519 |
-
"documentation": {}
|
| 520 |
-
},
|
| 521 |
-
{
|
| 522 |
-
"label": "get_page_content_v2",
|
| 523 |
-
"kind": 2,
|
| 524 |
-
"importPath": "app",
|
| 525 |
-
"description": "app",
|
| 526 |
-
"peekOfCode": "def get_page_content_v2(book_id, page_num):\n \"\"\"\n 获取指定页面的完整内容\n Args:\n book_id: 书籍ID\n page_num: 页码\n Returns:\n {\n \"success\": true,\n \"book_id\": 1,",
|
| 527 |
-
"detail": "app",
|
| 528 |
-
"documentation": {}
|
| 529 |
-
},
|
| 530 |
-
{
|
| 531 |
-
"label": "search_book_content",
|
| 532 |
-
"kind": 2,
|
| 533 |
-
"importPath": "app",
|
| 534 |
-
"description": "app",
|
| 535 |
-
"peekOfCode": "def search_book_content(book_id):\n \"\"\"\n 在书籍中搜索内容\n Query Parameters:\n keyword: 搜索关键词\n limit: 返回结果数量(默认20)\n Returns:\n {\n \"success\": true,\n \"book_id\": 168,",
|
| 536 |
-
"detail": "app",
|
| 537 |
-
"documentation": {}
|
| 538 |
-
},
|
| 539 |
-
{
|
| 540 |
-
"label": "search_all_content",
|
| 541 |
-
"kind": 2,
|
| 542 |
-
"importPath": "app",
|
| 543 |
-
"description": "app",
|
| 544 |
-
"peekOfCode": "def search_all_content():\n \"\"\"\n 在所有书籍中搜索内容\n Query Parameters:\n keyword: 搜索关键词\n limit: 返回结果数量(默认50)\n Returns:\n {\n \"success\": true,\n \"keyword\": \"hello\",",
|
| 545 |
-
"detail": "app",
|
| 546 |
-
"documentation": {}
|
| 547 |
-
},
|
| 548 |
-
{
|
| 549 |
-
"label": "get_book_statistics",
|
| 550 |
-
"kind": 2,
|
| 551 |
-
"importPath": "app",
|
| 552 |
-
"description": "app",
|
| 553 |
-
"peekOfCode": "def get_book_statistics(book_id):\n \"\"\"\n 获取书籍统计信息\n Args:\n book_id: 书籍ID\n Returns:\n {\n \"success\": true,\n \"statistics\": {\n \"book_id\": 168,",
|
| 554 |
-
"detail": "app",
|
| 555 |
-
"documentation": {}
|
| 556 |
-
},
|
| 557 |
-
{
|
| 558 |
-
"label": "get_overall_statistics",
|
| 559 |
-
"kind": 2,
|
| 560 |
-
"importPath": "app",
|
| 561 |
-
"description": "app",
|
| 562 |
-
"peekOfCode": "def get_overall_statistics():\n \"\"\"\n 获取整体统计信息\n Returns:\n {\n \"success\": true,\n \"statistics\": {\n \"total_books\": 30,\n \"total_pages\": 2000,\n \"total_pieces\": 50000,",
|
| 563 |
-
"detail": "app",
|
| 564 |
-
"documentation": {}
|
| 565 |
-
},
|
| 566 |
-
{
|
| 567 |
-
"label": "not_found",
|
| 568 |
-
"kind": 2,
|
| 569 |
-
"importPath": "app",
|
| 570 |
-
"description": "app",
|
| 571 |
-
"peekOfCode": "def not_found(error):\n \"\"\"404错误处理\"\"\"\n if request.path.startswith('/api/'):\n return jsonify({'error': '接口不存在'}), 404\n return send_from_directory('.', 'index.html')\n@app.errorhandler(500)\ndef internal_error(error):\n \"\"\"500错误处理\"\"\"\n app.logger.error(f'服务器错误: {error}')\n return jsonify({'error': '服务器内部错误'}), 500",
|
| 572 |
-
"detail": "app",
|
| 573 |
-
"documentation": {}
|
| 574 |
-
},
|
| 575 |
-
{
|
| 576 |
-
"label": "internal_error",
|
| 577 |
-
"kind": 2,
|
| 578 |
-
"importPath": "app",
|
| 579 |
-
"description": "app",
|
| 580 |
-
"peekOfCode": "def internal_error(error):\n \"\"\"500错误处理\"\"\"\n app.logger.error(f'服务器错误: {error}')\n return jsonify({'error': '服务器内部错误'}), 500\n# ============================================================================\n# 请求钩子\n# ============================================================================\n@app.before_request\ndef before_request():\n \"\"\"请求前处理\"\"\"",
|
| 581 |
-
"detail": "app",
|
| 582 |
-
"documentation": {}
|
| 583 |
-
},
|
| 584 |
-
{
|
| 585 |
-
"label": "before_request",
|
| 586 |
-
"kind": 2,
|
| 587 |
-
"importPath": "app",
|
| 588 |
-
"description": "app",
|
| 589 |
-
"peekOfCode": "def before_request():\n \"\"\"请求前处理\"\"\"\n # 获取客户端真实 IP\n client_ip = get_client_ip()\n # 记录所有请求信息(包括静态文件)\n user_agent = request.headers.get('User-Agent', 'Unknown')[:100] # 限制长度\n # API 请求记录详细信息\n if request.path.startswith('/api/'):\n app.logger.info(\n f\"[{client_ip}] {request.method} {request.path} \"",
|
| 590 |
-
"detail": "app",
|
| 591 |
-
"documentation": {}
|
| 592 |
-
},
|
| 593 |
-
{
|
| 594 |
-
"label": "after_request",
|
| 595 |
-
"kind": 2,
|
| 596 |
-
"importPath": "app",
|
| 597 |
-
"description": "app",
|
| 598 |
-
"peekOfCode": "def after_request(response):\n \"\"\"请求后处理 - 添加缓存控制\"\"\"\n # API 端点不缓存\n if request.path.startswith('/api/'):\n response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'\n response.headers['Pragma'] = 'no-cache'\n response.headers['Expires'] = '0'\n else:\n # 静态资源缓存1小时\n response.headers['Cache-Control'] = 'public, max-age=3600'",
|
| 599 |
-
"detail": "app",
|
| 600 |
-
"documentation": {}
|
| 601 |
-
},
|
| 602 |
-
{
|
| 603 |
-
"label": "auto_generate_database",
|
| 604 |
-
"kind": 2,
|
| 605 |
-
"importPath": "app",
|
| 606 |
-
"description": "app",
|
| 607 |
-
"peekOfCode": "def auto_generate_database():\n \"\"\"\n 自动生成数据库\n 如果 books.db 不存在,自动运行导入脚本生成数据库\n \"\"\"\n db_path = 'books.db'\n schema_path = 'db_schema.sql'\n data_dir = 'books_api_data'\n # 检查必要的文件和目录\n if not os.path.exists(schema_path):",
|
| 608 |
-
"detail": "app",
|
| 609 |
-
"documentation": {}
|
| 610 |
-
},
|
| 611 |
-
{
|
| 612 |
-
"label": "initialize_app",
|
| 613 |
-
"kind": 2,
|
| 614 |
-
"importPath": "app",
|
| 615 |
-
"description": "app",
|
| 616 |
-
"peekOfCode": "def initialize_app():\n \"\"\"初始化应用\"\"\"\n print(\"🚀 交互式英语学习应用 - Flask 版本\")\n print(\"=\" * 60)\n # 设置日志\n setup_logging()\n # 检查必要文件(暂时不检查 books.db,因为会自动生成)\n if not os.path.exists('index.html'):\n app.logger.error(f\"❌ 缺少必要文件: index.html\")\n return False",
|
| 617 |
-
"detail": "app",
|
| 618 |
-
"documentation": {}
|
| 619 |
-
},
|
| 620 |
-
{
|
| 621 |
-
"label": "main",
|
| 622 |
-
"kind": 2,
|
| 623 |
-
"importPath": "app",
|
| 624 |
-
"description": "app",
|
| 625 |
-
"peekOfCode": "def main():\n \"\"\"主函数\"\"\"\n # 初始化应用\n if not initialize_app():\n print(\"❌ 应用初始化失败\")\n return 1\n # Hugging Face Spaces 要求监听 7860 端口\n port = int(os.environ.get('PORT', 7860))\n debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'\n print(f\"🌐 监听端口: {port}\")",
|
| 626 |
-
"detail": "app",
|
| 627 |
-
"documentation": {}
|
| 628 |
-
},
|
| 629 |
-
{
|
| 630 |
-
"label": "app",
|
| 631 |
-
"kind": 5,
|
| 632 |
-
"importPath": "app",
|
| 633 |
-
"description": "app",
|
| 634 |
-
"peekOfCode": "app = Flask(__name__, \n static_folder='static',\n static_url_path='/static')\n# 配置\napp.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')\napp.config['JSON_AS_ASCII'] = False # 支持中文JSON\napp.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 最大上传16MB\n# 启用CORS\nCORS(app, resources={\n r\"/*\": {",
|
| 635 |
-
"detail": "app",
|
| 636 |
-
"documentation": {}
|
| 637 |
-
},
|
| 638 |
-
{
|
| 639 |
-
"label": "app.config['SECRET_KEY']",
|
| 640 |
-
"kind": 5,
|
| 641 |
-
"importPath": "app",
|
| 642 |
-
"description": "app",
|
| 643 |
-
"peekOfCode": "app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')\napp.config['JSON_AS_ASCII'] = False # 支持中文JSON\napp.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 最大上传16MB\n# 启用CORS\nCORS(app, resources={\n r\"/*\": {\n \"origins\": \"*\",\n \"methods\": [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n \"allow_headers\": [\"Content-Type\", \"Authorization\"]\n }",
|
| 644 |
-
"detail": "app",
|
| 645 |
-
"documentation": {}
|
| 646 |
-
},
|
| 647 |
-
{
|
| 648 |
-
"label": "app.config['JSON_AS_ASCII']",
|
| 649 |
-
"kind": 5,
|
| 650 |
-
"importPath": "app",
|
| 651 |
-
"description": "app",
|
| 652 |
-
"peekOfCode": "app.config['JSON_AS_ASCII'] = False # 支持中文JSON\napp.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 最大上传16MB\n# 启用CORS\nCORS(app, resources={\n r\"/*\": {\n \"origins\": \"*\",\n \"methods\": [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n \"allow_headers\": [\"Content-Type\", \"Authorization\"]\n }\n})",
|
| 653 |
-
"detail": "app",
|
| 654 |
-
"documentation": {}
|
| 655 |
-
},
|
| 656 |
-
{
|
| 657 |
-
"label": "app.config['MAX_CONTENT_LENGTH']",
|
| 658 |
-
"kind": 5,
|
| 659 |
-
"importPath": "app",
|
| 660 |
-
"description": "app",
|
| 661 |
-
"peekOfCode": "app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 最大上传16MB\n# 启用CORS\nCORS(app, resources={\n r\"/*\": {\n \"origins\": \"*\",\n \"methods\": [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n \"allow_headers\": [\"Content-Type\", \"Authorization\"]\n }\n})\n# 配置日志",
|
| 662 |
-
"detail": "app",
|
| 663 |
-
"documentation": {}
|
| 664 |
-
},
|
| 665 |
-
{
|
| 666 |
-
"label": "OPENSEARCH_CONFIG",
|
| 667 |
-
"kind": 5,
|
| 668 |
-
"importPath": "app",
|
| 669 |
-
"description": "app",
|
| 670 |
-
"peekOfCode": "OPENSEARCH_CONFIG = {\n 'host': os.environ.get('OPENSEARCH_HOST', '192.168.3.33'),\n 'port': int(os.environ.get('OPENSEARCH_PORT', 9200)),\n 'use_ssl': os.environ.get('OPENSEARCH_USE_SSL', 'False').lower() == 'true',\n}\n# 全局变量:存储学习数据\nBOOK_DATA = None\n# 数据库实例\nDB = None\ndef get_client_ip():",
|
| 671 |
-
"detail": "app",
|
| 672 |
-
"documentation": {}
|
| 673 |
-
},
|
| 674 |
-
{
|
| 675 |
-
"label": "BOOK_DATA",
|
| 676 |
-
"kind": 5,
|
| 677 |
-
"importPath": "app",
|
| 678 |
-
"description": "app",
|
| 679 |
-
"peekOfCode": "BOOK_DATA = None\n# 数据库实例\nDB = None\ndef get_client_ip():\n \"\"\"\n 获取客户端真实 IP 地址\n 在代理服务器(如 Hugging Face Spaces)后面时,需要检查代理头部\n \"\"\"\n # 按优先级检查各种代理头部\n headers_to_check = [",
|
| 680 |
-
"detail": "app",
|
| 681 |
-
"documentation": {}
|
| 682 |
-
},
|
| 683 |
-
{
|
| 684 |
-
"label": "DB",
|
| 685 |
-
"kind": 5,
|
| 686 |
-
"importPath": "app",
|
| 687 |
-
"description": "app",
|
| 688 |
-
"peekOfCode": "DB = None\ndef get_client_ip():\n \"\"\"\n 获取客户端真实 IP 地址\n 在代理服务器(如 Hugging Face Spaces)后面时,需要检查代理头部\n \"\"\"\n # 按优先级检查各种代理头部\n headers_to_check = [\n 'X-Forwarded-For',\n 'X-Real-IP',",
|
| 689 |
-
"detail": "app",
|
| 690 |
-
"documentation": {}
|
| 691 |
-
},
|
| 692 |
-
{
|
| 693 |
-
"label": "ResourceManager",
|
| 694 |
-
"kind": 6,
|
| 695 |
-
"importPath": "check_and_download",
|
| 696 |
-
"description": "check_and_download",
|
| 697 |
-
"peekOfCode": "class ResourceManager:\n def __init__(self, json_file: str, assets_dir: str):\n self.json_file = json_file\n self.assets_dir = Path(assets_dir)\n self.audio_dir = self.assets_dir / \"audios\"\n self.image_dir = self.assets_dir / \"images\"\n # 确保目录存在\n self.audio_dir.mkdir(parents=True, exist_ok=True)\n self.image_dir.mkdir(parents=True, exist_ok=True)\n def load_json_data(self) -> Dict:",
|
| 698 |
-
"detail": "check_and_download",
|
| 699 |
-
"documentation": {}
|
| 700 |
-
},
|
| 701 |
-
{
|
| 702 |
-
"label": "main",
|
| 703 |
-
"kind": 2,
|
| 704 |
-
"importPath": "check_and_download",
|
| 705 |
-
"description": "check_and_download",
|
| 706 |
-
"peekOfCode": "def main():\n rm = ResourceManager(\"book_10242.json\", \"assets\")\n print(\"=== 资源完整性检查和下载脚本 ===\")\n # 检查并找出缺失的文件\n missing_urls = rm.check_and_find_missing()\n if not missing_urls[\"audio\"] and not missing_urls[\"image\"]:\n print(\"\\n所有文件都已正确下载,无需额外操作!\")\n return\n # 生成下载脚本\n script_content = rm.generate_download_script(missing_urls)",
|
| 707 |
-
"detail": "check_and_download",
|
| 708 |
-
"documentation": {}
|
| 709 |
-
},
|
| 710 |
-
{
|
| 711 |
-
"label": "Config",
|
| 712 |
-
"kind": 6,
|
| 713 |
-
"importPath": "config",
|
| 714 |
-
"description": "config",
|
| 715 |
-
"peekOfCode": "class Config:\n \"\"\"基础配置\"\"\"\n # Flask 基础配置\n SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-please-change-in-production'\n JSON_AS_ASCII = False # 支持中文JSON\n MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 最大上传16MB\n # 会话配置\n SESSION_TYPE = 'filesystem'\n SESSION_PERMANENT = False\n SESSION_USE_SIGNER = True",
|
| 716 |
-
"detail": "config",
|
| 717 |
-
"documentation": {}
|
| 718 |
-
},
|
| 719 |
-
{
|
| 720 |
-
"label": "DevelopmentConfig",
|
| 721 |
-
"kind": 6,
|
| 722 |
-
"importPath": "config",
|
| 723 |
-
"description": "config",
|
| 724 |
-
"peekOfCode": "class DevelopmentConfig(Config):\n \"\"\"开发环境配置\"\"\"\n DEBUG = True\n TESTING = False\nclass ProductionConfig(Config):\n \"\"\"生产环境配置\"\"\"\n DEBUG = False\n TESTING = False\n # 生产环境应该从环境变量读取敏感信息\n # SECRET_KEY 验证在应用启动时进行,而不是在导入时",
|
| 725 |
-
"detail": "config",
|
| 726 |
-
"documentation": {}
|
| 727 |
-
},
|
| 728 |
-
{
|
| 729 |
-
"label": "ProductionConfig",
|
| 730 |
-
"kind": 6,
|
| 731 |
-
"importPath": "config",
|
| 732 |
-
"description": "config",
|
| 733 |
-
"peekOfCode": "class ProductionConfig(Config):\n \"\"\"生产环境配置\"\"\"\n DEBUG = False\n TESTING = False\n # 生产环境应该从环境变量读取敏感信息\n # SECRET_KEY 验证在应用启动时进行,而不是在导入时\n SECRET_KEY = os.environ.get('SECRET_KEY', Config.SECRET_KEY)\nclass TestingConfig(Config):\n \"\"\"测试环境配置\"\"\"\n TESTING = True",
|
| 734 |
-
"detail": "config",
|
| 735 |
-
"documentation": {}
|
| 736 |
-
},
|
| 737 |
-
{
|
| 738 |
-
"label": "TestingConfig",
|
| 739 |
-
"kind": 6,
|
| 740 |
-
"importPath": "config",
|
| 741 |
-
"description": "config",
|
| 742 |
-
"peekOfCode": "class TestingConfig(Config):\n \"\"\"测试环境配置\"\"\"\n TESTING = True\n DEBUG = True\n # 使用内存数据库进行测试\n # SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'\n# 配置字典\nconfig = {\n 'development': DevelopmentConfig,\n 'production': ProductionConfig,",
|
| 743 |
-
"detail": "config",
|
| 744 |
-
"documentation": {}
|
| 745 |
-
},
|
| 746 |
-
{
|
| 747 |
-
"label": "config",
|
| 748 |
-
"kind": 5,
|
| 749 |
-
"importPath": "config",
|
| 750 |
-
"description": "config",
|
| 751 |
-
"peekOfCode": "config = {\n 'development': DevelopmentConfig,\n 'production': ProductionConfig,\n 'testing': TestingConfig,\n 'default': DevelopmentConfig\n}",
|
| 752 |
-
"detail": "config",
|
| 753 |
-
"documentation": {}
|
| 754 |
-
},
|
| 755 |
-
{
|
| 756 |
-
"label": "DatabaseManager",
|
| 757 |
-
"kind": 6,
|
| 758 |
-
"importPath": "db_manager",
|
| 759 |
-
"description": "db_manager",
|
| 760 |
-
"peekOfCode": "class DatabaseManager:\n \"\"\"数据库管理器类\"\"\"\n def __init__(self, db_path: str = 'books.db'):\n \"\"\"\n 初始化数据库管理器\n Args:\n db_path: 数据库文件路径\n \"\"\"\n self.db_path = db_path\n self.conn = None",
|
| 761 |
-
"detail": "db_manager",
|
| 762 |
-
"documentation": {}
|
| 763 |
-
},
|
| 764 |
-
{
|
| 765 |
-
"label": "get_db_instance",
|
| 766 |
-
"kind": 2,
|
| 767 |
-
"importPath": "db_manager",
|
| 768 |
-
"description": "db_manager",
|
| 769 |
-
"peekOfCode": "def get_db_instance(db_path: str = 'books.db') -> DatabaseManager:\n \"\"\"\n 获取数据库管理器单例\n Args:\n db_path: 数据库文件路径\n Returns:\n DatabaseManager实例\n \"\"\"\n global _db_instance\n if _db_instance is None:",
|
| 770 |
-
"detail": "db_manager",
|
| 771 |
-
"documentation": {}
|
| 772 |
-
},
|
| 773 |
-
{
|
| 774 |
-
"label": "close_db",
|
| 775 |
-
"kind": 2,
|
| 776 |
-
"importPath": "db_manager",
|
| 777 |
-
"description": "db_manager",
|
| 778 |
-
"peekOfCode": "def close_db():\n \"\"\"关闭全局数据库连接\"\"\"\n global _db_instance\n if _db_instance:\n _db_instance.close()\n _db_instance = None",
|
| 779 |
-
"detail": "db_manager",
|
| 780 |
-
"documentation": {}
|
| 781 |
-
},
|
| 782 |
-
{
|
| 783 |
-
"label": "logger",
|
| 784 |
-
"kind": 5,
|
| 785 |
-
"importPath": "db_manager",
|
| 786 |
-
"description": "db_manager",
|
| 787 |
-
"peekOfCode": "logger = logging.getLogger(__name__)\nclass DatabaseManager:\n \"\"\"数据库管理器类\"\"\"\n def __init__(self, db_path: str = 'books.db'):\n \"\"\"\n 初始化数据库管理器\n Args:\n db_path: 数据库文件路径\n \"\"\"\n self.db_path = db_path",
|
| 788 |
-
"detail": "db_manager",
|
| 789 |
-
"documentation": {}
|
| 790 |
-
},
|
| 791 |
-
{
|
| 792 |
-
"label": "_db_instance",
|
| 793 |
-
"kind": 5,
|
| 794 |
-
"importPath": "db_manager",
|
| 795 |
-
"description": "db_manager",
|
| 796 |
-
"peekOfCode": "_db_instance = None\ndef get_db_instance(db_path: str = 'books.db') -> DatabaseManager:\n \"\"\"\n 获取数据库管理器单例\n Args:\n db_path: 数据库文件路径\n Returns:\n DatabaseManager实例\n \"\"\"\n global _db_instance",
|
| 797 |
-
"detail": "db_manager",
|
| 798 |
-
"documentation": {}
|
| 799 |
-
},
|
| 800 |
-
{
|
| 801 |
-
"label": "main",
|
| 802 |
-
"kind": 2,
|
| 803 |
-
"importPath": "debug_pages",
|
| 804 |
-
"description": "debug_pages",
|
| 805 |
-
"peekOfCode": "def main():\n \"\"\"检查第4页和第5页的数据\"\"\"\n try:\n with open('book_10242.json', 'r', encoding='utf-8') as f:\n data = json.load(f)\n # 解析Data字段中的JSON字符串\n pages_data = json.loads(data['Data'])\n print(f\"总页数: {len(pages_data)}\")\n print(\"=\" * 80)\n # 检查第4页和第5页(数组索引为3和4)",
|
| 806 |
-
"detail": "debug_pages",
|
| 807 |
-
"documentation": {}
|
| 808 |
-
},
|
| 809 |
-
{
|
| 810 |
-
"label": "extract_filename_from_url",
|
| 811 |
-
"kind": 2,
|
| 812 |
-
"importPath": "delete_encrypted_files",
|
| 813 |
-
"description": "delete_encrypted_files",
|
| 814 |
-
"peekOfCode": "def extract_filename_from_url(url):\n \"\"\"从URL中提取文件名\"\"\"\n if not url:\n return None\n # 解析URL,去掉查询参数\n parsed = urlparse(url)\n path = parsed.path\n # URL解码\n path = unquote(path)\n # 提取文件名",
|
| 815 |
-
"detail": "delete_encrypted_files",
|
| 816 |
-
"documentation": {}
|
| 817 |
-
},
|
| 818 |
-
{
|
| 819 |
-
"label": "main",
|
| 820 |
-
"kind": 2,
|
| 821 |
-
"importPath": "delete_encrypted_files",
|
| 822 |
-
"description": "delete_encrypted_files",
|
| 823 |
-
"peekOfCode": "def main():\n json_file = '/data/zhangl/code/hf/point/book_10242.json'\n audio_dir = '/data/zhangl/code/hf/point/assets/audios'\n image_dir = '/data/zhangl/code/hf/point/assets/images'\n print(\"正在解析 JSON 文件...\")\n try:\n with open(json_file, 'r', encoding='utf-8') as f:\n data = json.load(f)\n # 解析Data字段中的JSON字符串\n pages_data = json.loads(data['Data'])",
|
| 824 |
-
"detail": "delete_encrypted_files",
|
| 825 |
-
"documentation": {}
|
| 826 |
-
},
|
| 827 |
-
{
|
| 828 |
-
"label": "create_directories",
|
| 829 |
-
"kind": 2,
|
| 830 |
-
"importPath": "download_resources",
|
| 831 |
-
"description": "download_resources",
|
| 832 |
-
"peekOfCode": "def create_directories():\n \"\"\"创建assets目录结构\"\"\"\n assets_dir = Path(\"assets\")\n images_dir = assets_dir / \"images\"\n audios_dir = assets_dir / \"audios\"\n images_dir.mkdir(parents=True, exist_ok=True)\n audios_dir.mkdir(parents=True, exist_ok=True)\n print(f\"✅ 创建目录: {images_dir}\")\n print(f\"✅ 创建目录: {audios_dir}\")\n return images_dir, audios_dir",
|
| 833 |
-
"detail": "download_resources",
|
| 834 |
-
"documentation": {}
|
| 835 |
-
},
|
| 836 |
-
{
|
| 837 |
-
"label": "get_filename_from_url",
|
| 838 |
-
"kind": 2,
|
| 839 |
-
"importPath": "download_resources",
|
| 840 |
-
"description": "download_resources",
|
| 841 |
-
"peekOfCode": "def get_filename_from_url(url):\n \"\"\"从URL中提取文件名\"\"\"\n # 解析URL并获取路径部分\n parsed_url = urllib.parse.urlparse(url)\n path = parsed_url.path\n # 获取文件名\n filename = os.path.basename(path)\n # 如果没有文件名,使用URL的hash作为文件名\n if not filename or '.' not in filename:\n url_hash = hashlib.md5(url.encode()).hexdigest()[:8]",
|
| 842 |
-
"detail": "download_resources",
|
| 843 |
-
"documentation": {}
|
| 844 |
-
},
|
| 845 |
-
{
|
| 846 |
-
"label": "download_file",
|
| 847 |
-
"kind": 2,
|
| 848 |
-
"importPath": "download_resources",
|
| 849 |
-
"description": "download_resources",
|
| 850 |
-
"peekOfCode": "def download_file(url, save_path, max_retries=3):\n \"\"\"下载文件\"\"\"\n headers = {\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'\n }\n for attempt in range(max_retries):\n try:\n print(f\"📥 下载中: {os.path.basename(save_path)}\")\n response = requests.get(url, headers=headers, timeout=30)\n response.raise_for_status()",
|
| 851 |
-
"detail": "download_resources",
|
| 852 |
-
"documentation": {}
|
| 853 |
-
},
|
| 854 |
-
{
|
| 855 |
-
"label": "extract_urls_from_json",
|
| 856 |
-
"kind": 2,
|
| 857 |
-
"importPath": "download_resources",
|
| 858 |
-
"description": "download_resources",
|
| 859 |
-
"peekOfCode": "def extract_urls_from_json(json_file):\n \"\"\"从JSON文件中提取所有图片和音频URL\"\"\"\n with open(json_file, 'r', encoding='utf-8') as f:\n data = json.load(f)\n urls = {\n 'images': set(),\n 'audios': set()\n }\n # 解析Data字段中的JSON字符串\n if 'Data' in data:",
|
| 860 |
-
"detail": "download_resources",
|
| 861 |
-
"documentation": {}
|
| 862 |
-
},
|
| 863 |
-
{
|
| 864 |
-
"label": "main",
|
| 865 |
-
"kind": 2,
|
| 866 |
-
"importPath": "download_resources",
|
| 867 |
-
"description": "download_resources",
|
| 868 |
-
"peekOfCode": "def main():\n \"\"\"主函数\"\"\"\n print(\"🚀 开始下载资源...\")\n # 创建目录\n images_dir, audios_dir = create_directories()\n # 提取URL\n print(\"📋 解析JSON文件...\")\n urls = extract_urls_from_json('book_10242.json')\n print(f\"📊 找到 {len(urls['images'])} 个图片URL\")\n print(f\"📊 找到 {len(urls['audios'])} 个音频URL\")",
|
| 869 |
-
"detail": "download_resources",
|
| 870 |
-
"documentation": {}
|
| 871 |
-
},
|
| 872 |
-
{
|
| 873 |
-
"label": "extract_data_field",
|
| 874 |
-
"kind": 2,
|
| 875 |
-
"importPath": "extract_data",
|
| 876 |
-
"description": "extract_data",
|
| 877 |
-
"peekOfCode": "def extract_data_field(input_file, output_file):\n \"\"\"\n 从输入文件中提取Data字段并保存到输出文件\n Args:\n input_file: 输入JSON文件路径\n output_file: 输出JSON文件路径\n \"\"\"\n try:\n # 读取输入文件\n print(f\"正在读取文件: {input_file}\")",
|
| 878 |
-
"detail": "extract_data",
|
| 879 |
-
"documentation": {}
|
| 880 |
-
},
|
| 881 |
-
{
|
| 882 |
-
"label": "on_starting",
|
| 883 |
-
"kind": 2,
|
| 884 |
-
"importPath": "gunicorn_config",
|
| 885 |
-
"description": "gunicorn_config",
|
| 886 |
-
"peekOfCode": "def on_starting(server):\n \"\"\"服务器启动时执行\"\"\"\n print(\"=\" * 60)\n print(\"🚀 Gunicorn 服务器启动\")\n print(f\"📍 绑定地址: {bind}\")\n print(f\"👷 工作进程数: {workers}\")\n print(f\"🧵 每进程线程数: {threads}\")\n print(f\"⏱️ 超时时间: {timeout}s\")\n print(\"=\" * 60)\ndef worker_int(worker):",
|
| 887 |
-
"detail": "gunicorn_config",
|
| 888 |
-
"documentation": {}
|
| 889 |
-
},
|
| 890 |
-
{
|
| 891 |
-
"label": "worker_int",
|
| 892 |
-
"kind": 2,
|
| 893 |
-
"importPath": "gunicorn_config",
|
| 894 |
-
"description": "gunicorn_config",
|
| 895 |
-
"peekOfCode": "def worker_int(worker):\n \"\"\"工作进程被中断时执行\"\"\"\n print(f\"⚠️ 工作进程 {worker.pid} 被中断\")\ndef worker_abort(worker):\n \"\"\"工作进程异常退出时执行\"\"\"\n print(f\"❌ 工作进程 {worker.pid} 异常退出\")\ndef post_worker_init(worker):\n \"\"\"工作进程初始化后执行\"\"\"\n print(f\"✅ 工作进程 {worker.pid} 初始化完成\")",
|
| 896 |
-
"detail": "gunicorn_config",
|
| 897 |
-
"documentation": {}
|
| 898 |
-
},
|
| 899 |
-
{
|
| 900 |
-
"label": "worker_abort",
|
| 901 |
-
"kind": 2,
|
| 902 |
-
"importPath": "gunicorn_config",
|
| 903 |
-
"description": "gunicorn_config",
|
| 904 |
-
"peekOfCode": "def worker_abort(worker):\n \"\"\"工作进程异常退出时执行\"\"\"\n print(f\"❌ 工作进程 {worker.pid} 异常退出\")\ndef post_worker_init(worker):\n \"\"\"工作进程初始化后执行\"\"\"\n print(f\"✅ 工作进程 {worker.pid} 初始化完成\")",
|
| 905 |
-
"detail": "gunicorn_config",
|
| 906 |
-
"documentation": {}
|
| 907 |
-
},
|
| 908 |
-
{
|
| 909 |
-
"label": "post_worker_init",
|
| 910 |
-
"kind": 2,
|
| 911 |
-
"importPath": "gunicorn_config",
|
| 912 |
-
"description": "gunicorn_config",
|
| 913 |
-
"peekOfCode": "def post_worker_init(worker):\n \"\"\"工作进程初始化后执行\"\"\"\n print(f\"✅ 工作进程 {worker.pid} 初始化完成\")",
|
| 914 |
-
"detail": "gunicorn_config",
|
| 915 |
-
"documentation": {}
|
| 916 |
-
},
|
| 917 |
-
{
|
| 918 |
-
"label": "bind",
|
| 919 |
-
"kind": 5,
|
| 920 |
-
"importPath": "gunicorn_config",
|
| 921 |
-
"description": "gunicorn_config",
|
| 922 |
-
"peekOfCode": "bind = f\"0.0.0.0:{os.environ.get('PORT', 7860)}\"\n# 工作进程数(根据 CPU 核心数自动调整)\nworkers = int(os.environ.get('GUNICORN_WORKERS', multiprocessing.cpu_count() * 2 + 1))\n# 如果资源受限,可以手动设置较小的值\nworkers = min(workers, 4)\n# 工作进程类型\nworker_class = 'sync'\n# 每个工作进程的线程数\nthreads = 2\n# 超时时间(秒)",
|
| 923 |
-
"detail": "gunicorn_config",
|
| 924 |
-
"documentation": {}
|
| 925 |
-
},
|
| 926 |
-
{
|
| 927 |
-
"label": "workers",
|
| 928 |
-
"kind": 5,
|
| 929 |
-
"importPath": "gunicorn_config",
|
| 930 |
-
"description": "gunicorn_config",
|
| 931 |
-
"peekOfCode": "workers = int(os.environ.get('GUNICORN_WORKERS', multiprocessing.cpu_count() * 2 + 1))\n# 如果资源受限,可以手动设置较小的值\nworkers = min(workers, 4)\n# 工作进程类型\nworker_class = 'sync'\n# 每个工作进程的线程数\nthreads = 2\n# 超时时间(秒)\ntimeout = 120\n# 保持活动连接的时间",
|
| 932 |
-
"detail": "gunicorn_config",
|
| 933 |
-
"documentation": {}
|
| 934 |
-
},
|
| 935 |
-
{
|
| 936 |
-
"label": "workers",
|
| 937 |
-
"kind": 5,
|
| 938 |
-
"importPath": "gunicorn_config",
|
| 939 |
-
"description": "gunicorn_config",
|
| 940 |
-
"peekOfCode": "workers = min(workers, 4)\n# 工作进程类型\nworker_class = 'sync'\n# 每个工作进程的线程数\nthreads = 2\n# 超时时间(秒)\ntimeout = 120\n# 保持活动连接的时间\nkeepalive = 5\n# 最大请求数(处理后重启进程,防止内存泄漏)",
|
| 941 |
-
"detail": "gunicorn_config",
|
| 942 |
-
"documentation": {}
|
| 943 |
-
},
|
| 944 |
-
{
|
| 945 |
-
"label": "worker_class",
|
| 946 |
-
"kind": 5,
|
| 947 |
-
"importPath": "gunicorn_config",
|
| 948 |
-
"description": "gunicorn_config",
|
| 949 |
-
"peekOfCode": "worker_class = 'sync'\n# 每个工作进程的线程数\nthreads = 2\n# 超时时间(秒)\ntimeout = 120\n# 保持活动连接的时间\nkeepalive = 5\n# 最大请求数(处理后重启进程,防止内存泄漏)\nmax_requests = 1000\nmax_requests_jitter = 50",
|
| 950 |
-
"detail": "gunicorn_config",
|
| 951 |
-
"documentation": {}
|
| 952 |
-
},
|
| 953 |
-
{
|
| 954 |
-
"label": "threads",
|
| 955 |
-
"kind": 5,
|
| 956 |
-
"importPath": "gunicorn_config",
|
| 957 |
-
"description": "gunicorn_config",
|
| 958 |
-
"peekOfCode": "threads = 2\n# 超时时间(秒)\ntimeout = 120\n# 保持活动连接的时间\nkeepalive = 5\n# 最大请求数(处理后重启进程,防止内存泄漏)\nmax_requests = 1000\nmax_requests_jitter = 50\n# 日志配置\naccesslog = '-' # 输出到 stdout",
|
| 959 |
-
"detail": "gunicorn_config",
|
| 960 |
-
"documentation": {}
|
| 961 |
-
},
|
| 962 |
-
{
|
| 963 |
-
"label": "timeout",
|
| 964 |
-
"kind": 5,
|
| 965 |
-
"importPath": "gunicorn_config",
|
| 966 |
-
"description": "gunicorn_config",
|
| 967 |
-
"peekOfCode": "timeout = 120\n# 保持活动连接的时间\nkeepalive = 5\n# 最大请求数(处理后重启进程,防止内存泄漏)\nmax_requests = 1000\nmax_requests_jitter = 50\n# 日志配置\naccesslog = '-' # 输出到 stdout\nerrorlog = '-' # 输出到 stderr\nloglevel = 'info'",
|
| 968 |
-
"detail": "gunicorn_config",
|
| 969 |
-
"documentation": {}
|
| 970 |
-
},
|
| 971 |
-
{
|
| 972 |
-
"label": "keepalive",
|
| 973 |
-
"kind": 5,
|
| 974 |
-
"importPath": "gunicorn_config",
|
| 975 |
-
"description": "gunicorn_config",
|
| 976 |
-
"peekOfCode": "keepalive = 5\n# 最大请求数(处理后重启进程,防止内存泄漏)\nmax_requests = 1000\nmax_requests_jitter = 50\n# 日志配置\naccesslog = '-' # 输出到 stdout\nerrorlog = '-' # 输出到 stderr\nloglevel = 'info'\n# 自定义访问日志格式 - 包含真实客户端 IP\n# %(h)s - 远程地址",
|
| 977 |
-
"detail": "gunicorn_config",
|
| 978 |
-
"documentation": {}
|
| 979 |
-
},
|
| 980 |
-
{
|
| 981 |
-
"label": "max_requests",
|
| 982 |
-
"kind": 5,
|
| 983 |
-
"importPath": "gunicorn_config",
|
| 984 |
-
"description": "gunicorn_config",
|
| 985 |
-
"peekOfCode": "max_requests = 1000\nmax_requests_jitter = 50\n# 日志配置\naccesslog = '-' # 输出到 stdout\nerrorlog = '-' # 输出到 stderr\nloglevel = 'info'\n# 自定义访问日志格式 - 包含真实客户端 IP\n# %(h)s - 远程地址\n# %({X-Forwarded-For}i)s - X-Forwarded-For 头部(代理后面的真实 IP)\n# %(t)s - 时间",
|
| 986 |
-
"detail": "gunicorn_config",
|
| 987 |
-
"documentation": {}
|
| 988 |
-
},
|
| 989 |
-
{
|
| 990 |
-
"label": "max_requests_jitter",
|
| 991 |
-
"kind": 5,
|
| 992 |
-
"importPath": "gunicorn_config",
|
| 993 |
-
"description": "gunicorn_config",
|
| 994 |
-
"peekOfCode": "max_requests_jitter = 50\n# 日志配置\naccesslog = '-' # 输出到 stdout\nerrorlog = '-' # 输出到 stderr\nloglevel = 'info'\n# 自定义访问日志格式 - 包含真实客户端 IP\n# %(h)s - 远程地址\n# %({X-Forwarded-For}i)s - X-Forwarded-For 头部(代理后面的真实 IP)\n# %(t)s - 时间\n# %(m)s - 请求方法",
|
| 995 |
-
"detail": "gunicorn_config",
|
| 996 |
-
"documentation": {}
|
| 997 |
-
},
|
| 998 |
-
{
|
| 999 |
-
"label": "accesslog",
|
| 1000 |
-
"kind": 5,
|
| 1001 |
-
"importPath": "gunicorn_config",
|
| 1002 |
-
"description": "gunicorn_config",
|
| 1003 |
-
"peekOfCode": "accesslog = '-' # 输出到 stdout\nerrorlog = '-' # 输出到 stderr\nloglevel = 'info'\n# 自定义访问日志格式 - 包含真实客户端 IP\n# %(h)s - 远程地址\n# %({X-Forwarded-For}i)s - X-Forwarded-For 头部(代理后面的真实 IP)\n# %(t)s - 时间\n# %(m)s - 请求方法\n# %(U)s - URL 路径\n# %(q)s - 查询字符串",
|
| 1004 |
-
"detail": "gunicorn_config",
|
| 1005 |
-
"documentation": {}
|
| 1006 |
-
},
|
| 1007 |
-
{
|
| 1008 |
-
"label": "errorlog",
|
| 1009 |
-
"kind": 5,
|
| 1010 |
-
"importPath": "gunicorn_config",
|
| 1011 |
-
"description": "gunicorn_config",
|
| 1012 |
-
"peekOfCode": "errorlog = '-' # 输出到 stderr\nloglevel = 'info'\n# 自定义访问日志格式 - 包含真实客户端 IP\n# %(h)s - 远程地址\n# %({X-Forwarded-For}i)s - X-Forwarded-For 头部(代理后面的真实 IP)\n# %(t)s - 时间\n# %(m)s - 请求方法\n# %(U)s - URL 路径\n# %(q)s - 查询字符串\n# %(s)s - 状态码",
|
| 1013 |
-
"detail": "gunicorn_config",
|
| 1014 |
-
"documentation": {}
|
| 1015 |
-
},
|
| 1016 |
-
{
|
| 1017 |
-
"label": "loglevel",
|
| 1018 |
-
"kind": 5,
|
| 1019 |
-
"importPath": "gunicorn_config",
|
| 1020 |
-
"description": "gunicorn_config",
|
| 1021 |
-
"peekOfCode": "loglevel = 'info'\n# 自定义访问日志格式 - 包含真实客户端 IP\n# %(h)s - 远程地址\n# %({X-Forwarded-For}i)s - X-Forwarded-For 头部(代理后面的真实 IP)\n# %(t)s - 时间\n# %(m)s - 请求方法\n# %(U)s - URL 路径\n# %(q)s - 查询字符串\n# %(s)s - 状态码\n# %(b)s - 响应大小",
|
| 1022 |
-
"detail": "gunicorn_config",
|
| 1023 |
-
"documentation": {}
|
| 1024 |
-
},
|
| 1025 |
-
{
|
| 1026 |
-
"label": "access_log_format",
|
| 1027 |
-
"kind": 5,
|
| 1028 |
-
"importPath": "gunicorn_config",
|
| 1029 |
-
"description": "gunicorn_config",
|
| 1030 |
-
"peekOfCode": "access_log_format = (\n '[%(t)s] '\n 'Client: %({X-Forwarded-For}i)s (Remote: %(h)s) '\n '%(m)s %(U)s%(q)s '\n 'Status: %(s)s '\n 'Size: %(b)s bytes '\n 'Time: %(D)s μs '\n 'UA: \"%({User-Agent}i)s\"'\n)\n# 进程名称前缀",
|
| 1031 |
-
"detail": "gunicorn_config",
|
| 1032 |
-
"documentation": {}
|
| 1033 |
-
},
|
| 1034 |
-
{
|
| 1035 |
-
"label": "proc_name",
|
| 1036 |
-
"kind": 5,
|
| 1037 |
-
"importPath": "gunicorn_config",
|
| 1038 |
-
"description": "gunicorn_config",
|
| 1039 |
-
"peekOfCode": "proc_name = 'english_learning_app'\n# 守护进程(通常设为 False,让容器管理进程)\ndaemon = False\n# 预加载应用(减少内存占用)\npreload_app = True\n# 在请求处理前后执行的钩子\ndef on_starting(server):\n \"\"\"服务器启动时执行\"\"\"\n print(\"=\" * 60)\n print(\"🚀 Gunicorn 服务器启动\")",
|
| 1040 |
-
"detail": "gunicorn_config",
|
| 1041 |
-
"documentation": {}
|
| 1042 |
-
},
|
| 1043 |
-
{
|
| 1044 |
-
"label": "daemon",
|
| 1045 |
-
"kind": 5,
|
| 1046 |
-
"importPath": "gunicorn_config",
|
| 1047 |
-
"description": "gunicorn_config",
|
| 1048 |
-
"peekOfCode": "daemon = False\n# 预加载应用(减少内存占用)\npreload_app = True\n# 在请求处理前后执行的钩子\ndef on_starting(server):\n \"\"\"服务器启动时执行\"\"\"\n print(\"=\" * 60)\n print(\"🚀 Gunicorn 服务器启动\")\n print(f\"📍 绑定地址: {bind}\")\n print(f\"👷 工作进程数: {workers}\")",
|
| 1049 |
-
"detail": "gunicorn_config",
|
| 1050 |
-
"documentation": {}
|
| 1051 |
-
},
|
| 1052 |
-
{
|
| 1053 |
-
"label": "preload_app",
|
| 1054 |
-
"kind": 5,
|
| 1055 |
-
"importPath": "gunicorn_config",
|
| 1056 |
-
"description": "gunicorn_config",
|
| 1057 |
-
"peekOfCode": "preload_app = True\n# 在请求处理前后执行的钩子\ndef on_starting(server):\n \"\"\"服务器启动时执行\"\"\"\n print(\"=\" * 60)\n print(\"🚀 Gunicorn 服务器启动\")\n print(f\"📍 绑定地址: {bind}\")\n print(f\"👷 工作进程数: {workers}\")\n print(f\"🧵 每进程线程数: {threads}\")\n print(f\"⏱️ 超时时间: {timeout}s\")",
|
| 1058 |
-
"detail": "gunicorn_config",
|
| 1059 |
-
"documentation": {}
|
| 1060 |
-
},
|
| 1061 |
-
{
|
| 1062 |
-
"label": "create_database",
|
| 1063 |
-
"kind": 2,
|
| 1064 |
-
"importPath": "import_book_data",
|
| 1065 |
-
"description": "import_book_data",
|
| 1066 |
-
"peekOfCode": "def create_database(db_path='books.db', schema_path='db_schema.sql'):\n \"\"\"\n 创建数据库和表结构\n Args:\n db_path: 数据库文件路径\n schema_path: Schema SQL文件路径\n Returns:\n sqlite3.Connection: 数据库连接对象\n \"\"\"\n logger.info(f\"📦 创建数据库: {db_path}\")",
|
| 1067 |
-
"detail": "import_book_data",
|
| 1068 |
-
"documentation": {}
|
| 1069 |
-
},
|
| 1070 |
-
{
|
| 1071 |
-
"label": "import_book_info",
|
| 1072 |
-
"kind": 2,
|
| 1073 |
-
"importPath": "import_book_data",
|
| 1074 |
-
"description": "import_book_data",
|
| 1075 |
-
"peekOfCode": "def import_book_info(conn, book_dir: Path) -> int:\n \"\"\"\n 导入书籍基本信息\n Args:\n conn: 数据库连接\n book_dir: 书籍目录\n Returns:\n int: 书籍ID\n \"\"\"\n book_info_file = book_dir / \"book_info.json\"",
|
| 1076 |
-
"detail": "import_book_data",
|
| 1077 |
-
"documentation": {}
|
| 1078 |
-
},
|
| 1079 |
-
{
|
| 1080 |
-
"label": "import_pages",
|
| 1081 |
-
"kind": 2,
|
| 1082 |
-
"importPath": "import_book_data",
|
| 1083 |
-
"description": "import_book_data",
|
| 1084 |
-
"peekOfCode": "def import_pages(conn, book_dir: Path, book_id: int):\n \"\"\"\n 导入页面数据\n Args:\n conn: 数据库连接\n book_dir: 书籍目录\n book_id: 书籍ID\n \"\"\"\n pages_file = book_dir / \"pages.json\"\n if not pages_file.exists():",
|
| 1085 |
-
"detail": "import_book_data",
|
| 1086 |
-
"documentation": {}
|
| 1087 |
-
},
|
| 1088 |
-
{
|
| 1089 |
-
"label": "generate_catalog_from_pages",
|
| 1090 |
-
"kind": 2,
|
| 1091 |
-
"importPath": "import_book_data",
|
| 1092 |
-
"description": "import_book_data",
|
| 1093 |
-
"peekOfCode": "def generate_catalog_from_pages(book_dir: Path, book_id: int, pages_data: list) -> list:\n \"\"\"\n 从pages.json自动生成目录结构\n 为每一页生成一个目录项,使用该页第一个piece的文本作为标题\n Args:\n book_dir: 书籍目录\n book_id: 书籍ID\n pages_data: 页面数据列表\n Returns:\n 生成的目录列表",
|
| 1094 |
-
"detail": "import_book_data",
|
| 1095 |
-
"documentation": {}
|
| 1096 |
-
},
|
| 1097 |
-
{
|
| 1098 |
-
"label": "import_catalog",
|
| 1099 |
-
"kind": 2,
|
| 1100 |
-
"importPath": "import_book_data",
|
| 1101 |
-
"description": "import_book_data",
|
| 1102 |
-
"peekOfCode": "def import_catalog(conn, book_dir: Path, book_id: int, pages_data: list = None):\n \"\"\"\n 导入目录数据(强制从pages.json生成)\n Args:\n conn: 数据库连接\n book_dir: 书籍目录\n book_id: 书籍ID\n pages_data: 页面数据(用于自动生成目录)\n \"\"\"\n catalogs = []",
|
| 1103 |
-
"detail": "import_book_data",
|
| 1104 |
-
"documentation": {}
|
| 1105 |
-
},
|
| 1106 |
-
{
|
| 1107 |
-
"label": "import_book",
|
| 1108 |
-
"kind": 2,
|
| 1109 |
-
"importPath": "import_book_data",
|
| 1110 |
-
"description": "import_book_data",
|
| 1111 |
-
"peekOfCode": "def import_book(conn, book_dir: Path):\n \"\"\"\n 导入单本书籍的完整数据\n Args:\n conn: 数据库连接\n book_dir: 书籍目录\n \"\"\"\n book_name = book_dir.name\n logger.info(f\"\\n{'=' * 60}\")\n logger.info(f\"开始导入书籍: {book_name}\")",
|
| 1112 |
-
"detail": "import_book_data",
|
| 1113 |
-
"documentation": {}
|
| 1114 |
-
},
|
| 1115 |
-
{
|
| 1116 |
-
"label": "import_all_books",
|
| 1117 |
-
"kind": 2,
|
| 1118 |
-
"importPath": "import_book_data",
|
| 1119 |
-
"description": "import_book_data",
|
| 1120 |
-
"peekOfCode": "def import_all_books(conn, data_dir: str):\n \"\"\"\n 导入所有书籍\n Args:\n conn: 数据库连接\n data_dir: books_api_data 目录路径\n \"\"\"\n logger.info(\"=\" * 60)\n logger.info(\"开始批量导入书籍数据\")\n logger.info(f\"数据目录: {data_dir}\")",
|
| 1121 |
-
"detail": "import_book_data",
|
| 1122 |
-
"documentation": {}
|
| 1123 |
-
},
|
| 1124 |
-
{
|
| 1125 |
-
"label": "verify_data",
|
| 1126 |
-
"kind": 2,
|
| 1127 |
-
"importPath": "import_book_data",
|
| 1128 |
-
"description": "import_book_data",
|
| 1129 |
-
"peekOfCode": "def verify_data(conn):\n \"\"\"\n 验证导入的数据\n Args:\n conn: 数据库连接\n \"\"\"\n logger.info(\"\\n🔍 验证数据...\")\n cursor = conn.cursor()\n # 统计数据\n cursor.execute('SELECT COUNT(*) FROM books')",
|
| 1130 |
-
"detail": "import_book_data",
|
| 1131 |
-
"documentation": {}
|
| 1132 |
-
},
|
| 1133 |
-
{
|
| 1134 |
-
"label": "main",
|
| 1135 |
-
"kind": 2,
|
| 1136 |
-
"importPath": "import_book_data",
|
| 1137 |
-
"description": "import_book_data",
|
| 1138 |
-
"peekOfCode": "def main():\n \"\"\"主函数\"\"\"\n import argparse\n print(\"=\" * 80)\n print(\"📚 Books API Data 导入工具\")\n print(\"=\" * 80)\n # 命令行参数解析\n parser = argparse.ArgumentParser(description='导入 books_api_data 数据到 SQLite 数据库')\n parser.add_argument('data_dir', nargs='?', default='books_api_data',\n help='books_api_data 数据目录路径 (默认: books_api_data)')",
|
| 1139 |
-
"detail": "import_book_data",
|
| 1140 |
-
"documentation": {}
|
| 1141 |
-
},
|
| 1142 |
-
{
|
| 1143 |
-
"label": "logger",
|
| 1144 |
-
"kind": 5,
|
| 1145 |
-
"importPath": "import_book_data",
|
| 1146 |
-
"description": "import_book_data",
|
| 1147 |
-
"peekOfCode": "logger = logging.getLogger(__name__)\ndef create_database(db_path='books.db', schema_path='db_schema.sql'):\n \"\"\"\n 创建数据库和表结构\n Args:\n db_path: 数据库文件路径\n schema_path: Schema SQL文件路径\n Returns:\n sqlite3.Connection: 数据库连接对象\n \"\"\"",
|
| 1148 |
-
"detail": "import_book_data",
|
| 1149 |
-
"documentation": {}
|
| 1150 |
-
},
|
| 1151 |
-
{
|
| 1152 |
-
"label": "OpenSearchClient",
|
| 1153 |
-
"kind": 6,
|
| 1154 |
-
"importPath": "opensearch_client",
|
| 1155 |
-
"description": "opensearch_client",
|
| 1156 |
-
"peekOfCode": "class OpenSearchClient:\n \"\"\"OpenSearch 客户端封装类\"\"\"\n def __init__(self, host='192.168.3.33', port=9200, use_ssl=False, verify_certs=False):\n \"\"\"\n 初始化 OpenSearch 客户端\n Args:\n host: OpenSearch 主机地址\n port: OpenSearch 端口\n use_ssl: 是否使用 SSL\n verify_certs: 是否验证证书",
|
| 1157 |
-
"detail": "opensearch_client",
|
| 1158 |
-
"documentation": {}
|
| 1159 |
-
},
|
| 1160 |
-
{
|
| 1161 |
-
"label": "initialize_learning_index",
|
| 1162 |
-
"kind": 2,
|
| 1163 |
-
"importPath": "opensearch_client",
|
| 1164 |
-
"description": "opensearch_client",
|
| 1165 |
-
"peekOfCode": "def initialize_learning_index(client, index_name='english_learning_content'):\n \"\"\"\n 初始化英语学习内容索引\n 索引包含:单词、例句、页面内容等\n \"\"\"\n # 定义索引映射\n mappings = {\n 'properties': {\n 'type': {'type': 'keyword'}, # 内容类型:word, sentence, page\n 'page_num': {'type': 'integer'}, # 页码",
|
| 1166 |
-
"detail": "opensearch_client",
|
| 1167 |
-
"documentation": {}
|
| 1168 |
-
},
|
| 1169 |
-
{
|
| 1170 |
-
"label": "search_english_content",
|
| 1171 |
-
"kind": 2,
|
| 1172 |
-
"importPath": "opensearch_client",
|
| 1173 |
-
"description": "opensearch_client",
|
| 1174 |
-
"peekOfCode": "def search_english_content(client, keyword, index_name='english_learning_content'):\n \"\"\"\n 搜索英语学习内容\n Args:\n client: OpenSearchClient 实例\n keyword: 搜索关键词\n index_name: 索引名��\n Returns:\n 搜索结果列表\n \"\"\"",
|
| 1175 |
-
"detail": "opensearch_client",
|
| 1176 |
-
"documentation": {}
|
| 1177 |
-
},
|
| 1178 |
-
{
|
| 1179 |
-
"label": "logger",
|
| 1180 |
-
"kind": 5,
|
| 1181 |
-
"importPath": "opensearch_client",
|
| 1182 |
-
"description": "opensearch_client",
|
| 1183 |
-
"peekOfCode": "logger = logging.getLogger(__name__)\nclass OpenSearchClient:\n \"\"\"OpenSearch 客户端封装类\"\"\"\n def __init__(self, host='192.168.3.33', port=9200, use_ssl=False, verify_certs=False):\n \"\"\"\n 初始化 OpenSearch 客户端\n Args:\n host: OpenSearch 主机地址\n port: OpenSearch 端口\n use_ssl: 是否使用 SSL",
|
| 1184 |
-
"detail": "opensearch_client",
|
| 1185 |
-
"documentation": {}
|
| 1186 |
-
},
|
| 1187 |
-
{
|
| 1188 |
-
"label": "main",
|
| 1189 |
-
"kind": 2,
|
| 1190 |
-
"importPath": "quick_check",
|
| 1191 |
-
"description": "quick_check",
|
| 1192 |
-
"peekOfCode": "def main():\n with open('book_10242.json', 'r', encoding='utf-8') as f:\n data = json.load(f)\n pages_data = json.loads(data['Data'])\n # 检查第4页(索引3)和第5页(索引4)\n for i in [3, 4]:\n if i < len(pages_data):\n page = pages_data[i]\n print(f\"=== 第{page['pageNumber']}页 ===\")\n print(f\"pageId: {page['pageId']}\")",
|
| 1193 |
-
"detail": "quick_check",
|
| 1194 |
-
"documentation": {}
|
| 1195 |
-
},
|
| 1196 |
-
{
|
| 1197 |
-
"label": "test_ip_logging",
|
| 1198 |
-
"kind": 2,
|
| 1199 |
-
"importPath": "test_ip_logging",
|
| 1200 |
-
"description": "test_ip_logging",
|
| 1201 |
-
"peekOfCode": "def test_ip_logging():\n \"\"\"测试 IP 地址记录\"\"\"\n base_url = \"http://localhost:7860\"\n print(\"=\" * 60)\n print(\"🧪 测试客户端 IP 记录功能\")\n print(\"=\" * 60)\n # 测试场景\n test_cases = [\n {\n \"name\": \"正常请求(无代理)\",",
|
| 1202 |
-
"detail": "test_ip_logging",
|
| 1203 |
-
"documentation": {}
|
| 1204 |
-
},
|
| 1205 |
-
{
|
| 1206 |
-
"label": "show_example_logs",
|
| 1207 |
-
"kind": 2,
|
| 1208 |
-
"importPath": "test_ip_logging",
|
| 1209 |
-
"description": "test_ip_logging",
|
| 1210 |
-
"peekOfCode": "def show_example_logs():\n \"\"\"显示日志示例\"\"\"\n print(\"\\n\" + \"=\" * 60)\n print(\"📝 日志输出示例\")\n print(\"=\" * 60)\n examples = [\n \"[2025-10-16 18:58:23] INFO in app: [203.0.113.1] GET /api/health | UA: Mozilla/5.0 (Windows NT 10.0)\",\n \"[2025-10-16 18:58:24] INFO in app: [198.51.100.1] POST /api/search | UA: Mozilla/5.0 (iPhone; CPU iPhone OS)\",\n \"[2025-10-16 18:58:25] INFO in app: [192.0.2.1] GET /api/book/info | UA: curl/7.68.0\",\n ]",
|
| 1211 |
-
"detail": "test_ip_logging",
|
| 1212 |
-
"documentation": {}
|
| 1213 |
-
},
|
| 1214 |
-
{
|
| 1215 |
-
"label": "print_status",
|
| 1216 |
-
"kind": 2,
|
| 1217 |
-
"importPath": "test_setup",
|
| 1218 |
-
"description": "test_setup",
|
| 1219 |
-
"peekOfCode": "def print_status(message, status):\n \"\"\"打印状态信息\"\"\"\n if status:\n print(f\"✅ {message}\")\n else:\n print(f\"❌ {message}\")\n return status\ndef check_python_version():\n \"\"\"检查Python版本\"\"\"\n version = sys.version_info",
|
| 1220 |
-
"detail": "test_setup",
|
| 1221 |
-
"documentation": {}
|
| 1222 |
-
},
|
| 1223 |
-
{
|
| 1224 |
-
"label": "check_python_version",
|
| 1225 |
-
"kind": 2,
|
| 1226 |
-
"importPath": "test_setup",
|
| 1227 |
-
"description": "test_setup",
|
| 1228 |
-
"peekOfCode": "def check_python_version():\n \"\"\"检查Python版本\"\"\"\n version = sys.version_info\n is_ok = version.major == 3 and version.minor >= 12\n print_status(\n f\"Python版本: {version.major}.{version.minor}.{version.micro} {'(需要 3.12+)' if not is_ok else ''}\",\n is_ok\n )\n return is_ok\ndef check_dependencies():",
|
| 1229 |
-
"detail": "test_setup",
|
| 1230 |
-
"documentation": {}
|
| 1231 |
-
},
|
| 1232 |
-
{
|
| 1233 |
-
"label": "check_dependencies",
|
| 1234 |
-
"kind": 2,
|
| 1235 |
-
"importPath": "test_setup",
|
| 1236 |
-
"description": "test_setup",
|
| 1237 |
-
"peekOfCode": "def check_dependencies():\n \"\"\"检查Python依赖\"\"\"\n dependencies = ['flask', 'flask_cors']\n all_ok = True\n for dep in dependencies:\n try:\n __import__(dep)\n print_status(f\"Python包 '{dep}' 已安装\", True)\n except ImportError:\n print_status(f\"Python包 '{dep}' 未安装\", False)",
|
| 1238 |
-
"detail": "test_setup",
|
| 1239 |
-
"documentation": {}
|
| 1240 |
-
},
|
| 1241 |
-
{
|
| 1242 |
-
"label": "check_files",
|
| 1243 |
-
"kind": 2,
|
| 1244 |
-
"importPath": "test_setup",
|
| 1245 |
-
"description": "test_setup",
|
| 1246 |
-
"peekOfCode": "def check_files():\n \"\"\"检查必要文件\"\"\"\n files = [\n 'app.py',\n 'db_manager.py',\n 'import_book_data.py',\n 'db_schema.sql',\n 'index.html',\n 'reader.html',\n 'static/js/catalog.js',",
|
| 1247 |
-
"detail": "test_setup",
|
| 1248 |
-
"documentation": {}
|
| 1249 |
-
},
|
| 1250 |
-
{
|
| 1251 |
-
"label": "check_data_directory",
|
| 1252 |
-
"kind": 2,
|
| 1253 |
-
"importPath": "test_setup",
|
| 1254 |
-
"description": "test_setup",
|
| 1255 |
-
"peekOfCode": "def check_data_directory():\n \"\"\"检查数据目录\"\"\"\n data_dir = Path('books_api_data')\n if not data_dir.exists():\n print_status(\"数据目录 'books_api_data' 不存在\", False)\n print(\" 提示: 请将 books_api_data 目录放置在项目根目录下\")\n return False\n # 检查是否有书籍数据\n book_dirs = [d for d in data_dir.iterdir() if d.is_dir()]\n if len(book_dirs) == 0:",
|
| 1256 |
-
"detail": "test_setup",
|
| 1257 |
-
"documentation": {}
|
| 1258 |
-
},
|
| 1259 |
-
{
|
| 1260 |
-
"label": "check_database",
|
| 1261 |
-
"kind": 2,
|
| 1262 |
-
"importPath": "test_setup",
|
| 1263 |
-
"description": "test_setup",
|
| 1264 |
-
"peekOfCode": "def check_database():\n \"\"\"检查数据库\"\"\"\n db_file = Path('books.db')\n if not db_file.exists():\n print_status(\"数据库 'books.db' 不存在\", False)\n print(\" 提示: 运行 'python3 import_book_data.py books_api_data' 来导入数据\")\n return False\n print_status(\"数据库 'books.db' 存在\", True)\n # 检查数据库内容\n try:",
|
| 1265 |
-
"detail": "test_setup",
|
| 1266 |
-
"documentation": {}
|
| 1267 |
-
},
|
| 1268 |
-
{
|
| 1269 |
-
"label": "main",
|
| 1270 |
-
"kind": 2,
|
| 1271 |
-
"importPath": "test_setup",
|
| 1272 |
-
"description": "test_setup",
|
| 1273 |
-
"peekOfCode": "def main():\n \"\"\"主函数\"\"\"\n print(\"=\" * 70)\n print(\"🔍 交互式英语学习平台 - 系统设置检查\")\n print(\"=\" * 70)\n print()\n checks = [\n (\"Python版本\", check_python_version),\n (\"Python依赖\", check_dependencies),\n (\"必要文件\", check_files),",
|
| 1274 |
-
"detail": "test_setup",
|
| 1275 |
-
"documentation": {}
|
| 1276 |
-
}
|
| 1277 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
DELETED
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
FROM python:3.12-slim
|
| 2 |
-
|
| 3 |
-
# 设置工作目录
|
| 4 |
-
WORKDIR /app
|
| 5 |
-
|
| 6 |
-
# 设置环境变量
|
| 7 |
-
ENV PYTHONUNBUFFERED=1 \
|
| 8 |
-
PYTHONDONTWRITEBYTECODE=1 \
|
| 9 |
-
PORT=7860 \
|
| 10 |
-
FLASK_ENV=production
|
| 11 |
-
|
| 12 |
-
# 安装系统依赖(如需要)
|
| 13 |
-
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 14 |
-
curl \
|
| 15 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 16 |
-
|
| 17 |
-
# 复制requirements文件
|
| 18 |
-
COPY requirements.txt .
|
| 19 |
-
|
| 20 |
-
# 安装Python依赖
|
| 21 |
-
RUN pip install --no-cache-dir --upgrade pip && \
|
| 22 |
-
pip install --no-cache-dir -r requirements.txt
|
| 23 |
-
|
| 24 |
-
# 复制应用文件
|
| 25 |
-
COPY . .
|
| 26 |
-
|
| 27 |
-
# 创建日志目录并设置权限
|
| 28 |
-
RUN mkdir -p logs && chmod 777 logs
|
| 29 |
-
|
| 30 |
-
# 暴露7860端口(Hugging Face Spaces要求)
|
| 31 |
-
EXPOSE 7860
|
| 32 |
-
|
| 33 |
-
# 健康检查
|
| 34 |
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 35 |
-
CMD curl -f http://localhost:7860/api/health || exit 1
|
| 36 |
-
|
| 37 |
-
# 使用 Gunicorn 启动 Flask 应用(生产环境推荐)
|
| 38 |
-
# 使用配置文件以显示客户端真实 IP
|
| 39 |
-
CMD ["gunicorn", "-c", "gunicorn_config.py", "app:app"]
|
| 40 |
-
|
| 41 |
-
# 如果想使用 Flask 内置服务器(开发/测试用),使用下面这行:
|
| 42 |
-
# CMD ["python3.12", "app.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,332 +1,10 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
-
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
基于 data 数据结构的交互式英语学习应用,支持点读、音频播放、翻译显示等功能。
|
| 14 |
-
|
| 15 |
-
## 📋 功能特性
|
| 16 |
-
|
| 17 |
-
### 书籍目录页面
|
| 18 |
-
- 📚 显示所有可用教材
|
| 19 |
-
- 🔍 搜索教材名称和分类
|
| 20 |
-
- 📊 显示统计信息(教材数、页面数、内容片段数等)
|
| 21 |
-
- 🎨 精美的卡片式布局
|
| 22 |
-
|
| 23 |
-
### 阅读页面
|
| 24 |
-
- 📖 交互式点读功能
|
| 25 |
-
- 🔊 音频播放控制
|
| 26 |
-
- 🌐 中英文翻译切换
|
| 27 |
-
- 📑 章节目录导航
|
| 28 |
-
- 🔖 书签功能
|
| 29 |
-
- 🔍 全文搜索
|
| 30 |
-
- ⚙️ 个性化设置(播放速度、自动翻译等)
|
| 31 |
-
- ⌨️ 键盘快捷键支持
|
| 32 |
-
|
| 33 |
-
## 🚀 快速开始
|
| 34 |
-
|
| 35 |
-
### 1. 环境要求
|
| 36 |
-
|
| 37 |
-
- Python 3.12
|
| 38 |
-
- SQLite 3
|
| 39 |
-
- 现代浏览器(Chrome、Firefox、Safari、Edge)
|
| 40 |
-
|
| 41 |
-
### 2. 安装依赖
|
| 42 |
-
|
| 43 |
-
```bash
|
| 44 |
-
pip install flask flask-cors
|
| 45 |
-
```
|
| 46 |
-
|
| 47 |
-
### 3. 准备数据
|
| 48 |
-
|
| 49 |
-
将 `data` 目录放置在项目根目录下:
|
| 50 |
-
|
| 51 |
-
```
|
| 52 |
-
/data/zhangl/code/hf/point/
|
| 53 |
-
├── data/ # 教材数据目录
|
| 54 |
-
│ ├── 168_一年级上册/
|
| 55 |
-
│ │ ├── book_info.json
|
| 56 |
-
│ │ ├── pages.json
|
| 57 |
-
│ │ ├── catalog.json
|
| 58 |
-
│ │ ├── images/
|
| 59 |
-
│ │ └── audios/
|
| 60 |
-
│ ├── 169_二年级上册/
|
| 61 |
-
│ └── ...
|
| 62 |
-
├── app.py
|
| 63 |
-
├── import_book_data.py
|
| 64 |
-
└── ...
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
### 4. 导入数据到数据库
|
| 68 |
-
|
| 69 |
-
```bash
|
| 70 |
-
# 首次导入(会自动创建数据库)
|
| 71 |
-
python3 import_book_data.py data
|
| 72 |
-
|
| 73 |
-
# 重新导入所有数据(删除现有数据库)
|
| 74 |
-
python3 import_book_data.py data --recreate
|
| 75 |
-
|
| 76 |
-
# 仅验证数据
|
| 77 |
-
python3 import_book_data.py --verify-only
|
| 78 |
-
```
|
| 79 |
-
|
| 80 |
-
导入选项:
|
| 81 |
-
- `data_dir`: 数据目录路径(默认:data)
|
| 82 |
-
- `--db`: 数据库文件路径(默认:books.db)
|
| 83 |
-
- `--schema`: Schema 文件路径(默认:db_schema.sql)
|
| 84 |
-
- `--recreate`: 删除现有数据库并重新创建
|
| 85 |
-
- `--verify-only`: 仅验证数据,不导入
|
| 86 |
-
|
| 87 |
-
### 5. 启动应用
|
| 88 |
-
|
| 89 |
-
```bash
|
| 90 |
-
python3 app.py
|
| 91 |
-
```
|
| 92 |
-
|
| 93 |
-
应用将在 `http://0.0.0.0:7860` 启动。
|
| 94 |
-
|
| 95 |
-
访问地址:
|
| 96 |
-
- 书籍目录:`http://localhost:7860/`
|
| 97 |
-
- 阅读页面:`http://localhost:7860/reader?book_id=168`
|
| 98 |
-
|
| 99 |
-
## 📁 项目结构
|
| 100 |
-
|
| 101 |
-
```
|
| 102 |
-
/data/zhangl/code/hf/point/
|
| 103 |
-
├── data/ # 教材数据目录
|
| 104 |
-
├── static/
|
| 105 |
-
│ ├── css/
|
| 106 |
-
│ │ ├── style.css # 主样式文件
|
| 107 |
-
│ │ ├── inter.css # Inter 字体
|
| 108 |
-
│ │ └── all.min.css # Font Awesome 图标
|
| 109 |
-
│ └── js/
|
| 110 |
-
│ ├── catalog.js # 书籍目录页面逻辑
|
| 111 |
-
│ └── reader.js # 阅读页面逻辑
|
| 112 |
-
├── app.py # Flask 应用主文件
|
| 113 |
-
├── db_manager.py # 数据库管理器
|
| 114 |
-
├── import_book_data.py # 数据导入脚本
|
| 115 |
-
├── db_schema.sql # 数据库结构定义
|
| 116 |
-
├── index.html # 书籍目录页面
|
| 117 |
-
├── reader.html # 阅读页面
|
| 118 |
-
├── books.db # SQLite 数据库(自动生成)
|
| 119 |
-
└── README.md
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
## 🗄️ 数据库结构
|
| 123 |
-
|
| 124 |
-
### books 表
|
| 125 |
-
存储书籍的基本信息:
|
| 126 |
-
- `market_book_id`: 书籍ID(主键)
|
| 127 |
-
- `market_book_name`: 书籍名称
|
| 128 |
-
- `market_book_cover`: 封面图片路径
|
| 129 |
-
- `max_page`: 最大页码
|
| 130 |
-
- `grade_id`: 年级ID
|
| 131 |
-
- `reel_id`: 学期ID(1=上册,2=下册)
|
| 132 |
-
- 等等...
|
| 133 |
-
|
| 134 |
-
### pages 表
|
| 135 |
-
存储页面信息:
|
| 136 |
-
- `page_id`: 页面ID(主键)
|
| 137 |
-
- `book_id`: 所属书籍ID
|
| 138 |
-
- `page_number`: 页码
|
| 139 |
-
- `origin_img_url`: 原始图片路径
|
| 140 |
-
- `encrypt_img_url`: 加密图片路径
|
| 141 |
-
|
| 142 |
-
### pieces 表
|
| 143 |
-
存储页面内容片段:
|
| 144 |
-
- `piece_id`: 片段ID(主键)
|
| 145 |
-
- `page_id`: 所属页面ID
|
| 146 |
-
- `original`: 原文(英文)
|
| 147 |
-
- `translation`: 译文(中文)
|
| 148 |
-
- `origin_sound_url`: 音频路径
|
| 149 |
-
- `duration`: 音频时长
|
| 150 |
-
- `coordinate_x/y/width/height`: 坐标信息
|
| 151 |
-
- 等等...
|
| 152 |
-
|
| 153 |
-
### catalogs 表
|
| 154 |
-
存储书籍目录结构:
|
| 155 |
-
- `catalog_id`: 目录项ID(主键)
|
| 156 |
-
- `book_id`: 所属书籍ID
|
| 157 |
-
- `catalog_name`: 目录名称(英文)
|
| 158 |
-
- `catalog_name_cn`: 目录名称(中文)
|
| 159 |
-
- `start_page/end_page`: 起止页码
|
| 160 |
-
- `parent_id`: 父级目录ID(支持树形结构)
|
| 161 |
-
|
| 162 |
-
### pieces_fts 表
|
| 163 |
-
全文搜索索引(FTS5):
|
| 164 |
-
- 支持在 `original` 和 `translation` 字段中快速搜索
|
| 165 |
-
|
| 166 |
-
## 🔌 API 接口
|
| 167 |
-
|
| 168 |
-
### 书籍相关
|
| 169 |
-
|
| 170 |
-
#### 获取所有书籍列表
|
| 171 |
-
```
|
| 172 |
-
GET /api/v2/books?grade_id=40
|
| 173 |
-
```
|
| 174 |
-
|
| 175 |
-
#### 获取书籍详情
|
| 176 |
-
```
|
| 177 |
-
GET /api/v2/books/{book_id}
|
| 178 |
-
```
|
| 179 |
-
|
| 180 |
-
#### 获取书籍页面列表
|
| 181 |
-
```
|
| 182 |
-
GET /api/v2/books/{book_id}/pages
|
| 183 |
-
```
|
| 184 |
-
|
| 185 |
-
#### 获取书籍目录结构
|
| 186 |
-
```
|
| 187 |
-
GET /api/v2/books/{book_id}/catalog
|
| 188 |
-
```
|
| 189 |
-
|
| 190 |
-
#### 获取页面内容
|
| 191 |
-
```
|
| 192 |
-
GET /api/v2/books/{book_id}/pages/{page_number}
|
| 193 |
-
```
|
| 194 |
-
|
| 195 |
-
### 搜索相关
|
| 196 |
-
|
| 197 |
-
#### 在书籍中搜索
|
| 198 |
-
```
|
| 199 |
-
GET /api/v2/books/{book_id}/search?keyword=hello&limit=20
|
| 200 |
-
```
|
| 201 |
-
|
| 202 |
-
#### 全局搜索
|
| 203 |
-
```
|
| 204 |
-
GET /api/v2/search?keyword=hello&limit=50
|
| 205 |
-
```
|
| 206 |
-
|
| 207 |
-
### 统计相关
|
| 208 |
-
|
| 209 |
-
#### 获取书籍统计信息
|
| 210 |
-
```
|
| 211 |
-
GET /api/v2/books/{book_id}/statistics
|
| 212 |
-
```
|
| 213 |
-
|
| 214 |
-
#### 获取整体统计信息
|
| 215 |
-
```
|
| 216 |
-
GET /api/v2/statistics
|
| 217 |
-
```
|
| 218 |
-
|
| 219 |
-
## ⌨️ 键盘快捷键
|
| 220 |
-
|
| 221 |
-
在阅读页面中:
|
| 222 |
-
- `←` / `→`: 上一页 / 下一页
|
| 223 |
-
- `Space`: 播放/暂停音频
|
| 224 |
-
- `T`: 切换翻译显示
|
| 225 |
-
- `I`: 切换交互区域显示
|
| 226 |
-
- `Esc`: 关闭弹出面板
|
| 227 |
-
|
| 228 |
-
## 🎨 功能亮点
|
| 229 |
-
|
| 230 |
-
1. **按需加载**: 页面内容按需加载,提高性能
|
| 231 |
-
2. **全文搜索**: 使用 SQLite FTS5 实现高效全文搜索
|
| 232 |
-
3. **响应式设计**: 自适应不同屏幕尺寸
|
| 233 |
-
4. **交互式点读**: 点击文本片段播放对应音频
|
| 234 |
-
5. **坐标系统**: 使用相对坐标,适配不同分辨率
|
| 235 |
-
6. **目录导航**: 支持章节目录快速跳转
|
| 236 |
-
7. **本地资源**: 支持从本地 data 目录加载资源
|
| 237 |
-
|
| 238 |
-
## 🔧 开发调试
|
| 239 |
-
|
| 240 |
-
### 启用调试模式
|
| 241 |
-
|
| 242 |
-
```bash
|
| 243 |
-
export FLASK_DEBUG=True
|
| 244 |
-
python3 app.py
|
| 245 |
-
```
|
| 246 |
-
|
| 247 |
-
### 查看日志
|
| 248 |
-
|
| 249 |
-
应用日志存储在 `logs/app.log`
|
| 250 |
-
|
| 251 |
-
### 数据库操作
|
| 252 |
-
|
| 253 |
-
使用 `db_manager.py` 提供的数据库管理器:
|
| 254 |
-
|
| 255 |
-
```python
|
| 256 |
-
from db_manager import get_db_instance
|
| 257 |
-
|
| 258 |
-
db = get_db_instance('books.db')
|
| 259 |
-
|
| 260 |
-
# 获取所有书籍
|
| 261 |
-
books = db.get_all_books()
|
| 262 |
-
|
| 263 |
-
# 搜索内容
|
| 264 |
-
results = db.search_content(book_id=168, keyword='hello', limit=20)
|
| 265 |
-
|
| 266 |
-
# 获取统计信息
|
| 267 |
-
stats = db.get_overall_statistics()
|
| 268 |
-
```
|
| 269 |
-
|
| 270 |
-
## 📝 数据格式说明
|
| 271 |
-
|
| 272 |
-
### 坐标系统
|
| 273 |
-
文本片段的坐标使用相对坐标(0-1范围):
|
| 274 |
-
```json
|
| 275 |
-
{
|
| 276 |
-
"coordinate": {
|
| 277 |
-
"x": 0.0482, // 左上角X坐标(相对于图片宽度)
|
| 278 |
-
"y": 0.0357, // 左上角Y坐标(相对于图片高度)
|
| 279 |
-
"width": 0.3195, // 宽度(相对于图片宽度)
|
| 280 |
-
"height": 0.0982 // 高度(相对于图片高度)
|
| 281 |
-
}
|
| 282 |
-
}
|
| 283 |
-
```
|
| 284 |
-
|
| 285 |
-
### 资源路径
|
| 286 |
-
资源路径采用相对路径格式:
|
| 287 |
-
```
|
| 288 |
-
168_一年级上册/images/page_001.jpg
|
| 289 |
-
168_一年级上册/audios/page_002_piece_00.mp3
|
| 290 |
-
```
|
| 291 |
-
|
| 292 |
-
实际访问时会自动拼接为:
|
| 293 |
-
```
|
| 294 |
-
data/168_一年级上册/images/page_001.jpg
|
| 295 |
-
data/168_一年级上册/audios/page_002_piece_00.mp3
|
| 296 |
-
```
|
| 297 |
-
|
| 298 |
-
## 🐛 故障排除
|
| 299 |
-
|
| 300 |
-
### 数据库导入失败
|
| 301 |
-
- 检查 data 目录是否存在
|
| 302 |
-
- 确保每个书籍目录包含 book_info.json、pages.json
|
| 303 |
-
- 检查 db_schema.sql 文件是否存在
|
| 304 |
-
|
| 305 |
-
### 页面加载失败
|
| 306 |
-
- 检查数据库是否正确导入
|
| 307 |
-
- 查看浏览器控制台是否有错误
|
| 308 |
-
- 检查 data 目录下的资源文件是否完整
|
| 309 |
-
|
| 310 |
-
### 音频播放失败
|
| 311 |
-
- 检查音频文件路径是否正确
|
| 312 |
-
- 确保浏览器支持 MP3 格式
|
| 313 |
-
- 查看浏览器控制台错误信息
|
| 314 |
-
|
| 315 |
-
## 📄 许可证
|
| 316 |
-
|
| 317 |
-
本项目仅供学习和研究使用。
|
| 318 |
-
|
| 319 |
-
## 👥 贡献
|
| 320 |
-
|
| 321 |
-
欢迎提交 Issue 和 Pull Request。
|
| 322 |
-
|
| 323 |
-
## 📞 联系方式
|
| 324 |
-
|
| 325 |
-
如有问题或建议,请联系开发团队。
|
| 326 |
-
|
| 327 |
-
---
|
| 328 |
-
|
| 329 |
-
**版本**: v2.0.0
|
| 330 |
-
**更新日期**: 2025-10-17
|
| 331 |
-
**Python版本**: 3.12
|
| 332 |
-
**数据库**: SQLite 3
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Point
|
| 3 |
+
emoji: 🐢
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: docker
|
|
|
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
DELETED
|
@@ -1,1068 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
# -*- coding: utf-8 -*-
|
| 3 |
-
"""
|
| 4 |
-
交互式英语学习应用 - Flask 重构版本
|
| 5 |
-
在7860端口提供HTTP服务,支持静态文件和RESTful API
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import os
|
| 9 |
-
import sys
|
| 10 |
-
import json
|
| 11 |
-
import time
|
| 12 |
-
from datetime import datetime
|
| 13 |
-
from pathlib import Path
|
| 14 |
-
|
| 15 |
-
from flask import Flask, render_template, send_from_directory, jsonify, request, session
|
| 16 |
-
from flask_cors import CORS
|
| 17 |
-
import logging
|
| 18 |
-
from logging.handlers import RotatingFileHandler
|
| 19 |
-
from database.db_manager import get_db_instance
|
| 20 |
-
|
| 21 |
-
# 创建 Flask 应用
|
| 22 |
-
app = Flask(__name__,
|
| 23 |
-
static_folder='static',
|
| 24 |
-
static_url_path='/static')
|
| 25 |
-
|
| 26 |
-
# 配置
|
| 27 |
-
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')
|
| 28 |
-
app.config['JSON_AS_ASCII'] = False # 支持中文JSON
|
| 29 |
-
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 最大上传16MB
|
| 30 |
-
|
| 31 |
-
# 启用CORS
|
| 32 |
-
CORS(app, resources={
|
| 33 |
-
r"/*": {
|
| 34 |
-
"origins": "*",
|
| 35 |
-
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
| 36 |
-
"allow_headers": ["Content-Type", "Authorization"]
|
| 37 |
-
}
|
| 38 |
-
})
|
| 39 |
-
|
| 40 |
-
# 配置日志
|
| 41 |
-
def setup_logging():
|
| 42 |
-
"""配置日志系统"""
|
| 43 |
-
if not app.debug:
|
| 44 |
-
try:
|
| 45 |
-
# 尝试在当前目录创建日志目录
|
| 46 |
-
log_dir = Path('logs')
|
| 47 |
-
log_dir.mkdir(exist_ok=True)
|
| 48 |
-
log_file = log_dir / 'app.log'
|
| 49 |
-
except (PermissionError, OSError):
|
| 50 |
-
# 如果当前目录无权限,使用 /tmp 目录
|
| 51 |
-
try:
|
| 52 |
-
log_dir = Path('/tmp/logs')
|
| 53 |
-
log_dir.mkdir(exist_ok=True)
|
| 54 |
-
log_file = log_dir / 'app.log'
|
| 55 |
-
print(f"⚠️ 使用临时日志目录: {log_file}")
|
| 56 |
-
except (PermissionError, OSError):
|
| 57 |
-
# 如果都失败,只使用控制台日志
|
| 58 |
-
print("⚠️ 无法创建日志文件,仅输出到控制台")
|
| 59 |
-
log_file = None
|
| 60 |
-
|
| 61 |
-
# 如果有有效的日志文件路径,添加文件处理器
|
| 62 |
-
if log_file:
|
| 63 |
-
try:
|
| 64 |
-
file_handler = RotatingFileHandler(
|
| 65 |
-
log_file,
|
| 66 |
-
maxBytes=10 * 1024 * 1024,
|
| 67 |
-
backupCount=10,
|
| 68 |
-
encoding='utf-8'
|
| 69 |
-
)
|
| 70 |
-
file_handler.setFormatter(logging.Formatter(
|
| 71 |
-
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
| 72 |
-
))
|
| 73 |
-
file_handler.setLevel(logging.INFO)
|
| 74 |
-
app.logger.addHandler(file_handler)
|
| 75 |
-
except (PermissionError, OSError) as e:
|
| 76 |
-
print(f"⚠️ 无法创建日志处理器: {e}")
|
| 77 |
-
|
| 78 |
-
# 确保至少有控制台输出
|
| 79 |
-
if not app.logger.handlers:
|
| 80 |
-
console_handler = logging.StreamHandler()
|
| 81 |
-
console_handler.setFormatter(logging.Formatter(
|
| 82 |
-
'[%(asctime)s] %(levelname)s: %(message)s'
|
| 83 |
-
))
|
| 84 |
-
console_handler.setLevel(logging.INFO)
|
| 85 |
-
app.logger.addHandler(console_handler)
|
| 86 |
-
|
| 87 |
-
app.logger.setLevel(logging.INFO)
|
| 88 |
-
app.logger.info('🚀 Flask 应用启动')
|
| 89 |
-
|
| 90 |
-
# OpenSearch 配置(根据项目需求)
|
| 91 |
-
OPENSEARCH_CONFIG = {
|
| 92 |
-
'host': os.environ.get('OPENSEARCH_HOST', '192.168.3.33'),
|
| 93 |
-
'port': int(os.environ.get('OPENSEARCH_PORT', 9200)),
|
| 94 |
-
'use_ssl': os.environ.get('OPENSEARCH_USE_SSL', 'False').lower() == 'true',
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
# 全局变量:存储学习数据
|
| 98 |
-
BOOK_DATA = None
|
| 99 |
-
|
| 100 |
-
# 数据库实例
|
| 101 |
-
DB = None
|
| 102 |
-
|
| 103 |
-
def get_client_ip():
|
| 104 |
-
"""
|
| 105 |
-
获取客户端真实 IP 地址
|
| 106 |
-
在代理服务器(如 Hugging Face Spaces)后面时,需要检查代理头部
|
| 107 |
-
"""
|
| 108 |
-
# 按优先级检查各种代理头部
|
| 109 |
-
headers_to_check = [
|
| 110 |
-
'X-Forwarded-For',
|
| 111 |
-
'X-Real-IP',
|
| 112 |
-
'CF-Connecting-IP', # Cloudflare
|
| 113 |
-
'True-Client-IP',
|
| 114 |
-
'X-Client-IP',
|
| 115 |
-
]
|
| 116 |
-
|
| 117 |
-
for header in headers_to_check:
|
| 118 |
-
ip = request.headers.get(header)
|
| 119 |
-
if ip:
|
| 120 |
-
# X-Forwarded-For 可能包含多个 IP,取第一个
|
| 121 |
-
return ip.split(',')[0].strip()
|
| 122 |
-
|
| 123 |
-
# 如果没有代理头部,使用 remote_addr
|
| 124 |
-
return request.remote_addr or 'Unknown'
|
| 125 |
-
|
| 126 |
-
def load_book_data():
|
| 127 |
-
"""
|
| 128 |
-
加载书籍数据(已废弃,保留是为了兼容性)
|
| 129 |
-
现在使用数据库接口,不再加载JSON文件
|
| 130 |
-
"""
|
| 131 |
-
global BOOK_DATA
|
| 132 |
-
app.logger.info('ℹ️ load_book_data 已废弃,使用数据库接口')
|
| 133 |
-
# 设置为空字典表示已初始化,但不再使用
|
| 134 |
-
BOOK_DATA = {}
|
| 135 |
-
return True
|
| 136 |
-
|
| 137 |
-
def init_database():
|
| 138 |
-
"""初始化数据库连接"""
|
| 139 |
-
global DB
|
| 140 |
-
try:
|
| 141 |
-
DB = get_db_instance('books.db')
|
| 142 |
-
app.logger.info('✅ 数据库连接初始化成功')
|
| 143 |
-
return True
|
| 144 |
-
except Exception as e:
|
| 145 |
-
app.logger.error(f'❌ 数据库初始化失败: {e}')
|
| 146 |
-
return False
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
# ============================================================================
|
| 150 |
-
# 路由:静态文件服务
|
| 151 |
-
# ============================================================================
|
| 152 |
-
|
| 153 |
-
@app.route('/')
|
| 154 |
-
def index():
|
| 155 |
-
"""主页 - 书籍目录"""
|
| 156 |
-
return send_from_directory('.', 'index.html')
|
| 157 |
-
|
| 158 |
-
@app.route('/reader')
|
| 159 |
-
def reader():
|
| 160 |
-
"""阅读页面"""
|
| 161 |
-
return send_from_directory('.', 'reader.html')
|
| 162 |
-
|
| 163 |
-
@app.route('/<path:filename>')
|
| 164 |
-
def serve_static(filename):
|
| 165 |
-
"""提供根目录下的静态文件(如 style.css, script.js)"""
|
| 166 |
-
# 避免与API路由冲突
|
| 167 |
-
if filename.startswith('api/'):
|
| 168 |
-
return jsonify({'error': '接口不存在'}), 404
|
| 169 |
-
return send_from_directory('.', filename)
|
| 170 |
-
|
| 171 |
-
# ============================================================================
|
| 172 |
-
# 路由:API 端点
|
| 173 |
-
# ============================================================================
|
| 174 |
-
|
| 175 |
-
@app.route('/api/health')
|
| 176 |
-
def health_check():
|
| 177 |
-
"""健康检查端点"""
|
| 178 |
-
return jsonify({
|
| 179 |
-
'status': 'healthy',
|
| 180 |
-
'timestamp': datetime.now().isoformat(),
|
| 181 |
-
'version': '2.0.0-flask'
|
| 182 |
-
})
|
| 183 |
-
|
| 184 |
-
@app.route('/api/book/info')
|
| 185 |
-
def get_book_info():
|
| 186 |
-
"""
|
| 187 |
-
获取书籍信息(旧接口,保留兼容性)
|
| 188 |
-
推荐使用: /api/v2/books/<book_id>
|
| 189 |
-
"""
|
| 190 |
-
try:
|
| 191 |
-
if not DB:
|
| 192 |
-
return jsonify({'error': '数据库未初始化'}), 500
|
| 193 |
-
|
| 194 |
-
# 默认获取第一本书的信息(为了兼容旧版本)
|
| 195 |
-
books = DB.get_all_books()
|
| 196 |
-
if not books:
|
| 197 |
-
return jsonify({'error': '没有找到书籍数据'}), 404
|
| 198 |
-
|
| 199 |
-
# 使用最后导入的书籍
|
| 200 |
-
book = books[-1]
|
| 201 |
-
|
| 202 |
-
# 返回书籍元信息
|
| 203 |
-
info = {
|
| 204 |
-
'book_id': book['market_book_id'],
|
| 205 |
-
'total_pages': book['max_page'],
|
| 206 |
-
'title': book['market_book_name'],
|
| 207 |
-
'loaded': True
|
| 208 |
-
}
|
| 209 |
-
return jsonify(info)
|
| 210 |
-
except Exception as e:
|
| 211 |
-
app.logger.error(f"获取书籍信息失败: {e}")
|
| 212 |
-
return jsonify({'error': str(e)}), 500
|
| 213 |
-
|
| 214 |
-
@app.route('/api/book/page/<int:page_num>')
|
| 215 |
-
def get_page_content(page_num):
|
| 216 |
-
"""
|
| 217 |
-
获取指定页面内容(旧接口,保留兼容性)
|
| 218 |
-
推荐使用: /api/v2/books/<book_id>/pages/<page_num>
|
| 219 |
-
"""
|
| 220 |
-
try:
|
| 221 |
-
if not DB:
|
| 222 |
-
return jsonify({'error': '数据库未初始化'}), 500
|
| 223 |
-
|
| 224 |
-
# 获取最后导入的书籍
|
| 225 |
-
books = DB.get_all_books()
|
| 226 |
-
if not books:
|
| 227 |
-
return jsonify({'error': '没有找到书籍数据'}), 404
|
| 228 |
-
|
| 229 |
-
book = books[-1]
|
| 230 |
-
book_id = book['market_book_id']
|
| 231 |
-
|
| 232 |
-
# 获取页面内容
|
| 233 |
-
page = DB.get_page_content(book_id, page_num)
|
| 234 |
-
if not page:
|
| 235 |
-
return jsonify({'error': '页码超出范围'}), 404
|
| 236 |
-
|
| 237 |
-
return jsonify({
|
| 238 |
-
'page_num': page_num,
|
| 239 |
-
'content': page,
|
| 240 |
-
'total_pages': book['max_page']
|
| 241 |
-
})
|
| 242 |
-
except Exception as e:
|
| 243 |
-
app.logger.error(f"获取页面内容失败: {e}")
|
| 244 |
-
return jsonify({'error': str(e)}), 500
|
| 245 |
-
|
| 246 |
-
@app.route('/api/progress/save', methods=['POST'])
|
| 247 |
-
def save_progress():
|
| 248 |
-
"""保存学习进度"""
|
| 249 |
-
try:
|
| 250 |
-
data = request.get_json()
|
| 251 |
-
|
| 252 |
-
# 这里可以保存到数据库,现在先存到 session
|
| 253 |
-
if 'progress' not in session:
|
| 254 |
-
session['progress'] = {}
|
| 255 |
-
|
| 256 |
-
session['progress'].update({
|
| 257 |
-
'current_page': data.get('current_page', 0),
|
| 258 |
-
'bookmarks': data.get('bookmarks', []),
|
| 259 |
-
'settings': data.get('settings', {}),
|
| 260 |
-
'last_updated': datetime.now().isoformat()
|
| 261 |
-
})
|
| 262 |
-
|
| 263 |
-
app.logger.info(f"保存学习进度: 第 {data.get('current_page')} 页")
|
| 264 |
-
|
| 265 |
-
return jsonify({
|
| 266 |
-
'success': True,
|
| 267 |
-
'message': '学习进度已保存'
|
| 268 |
-
})
|
| 269 |
-
except Exception as e:
|
| 270 |
-
app.logger.error(f"保存进度失败: {e}")
|
| 271 |
-
return jsonify({
|
| 272 |
-
'success': False,
|
| 273 |
-
'error': str(e)
|
| 274 |
-
}), 500
|
| 275 |
-
|
| 276 |
-
@app.route('/api/progress/load', methods=['GET'])
|
| 277 |
-
def load_progress():
|
| 278 |
-
"""加载学习进度"""
|
| 279 |
-
progress = session.get('progress', {
|
| 280 |
-
'current_page': 0,
|
| 281 |
-
'bookmarks': [],
|
| 282 |
-
'settings': {}
|
| 283 |
-
})
|
| 284 |
-
return jsonify(progress)
|
| 285 |
-
|
| 286 |
-
@app.route('/api/search', methods=['POST'])
|
| 287 |
-
def search_content():
|
| 288 |
-
"""搜索内容(未来可集成 OpenSearch)"""
|
| 289 |
-
try:
|
| 290 |
-
data = request.get_json()
|
| 291 |
-
keyword = data.get('keyword', '').strip()
|
| 292 |
-
|
| 293 |
-
if not keyword:
|
| 294 |
-
return jsonify({'error': '搜索关键词不能为空'}), 400
|
| 295 |
-
|
| 296 |
-
# 简单搜索实现(在书籍数据中搜索)
|
| 297 |
-
if not BOOK_DATA:
|
| 298 |
-
return jsonify({'error': '书籍数据未加载'}), 500
|
| 299 |
-
|
| 300 |
-
results = []
|
| 301 |
-
pages = BOOK_DATA.get('pages', [])
|
| 302 |
-
|
| 303 |
-
for page_num, page_content in enumerate(pages):
|
| 304 |
-
# 在页面内容中搜索关键词
|
| 305 |
-
page_str = json.dumps(page_content, ensure_ascii=False)
|
| 306 |
-
if keyword.lower() in page_str.lower():
|
| 307 |
-
results.append({
|
| 308 |
-
'page_num': page_num,
|
| 309 |
-
'preview': str(page_content)[:200] + '...'
|
| 310 |
-
})
|
| 311 |
-
|
| 312 |
-
app.logger.info(f"搜索 '{keyword}': 找到 {len(results)} 个结果")
|
| 313 |
-
|
| 314 |
-
return jsonify({
|
| 315 |
-
'keyword': keyword,
|
| 316 |
-
'total': len(results),
|
| 317 |
-
'results': results[:20] # 限制返回前20个结果
|
| 318 |
-
})
|
| 319 |
-
except Exception as e:
|
| 320 |
-
app.logger.error(f"搜索失败: {e}")
|
| 321 |
-
return jsonify({
|
| 322 |
-
'success': False,
|
| 323 |
-
'error': str(e)
|
| 324 |
-
}), 500
|
| 325 |
-
|
| 326 |
-
@app.route('/api/opensearch/status')
|
| 327 |
-
def opensearch_status():
|
| 328 |
-
"""检查 OpenSearch 连接状态(示例)"""
|
| 329 |
-
try:
|
| 330 |
-
# 这里可以实际连接 OpenSearch
|
| 331 |
-
# from opensearchpy import OpenSearch
|
| 332 |
-
# client = OpenSearch([{'host': OPENSEARCH_CONFIG['host'], 'port': OPENSEARCH_CONFIG['port']}])
|
| 333 |
-
# info = client.info()
|
| 334 |
-
|
| 335 |
-
return jsonify({
|
| 336 |
-
'configured': True,
|
| 337 |
-
'host': OPENSEARCH_CONFIG['host'],
|
| 338 |
-
'port': OPENSEARCH_CONFIG['port'],
|
| 339 |
-
'status': 'not_implemented',
|
| 340 |
-
'message': 'OpenSearch 集成待实现'
|
| 341 |
-
})
|
| 342 |
-
except Exception as e:
|
| 343 |
-
return jsonify({
|
| 344 |
-
'configured': False,
|
| 345 |
-
'error': str(e)
|
| 346 |
-
}), 500
|
| 347 |
-
|
| 348 |
-
@app.route('/api/stats')
|
| 349 |
-
def get_stats():
|
| 350 |
-
"""获取学习统计数据"""
|
| 351 |
-
# 从 session 或数据库获取统计数据
|
| 352 |
-
progress = session.get('progress', {})
|
| 353 |
-
|
| 354 |
-
return jsonify({
|
| 355 |
-
'current_page': progress.get('current_page', 0),
|
| 356 |
-
'total_bookmarks': len(progress.get('bookmarks', [])),
|
| 357 |
-
'last_visit': progress.get('last_updated', None),
|
| 358 |
-
'total_pages': len(BOOK_DATA.get('pages', [])) if BOOK_DATA else 0
|
| 359 |
-
})
|
| 360 |
-
|
| 361 |
-
# ============================================================================
|
| 362 |
-
# 路由:新版API端点(基于SQLite数据库)
|
| 363 |
-
# ============================================================================
|
| 364 |
-
|
| 365 |
-
@app.route('/api/v2/books')
|
| 366 |
-
def get_books():
|
| 367 |
-
"""
|
| 368 |
-
获取所有书籍列表
|
| 369 |
-
|
| 370 |
-
Query Parameters:
|
| 371 |
-
grade_id: 年级ID(可选)
|
| 372 |
-
|
| 373 |
-
Returns:
|
| 374 |
-
{
|
| 375 |
-
"success": true,
|
| 376 |
-
"count": 10,
|
| 377 |
-
"books": [
|
| 378 |
-
{
|
| 379 |
-
"market_book_id": 168,
|
| 380 |
-
"market_book_name": "一年级上册",
|
| 381 |
-
"market_book_cover": "168_一年级上册/images/page_001.jpg",
|
| 382 |
-
"max_page": 73,
|
| 383 |
-
"market_classify_name": "沪教版(深圳)",
|
| 384 |
-
"grade_id": 40,
|
| 385 |
-
"reel_id": 1
|
| 386 |
-
}
|
| 387 |
-
]
|
| 388 |
-
}
|
| 389 |
-
"""
|
| 390 |
-
try:
|
| 391 |
-
if not DB:
|
| 392 |
-
return jsonify({
|
| 393 |
-
'success': False,
|
| 394 |
-
'error': '数据库未初始化'
|
| 395 |
-
}), 500
|
| 396 |
-
|
| 397 |
-
# 检查是否有年级筛选
|
| 398 |
-
grade_id = request.args.get('grade_id', type=int)
|
| 399 |
-
|
| 400 |
-
if grade_id:
|
| 401 |
-
books = DB.get_books_by_grade(grade_id)
|
| 402 |
-
else:
|
| 403 |
-
books = DB.get_all_books()
|
| 404 |
-
|
| 405 |
-
app.logger.info(f"获取书籍列表: {len(books)} 本书")
|
| 406 |
-
|
| 407 |
-
return jsonify({
|
| 408 |
-
'success': True,
|
| 409 |
-
'count': len(books),
|
| 410 |
-
'books': books
|
| 411 |
-
})
|
| 412 |
-
except Exception as e:
|
| 413 |
-
app.logger.error(f"获取书籍列表失败: {e}")
|
| 414 |
-
return jsonify({
|
| 415 |
-
'success': False,
|
| 416 |
-
'error': str(e)
|
| 417 |
-
}), 500
|
| 418 |
-
|
| 419 |
-
@app.route('/api/v2/books/<int:book_id>')
|
| 420 |
-
def get_book_info_v2(book_id):
|
| 421 |
-
"""
|
| 422 |
-
获取指定书籍的详细信息
|
| 423 |
-
|
| 424 |
-
Args:
|
| 425 |
-
book_id: 书籍ID
|
| 426 |
-
|
| 427 |
-
Returns:
|
| 428 |
-
{
|
| 429 |
-
"success": true,
|
| 430 |
-
"book": {
|
| 431 |
-
"book_id": 1,
|
| 432 |
-
"book_name": "书籍名称",
|
| 433 |
-
"total_pages": 100,
|
| 434 |
-
...
|
| 435 |
-
}
|
| 436 |
-
}
|
| 437 |
-
"""
|
| 438 |
-
try:
|
| 439 |
-
if not DB:
|
| 440 |
-
return jsonify({
|
| 441 |
-
'success': False,
|
| 442 |
-
'error': '数据库未初始化'
|
| 443 |
-
}), 500
|
| 444 |
-
|
| 445 |
-
book = DB.get_book_by_id(book_id)
|
| 446 |
-
if not book:
|
| 447 |
-
return jsonify({
|
| 448 |
-
'success': False,
|
| 449 |
-
'error': '书籍不存在'
|
| 450 |
-
}), 404
|
| 451 |
-
|
| 452 |
-
app.logger.info(f"获取书籍信息: ID={book_id}")
|
| 453 |
-
|
| 454 |
-
return jsonify({
|
| 455 |
-
'success': True,
|
| 456 |
-
'book': book
|
| 457 |
-
})
|
| 458 |
-
except Exception as e:
|
| 459 |
-
app.logger.error(f"获取书籍信息失败: {e}")
|
| 460 |
-
return jsonify({
|
| 461 |
-
'success': False,
|
| 462 |
-
'error': str(e)
|
| 463 |
-
}), 500
|
| 464 |
-
|
| 465 |
-
@app.route('/api/v2/books/<int:book_id>/pages')
|
| 466 |
-
def get_book_pages(book_id):
|
| 467 |
-
"""
|
| 468 |
-
获取书籍的所有页面列表(不含片段内容)
|
| 469 |
-
|
| 470 |
-
Args:
|
| 471 |
-
book_id: 书籍ID
|
| 472 |
-
|
| 473 |
-
Returns:
|
| 474 |
-
{
|
| 475 |
-
"success": true,
|
| 476 |
-
"book_id": 168,
|
| 477 |
-
"book_name": "一年级上册",
|
| 478 |
-
"total_pages": 73,
|
| 479 |
-
"pages": [
|
| 480 |
-
{
|
| 481 |
-
"page_id": 2111,
|
| 482 |
-
"page_number": 1,
|
| 483 |
-
"origin_img_url": "...",
|
| 484 |
-
"encrypt_img_url": "..."
|
| 485 |
-
}
|
| 486 |
-
]
|
| 487 |
-
}
|
| 488 |
-
"""
|
| 489 |
-
try:
|
| 490 |
-
if not DB:
|
| 491 |
-
return jsonify({
|
| 492 |
-
'success': False,
|
| 493 |
-
'error': '数据库未初始化'
|
| 494 |
-
}), 500
|
| 495 |
-
|
| 496 |
-
# 检查书籍是否存在
|
| 497 |
-
book = DB.get_book_by_id(book_id)
|
| 498 |
-
if not book:
|
| 499 |
-
return jsonify({
|
| 500 |
-
'success': False,
|
| 501 |
-
'error': '书籍不存在'
|
| 502 |
-
}), 404
|
| 503 |
-
|
| 504 |
-
# 获取页面列表
|
| 505 |
-
pages = DB.get_book_pages(book_id)
|
| 506 |
-
|
| 507 |
-
app.logger.info(f"获取书籍页面列表: 书籍ID={book_id}, 页数={len(pages)}")
|
| 508 |
-
|
| 509 |
-
return jsonify({
|
| 510 |
-
'success': True,
|
| 511 |
-
'book_id': book_id,
|
| 512 |
-
'book_name': book['market_book_name'],
|
| 513 |
-
'total_pages': len(pages),
|
| 514 |
-
'pages': pages
|
| 515 |
-
})
|
| 516 |
-
except Exception as e:
|
| 517 |
-
app.logger.error(f"获取书籍页面列表失败: {e}")
|
| 518 |
-
return jsonify({
|
| 519 |
-
'success': False,
|
| 520 |
-
'error': str(e)
|
| 521 |
-
}), 500
|
| 522 |
-
|
| 523 |
-
@app.route('/api/v2/books/<int:book_id>/catalog')
|
| 524 |
-
def get_book_catalog(book_id):
|
| 525 |
-
"""
|
| 526 |
-
获取书籍目录结构(章节目录)
|
| 527 |
-
|
| 528 |
-
Args:
|
| 529 |
-
book_id: 书籍ID
|
| 530 |
-
|
| 531 |
-
Returns:
|
| 532 |
-
{
|
| 533 |
-
"success": true,
|
| 534 |
-
"book_id": 168,
|
| 535 |
-
"book_name": "一年级上册",
|
| 536 |
-
"catalog": [
|
| 537 |
-
{
|
| 538 |
-
"catalog_id": 1,
|
| 539 |
-
"catalog_name": "Unit 1 Hello",
|
| 540 |
-
"catalog_name_cn": "第一单元 你好",
|
| 541 |
-
"start_page": 2,
|
| 542 |
-
"end_page": 10,
|
| 543 |
-
"thumbnail": "...",
|
| 544 |
-
"children": []
|
| 545 |
-
}
|
| 546 |
-
]
|
| 547 |
-
}
|
| 548 |
-
"""
|
| 549 |
-
try:
|
| 550 |
-
if not DB:
|
| 551 |
-
return jsonify({
|
| 552 |
-
'success': False,
|
| 553 |
-
'error': '数据库未初始化'
|
| 554 |
-
}), 500
|
| 555 |
-
|
| 556 |
-
# 检查书籍是否存在
|
| 557 |
-
book = DB.get_book_by_id(book_id)
|
| 558 |
-
if not book:
|
| 559 |
-
return jsonify({
|
| 560 |
-
'success': False,
|
| 561 |
-
'error': '书籍不存在'
|
| 562 |
-
}), 404
|
| 563 |
-
|
| 564 |
-
# 获取目录
|
| 565 |
-
catalog = DB.get_book_catalog(book_id)
|
| 566 |
-
|
| 567 |
-
app.logger.info(f"获取书籍目录: 书籍ID={book_id}, 目录项={len(catalog)}")
|
| 568 |
-
|
| 569 |
-
return jsonify({
|
| 570 |
-
'success': True,
|
| 571 |
-
'book_id': book_id,
|
| 572 |
-
'book_name': book['market_book_name'],
|
| 573 |
-
'catalog': catalog
|
| 574 |
-
})
|
| 575 |
-
except Exception as e:
|
| 576 |
-
app.logger.error(f"获取书籍目录失败: {e}")
|
| 577 |
-
return jsonify({
|
| 578 |
-
'success': False,
|
| 579 |
-
'error': str(e)
|
| 580 |
-
}), 500
|
| 581 |
-
|
| 582 |
-
@app.route('/api/v2/books/<int:book_id>/pages/<int:page_num>')
|
| 583 |
-
def get_page_content_v2(book_id, page_num):
|
| 584 |
-
"""
|
| 585 |
-
获取指定页面的完整内容
|
| 586 |
-
|
| 587 |
-
Args:
|
| 588 |
-
book_id: 书籍ID
|
| 589 |
-
page_num: 页码
|
| 590 |
-
|
| 591 |
-
Returns:
|
| 592 |
-
{
|
| 593 |
-
"success": true,
|
| 594 |
-
"book_id": 1,
|
| 595 |
-
"page": {
|
| 596 |
-
"page_id": 1001,
|
| 597 |
-
"page_number": 1,
|
| 598 |
-
"origin_img_url": "https://...",
|
| 599 |
-
"pieces": [
|
| 600 |
-
{
|
| 601 |
-
"piece_id": 10001,
|
| 602 |
-
"original": "Hello",
|
| 603 |
-
"translation": "你好",
|
| 604 |
-
"origin_sound_url": "https://...",
|
| 605 |
-
...
|
| 606 |
-
}
|
| 607 |
-
],
|
| 608 |
-
"piece_count": 10
|
| 609 |
-
}
|
| 610 |
-
}
|
| 611 |
-
"""
|
| 612 |
-
try:
|
| 613 |
-
if not DB:
|
| 614 |
-
return jsonify({
|
| 615 |
-
'success': False,
|
| 616 |
-
'error': '数据库未初始化'
|
| 617 |
-
}), 500
|
| 618 |
-
|
| 619 |
-
# 检查书籍是否存在
|
| 620 |
-
book = DB.get_book_by_id(book_id)
|
| 621 |
-
if not book:
|
| 622 |
-
return jsonify({
|
| 623 |
-
'success': False,
|
| 624 |
-
'error': '书籍不存在'
|
| 625 |
-
}), 404
|
| 626 |
-
|
| 627 |
-
# 获取页面内容
|
| 628 |
-
page = DB.get_page_content(book_id, page_num)
|
| 629 |
-
if not page:
|
| 630 |
-
return jsonify({
|
| 631 |
-
'success': False,
|
| 632 |
-
'error': f'页码 {page_num} 不存在'
|
| 633 |
-
}), 404
|
| 634 |
-
|
| 635 |
-
app.logger.info(f"获取页面内容: 书籍ID={book_id}, 页码={page_num}")
|
| 636 |
-
|
| 637 |
-
return jsonify({
|
| 638 |
-
'success': True,
|
| 639 |
-
'book_id': book_id,
|
| 640 |
-
'book_name': book['market_book_name'],
|
| 641 |
-
'page': page
|
| 642 |
-
})
|
| 643 |
-
except Exception as e:
|
| 644 |
-
app.logger.error(f"获取页面内容失败: {e}")
|
| 645 |
-
return jsonify({
|
| 646 |
-
'success': False,
|
| 647 |
-
'error': str(e)
|
| 648 |
-
}), 500
|
| 649 |
-
|
| 650 |
-
@app.route('/api/v2/books/<int:book_id>/search')
|
| 651 |
-
def search_book_content(book_id):
|
| 652 |
-
"""
|
| 653 |
-
在书籍中搜索内容
|
| 654 |
-
|
| 655 |
-
Query Parameters:
|
| 656 |
-
keyword: 搜索关键词
|
| 657 |
-
limit: ��回结果数量(默认20)
|
| 658 |
-
|
| 659 |
-
Returns:
|
| 660 |
-
{
|
| 661 |
-
"success": true,
|
| 662 |
-
"book_id": 168,
|
| 663 |
-
"keyword": "hello",
|
| 664 |
-
"count": 5,
|
| 665 |
-
"results": [
|
| 666 |
-
{
|
| 667 |
-
"page_number": 2,
|
| 668 |
-
"piece_id": 26342,
|
| 669 |
-
"original": "Hello",
|
| 670 |
-
"translation": "你好",
|
| 671 |
-
"origin_sound_url": "..."
|
| 672 |
-
}
|
| 673 |
-
]
|
| 674 |
-
}
|
| 675 |
-
"""
|
| 676 |
-
try:
|
| 677 |
-
if not DB:
|
| 678 |
-
return jsonify({
|
| 679 |
-
'success': False,
|
| 680 |
-
'error': '数据库未初始化'
|
| 681 |
-
}), 500
|
| 682 |
-
|
| 683 |
-
keyword = request.args.get('keyword', '').strip()
|
| 684 |
-
if not keyword:
|
| 685 |
-
return jsonify({
|
| 686 |
-
'success': False,
|
| 687 |
-
'error': '请提供搜索关键词'
|
| 688 |
-
}), 400
|
| 689 |
-
|
| 690 |
-
limit = request.args.get('limit', 20, type=int)
|
| 691 |
-
|
| 692 |
-
# 检查书籍是否存在
|
| 693 |
-
book = DB.get_book_by_id(book_id)
|
| 694 |
-
if not book:
|
| 695 |
-
return jsonify({
|
| 696 |
-
'success': False,
|
| 697 |
-
'error': '书籍不存在'
|
| 698 |
-
}), 404
|
| 699 |
-
|
| 700 |
-
# 搜索
|
| 701 |
-
results = DB.search_content(book_id, keyword, limit)
|
| 702 |
-
|
| 703 |
-
app.logger.info(f"搜索内容: 书籍ID={book_id}, 关键词={keyword}, 结果数={len(results)}")
|
| 704 |
-
|
| 705 |
-
return jsonify({
|
| 706 |
-
'success': True,
|
| 707 |
-
'book_id': book_id,
|
| 708 |
-
'book_name': book['market_book_name'],
|
| 709 |
-
'keyword': keyword,
|
| 710 |
-
'count': len(results),
|
| 711 |
-
'results': results
|
| 712 |
-
})
|
| 713 |
-
except Exception as e:
|
| 714 |
-
app.logger.error(f"搜索失败: {e}")
|
| 715 |
-
return jsonify({
|
| 716 |
-
'success': False,
|
| 717 |
-
'error': str(e)
|
| 718 |
-
}), 500
|
| 719 |
-
|
| 720 |
-
@app.route('/api/v2/search')
|
| 721 |
-
def search_all_content():
|
| 722 |
-
"""
|
| 723 |
-
在所有书籍中搜索内容
|
| 724 |
-
|
| 725 |
-
Query Parameters:
|
| 726 |
-
keyword: 搜索关键词
|
| 727 |
-
limit: 返回结果数量(默认50)
|
| 728 |
-
|
| 729 |
-
Returns:
|
| 730 |
-
{
|
| 731 |
-
"success": true,
|
| 732 |
-
"keyword": "hello",
|
| 733 |
-
"count": 15,
|
| 734 |
-
"results": [
|
| 735 |
-
{
|
| 736 |
-
"market_book_id": 168,
|
| 737 |
-
"market_book_name": "一年级上册",
|
| 738 |
-
"page_number": 2,
|
| 739 |
-
"piece_id": 26342,
|
| 740 |
-
"original": "Hello",
|
| 741 |
-
"translation": "你好",
|
| 742 |
-
"origin_sound_url": "..."
|
| 743 |
-
}
|
| 744 |
-
]
|
| 745 |
-
}
|
| 746 |
-
"""
|
| 747 |
-
try:
|
| 748 |
-
if not DB:
|
| 749 |
-
return jsonify({
|
| 750 |
-
'success': False,
|
| 751 |
-
'error': '数据库未初始化'
|
| 752 |
-
}), 500
|
| 753 |
-
|
| 754 |
-
keyword = request.args.get('keyword', '').strip()
|
| 755 |
-
if not keyword:
|
| 756 |
-
return jsonify({
|
| 757 |
-
'success': False,
|
| 758 |
-
'error': '请提供搜索关键词'
|
| 759 |
-
}), 400
|
| 760 |
-
|
| 761 |
-
limit = request.args.get('limit', 50, type=int)
|
| 762 |
-
|
| 763 |
-
# 搜索所有书籍
|
| 764 |
-
results = DB.search_all_books(keyword, limit)
|
| 765 |
-
|
| 766 |
-
app.logger.info(f"全局搜索: 关键词={keyword}, 结果数={len(results)}")
|
| 767 |
-
|
| 768 |
-
return jsonify({
|
| 769 |
-
'success': True,
|
| 770 |
-
'keyword': keyword,
|
| 771 |
-
'count': len(results),
|
| 772 |
-
'results': results
|
| 773 |
-
})
|
| 774 |
-
except Exception as e:
|
| 775 |
-
app.logger.error(f"搜索失败: {e}")
|
| 776 |
-
return jsonify({
|
| 777 |
-
'success': False,
|
| 778 |
-
'error': str(e)
|
| 779 |
-
}), 500
|
| 780 |
-
|
| 781 |
-
@app.route('/api/v2/books/<int:book_id>/statistics')
|
| 782 |
-
def get_book_statistics(book_id):
|
| 783 |
-
"""
|
| 784 |
-
获取书籍统计信息
|
| 785 |
-
|
| 786 |
-
Args:
|
| 787 |
-
book_id: 书籍ID
|
| 788 |
-
|
| 789 |
-
Returns:
|
| 790 |
-
{
|
| 791 |
-
"success": true,
|
| 792 |
-
"statistics": {
|
| 793 |
-
"book_id": 168,
|
| 794 |
-
"total_pages": 73,
|
| 795 |
-
"total_pieces": 500,
|
| 796 |
-
"total_audio": 450,
|
| 797 |
-
"total_catalogs": 12
|
| 798 |
-
}
|
| 799 |
-
}
|
| 800 |
-
"""
|
| 801 |
-
try:
|
| 802 |
-
if not DB:
|
| 803 |
-
return jsonify({
|
| 804 |
-
'success': False,
|
| 805 |
-
'error': '数据库未初始化'
|
| 806 |
-
}), 500
|
| 807 |
-
|
| 808 |
-
# 检查书籍是否存在
|
| 809 |
-
book = DB.get_book_by_id(book_id)
|
| 810 |
-
if not book:
|
| 811 |
-
return jsonify({
|
| 812 |
-
'success': False,
|
| 813 |
-
'error': '书籍不存在'
|
| 814 |
-
}), 404
|
| 815 |
-
|
| 816 |
-
# 获取统计信息
|
| 817 |
-
stats = DB.get_book_statistics(book_id)
|
| 818 |
-
|
| 819 |
-
app.logger.info(f"获取统计信息: 书籍ID={book_id}")
|
| 820 |
-
|
| 821 |
-
return jsonify({
|
| 822 |
-
'success': True,
|
| 823 |
-
'statistics': stats
|
| 824 |
-
})
|
| 825 |
-
except Exception as e:
|
| 826 |
-
app.logger.error(f"获取统计信息失败: {e}")
|
| 827 |
-
return jsonify({
|
| 828 |
-
'success': False,
|
| 829 |
-
'error': str(e)
|
| 830 |
-
}), 500
|
| 831 |
-
|
| 832 |
-
@app.route('/api/v2/statistics')
|
| 833 |
-
def get_overall_statistics():
|
| 834 |
-
"""
|
| 835 |
-
获取整体统计信息
|
| 836 |
-
|
| 837 |
-
Returns:
|
| 838 |
-
{
|
| 839 |
-
"success": true,
|
| 840 |
-
"statistics": {
|
| 841 |
-
"total_books": 30,
|
| 842 |
-
"total_pages": 2000,
|
| 843 |
-
"total_pieces": 50000,
|
| 844 |
-
"total_catalogs": 360
|
| 845 |
-
}
|
| 846 |
-
}
|
| 847 |
-
"""
|
| 848 |
-
try:
|
| 849 |
-
if not DB:
|
| 850 |
-
return jsonify({
|
| 851 |
-
'success': False,
|
| 852 |
-
'error': '数据库未初始化'
|
| 853 |
-
}), 500
|
| 854 |
-
|
| 855 |
-
stats = DB.get_overall_statistics()
|
| 856 |
-
|
| 857 |
-
app.logger.info("获取整体统计信息")
|
| 858 |
-
|
| 859 |
-
return jsonify({
|
| 860 |
-
'success': True,
|
| 861 |
-
'statistics': stats
|
| 862 |
-
})
|
| 863 |
-
except Exception as e:
|
| 864 |
-
app.logger.error(f"获取统计信息失败: {e}")
|
| 865 |
-
return jsonify({
|
| 866 |
-
'success': False,
|
| 867 |
-
'error': str(e)
|
| 868 |
-
}), 500
|
| 869 |
-
|
| 870 |
-
# ============================================================================
|
| 871 |
-
# 错误处理
|
| 872 |
-
# ============================================================================
|
| 873 |
-
|
| 874 |
-
@app.errorhandler(404)
|
| 875 |
-
def not_found(error):
|
| 876 |
-
"""404错误处理"""
|
| 877 |
-
if request.path.startswith('/api/'):
|
| 878 |
-
return jsonify({'error': '接口不存在'}), 404
|
| 879 |
-
return send_from_directory('.', 'index.html')
|
| 880 |
-
|
| 881 |
-
@app.errorhandler(500)
|
| 882 |
-
def internal_error(error):
|
| 883 |
-
"""500错误处理"""
|
| 884 |
-
app.logger.error(f'服务器错误: {error}')
|
| 885 |
-
return jsonify({'error': '服务器内部错误'}), 500
|
| 886 |
-
|
| 887 |
-
# ============================================================================
|
| 888 |
-
# 请求钩子
|
| 889 |
-
# ============================================================================
|
| 890 |
-
|
| 891 |
-
@app.before_request
|
| 892 |
-
def before_request():
|
| 893 |
-
"""请求前处理"""
|
| 894 |
-
# 获取客户端真实 IP
|
| 895 |
-
client_ip = get_client_ip()
|
| 896 |
-
|
| 897 |
-
# 记录所有请求信息(包括静态文件)
|
| 898 |
-
user_agent = request.headers.get('User-Agent', 'Unknown')[:100] # 限制长度
|
| 899 |
-
|
| 900 |
-
# API 请求记录详细信息
|
| 901 |
-
if request.path.startswith('/api/'):
|
| 902 |
-
app.logger.info(
|
| 903 |
-
f"[{client_ip}] {request.method} {request.path} "
|
| 904 |
-
f"| UA: {user_agent}"
|
| 905 |
-
)
|
| 906 |
-
# 静态资源只记录简要信息
|
| 907 |
-
elif app.debug:
|
| 908 |
-
app.logger.debug(f"[{client_ip}] {request.method} {request.path}")
|
| 909 |
-
|
| 910 |
-
# 将 IP 存储到 g 对象,方便在其他地方使用
|
| 911 |
-
from flask import g
|
| 912 |
-
g.client_ip = client_ip
|
| 913 |
-
|
| 914 |
-
@app.after_request
|
| 915 |
-
def after_request(response):
|
| 916 |
-
"""请求后处理 - 添加缓存控制"""
|
| 917 |
-
# API 端点不缓存
|
| 918 |
-
if request.path.startswith('/api/'):
|
| 919 |
-
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
| 920 |
-
response.headers['Pragma'] = 'no-cache'
|
| 921 |
-
response.headers['Expires'] = '0'
|
| 922 |
-
else:
|
| 923 |
-
# 静态资源缓存1小时
|
| 924 |
-
response.headers['Cache-Control'] = 'public, max-age=3600'
|
| 925 |
-
|
| 926 |
-
return response
|
| 927 |
-
|
| 928 |
-
# ============================================================================
|
| 929 |
-
# 应用初始化和启动
|
| 930 |
-
# ============================================================================
|
| 931 |
-
|
| 932 |
-
def auto_generate_database():
|
| 933 |
-
"""
|
| 934 |
-
自动生成数据库
|
| 935 |
-
如果 books.db 不存在,自动运行导入脚本生成数据库
|
| 936 |
-
"""
|
| 937 |
-
db_path = 'books.db'
|
| 938 |
-
schema_path = 'database/db_schema.sql'
|
| 939 |
-
data_dir = 'data'
|
| 940 |
-
|
| 941 |
-
# 检查必要的文件和目录
|
| 942 |
-
if not os.path.exists(schema_path):
|
| 943 |
-
print(f"❌ 错误: Schema文件 {schema_path} 不存在")
|
| 944 |
-
return False
|
| 945 |
-
|
| 946 |
-
if not os.path.exists(data_dir):
|
| 947 |
-
print(f"❌ 错误: 数据目录 {data_dir} 不存在")
|
| 948 |
-
print(f" 无法自动生成数据库,请先准备数据")
|
| 949 |
-
return False
|
| 950 |
-
|
| 951 |
-
print(f"📦 未找到数据库文件,正在自动生成...")
|
| 952 |
-
print(f" 数据目录: {data_dir}")
|
| 953 |
-
print(f" 这可能需要几分钟时间,请稍候...")
|
| 954 |
-
print("=" * 60)
|
| 955 |
-
|
| 956 |
-
try:
|
| 957 |
-
# 导入必要的函数
|
| 958 |
-
from database.import_book_data import create_database, import_all_books, verify_data
|
| 959 |
-
|
| 960 |
-
# 创建数据库
|
| 961 |
-
conn = create_database(db_path, schema_path)
|
| 962 |
-
|
| 963 |
-
# 导入所有书籍数据
|
| 964 |
-
import_all_books(conn, data_dir)
|
| 965 |
-
|
| 966 |
-
# 验证数据
|
| 967 |
-
verify_data(conn)
|
| 968 |
-
|
| 969 |
-
# 关闭连接
|
| 970 |
-
conn.close()
|
| 971 |
-
|
| 972 |
-
print("=" * 60)
|
| 973 |
-
print("✅ 数据库自动生成完成!")
|
| 974 |
-
print("=" * 60)
|
| 975 |
-
return True
|
| 976 |
-
|
| 977 |
-
except Exception as e:
|
| 978 |
-
print(f"❌ 自动生成数据库失败: {e}")
|
| 979 |
-
app.logger.error(f"自动生成数据库失败: {e}", exc_info=True)
|
| 980 |
-
return False
|
| 981 |
-
|
| 982 |
-
def initialize_app():
|
| 983 |
-
"""初始化应用"""
|
| 984 |
-
print("🚀 交互式英语学习应用 - Flask 版本")
|
| 985 |
-
print("=" * 60)
|
| 986 |
-
|
| 987 |
-
# 设置日志
|
| 988 |
-
setup_logging()
|
| 989 |
-
|
| 990 |
-
# 检查必要文件(暂时不检查 books.db,因为会自动生成)
|
| 991 |
-
if not os.path.exists('index.html'):
|
| 992 |
-
app.logger.error(f"❌ 缺少必要文件: index.html")
|
| 993 |
-
return False
|
| 994 |
-
|
| 995 |
-
# 加载书籍数据
|
| 996 |
-
if not load_book_data():
|
| 997 |
-
return False
|
| 998 |
-
|
| 999 |
-
# 检查数据库文件
|
| 1000 |
-
if not os.path.exists('books.db'):
|
| 1001 |
-
print("ℹ️ 未找到数据库文件 books.db")
|
| 1002 |
-
print("🔄 正在自动生成数据库...")
|
| 1003 |
-
|
| 1004 |
-
# 自动生成数据库
|
| 1005 |
-
if not auto_generate_database():
|
| 1006 |
-
print("❌ 数据库自动生成失败")
|
| 1007 |
-
print(" 请手动运行: python3 database/import_book_data.py")
|
| 1008 |
-
return False
|
| 1009 |
-
|
| 1010 |
-
# 初始化数据库连接
|
| 1011 |
-
print("📚 正在初始化数据库连接...")
|
| 1012 |
-
if not init_database():
|
| 1013 |
-
print("❌ 数据库初始化失败")
|
| 1014 |
-
return False
|
| 1015 |
-
|
| 1016 |
-
print("✅ 应用初始化完成")
|
| 1017 |
-
return True
|
| 1018 |
-
|
| 1019 |
-
def main():
|
| 1020 |
-
"""主函数"""
|
| 1021 |
-
# 初始化应用(如果还未初始化)
|
| 1022 |
-
if not DB:
|
| 1023 |
-
if not initialize_app():
|
| 1024 |
-
print("❌ 应用初始化失败")
|
| 1025 |
-
return 1
|
| 1026 |
-
|
| 1027 |
-
# Hugging Face Spaces 要求监听 7860 端口
|
| 1028 |
-
port = int(os.environ.get('PORT', 7860))
|
| 1029 |
-
debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
|
| 1030 |
-
|
| 1031 |
-
print(f"🌐 监听端口: {port}")
|
| 1032 |
-
print(f"📁 工作目录: {os.getcwd()}")
|
| 1033 |
-
print(f"🔧 调试模式: {'开启' if debug else '关闭'}")
|
| 1034 |
-
print("=" * 60)
|
| 1035 |
-
print("🎉 应用已准备就绪!")
|
| 1036 |
-
print("=" * 60)
|
| 1037 |
-
|
| 1038 |
-
try:
|
| 1039 |
-
# 启动 Flask 应用
|
| 1040 |
-
app.run(
|
| 1041 |
-
host='0.0.0.0',
|
| 1042 |
-
port=port,
|
| 1043 |
-
debug=debug,
|
| 1044 |
-
threaded=True, # 多线程处理请求
|
| 1045 |
-
use_reloader=debug # 开发模式下启用热重载
|
| 1046 |
-
)
|
| 1047 |
-
except KeyboardInterrupt:
|
| 1048 |
-
print("\n\n🛑 服务器已停止")
|
| 1049 |
-
return 0
|
| 1050 |
-
except Exception as e:
|
| 1051 |
-
print(f"❌ 服务器启动失败: {e}")
|
| 1052 |
-
app.logger.error(f"服务器启动失败: {e}")
|
| 1053 |
-
return 1
|
| 1054 |
-
|
| 1055 |
-
# ============================================================================
|
| 1056 |
-
# 应用初始化(支持 Gunicorn 和直接运行)
|
| 1057 |
-
# ============================================================================
|
| 1058 |
-
|
| 1059 |
-
# 在模块加载时初始化应用(无论是 gunicorn 还是直接运行都会执行)
|
| 1060 |
-
# 这确保了在 HuggingFace Spaces 上使用 gunicorn 时也能正确初始化
|
| 1061 |
-
if not DB: # 避免重复初始化
|
| 1062 |
-
print("🔧 开始初始化应用...")
|
| 1063 |
-
if not initialize_app():
|
| 1064 |
-
print("❌ 应用初始化失败")
|
| 1065 |
-
sys.exit(1)
|
| 1066 |
-
|
| 1067 |
-
if __name__ == "__main__":
|
| 1068 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
books.db
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b95bb63da9498f42a3e27b60303a62bd8071958b4d1cb8e4475312ac68838d8f
|
| 3 |
-
size 516096
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_002_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:a5f33e7250177e0b9919b0c2b2f1e1eb58f059efd17146821b49aeb6cac19bf3
|
| 3 |
-
size 51826
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_003_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:f3db8d98e71f8fadcb57b1eff3fd1652dfd0b12f3fb3021881df15ca7a884921
|
| 3 |
-
size 45139
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:0b43ed35c99df647d6205526e780b0492982f42e8e7bc86276674b82305a3d44
|
| 3 |
-
size 42631
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:f976fad7083db695b0210e81c7c37fdc1a3751be9d0cf52c6452454158b05827
|
| 3 |
-
size 36362
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_02.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:db1910ca633d8ca10e97f09dd3edc210ed66772510013b4525fd56bdc1ce0cb7
|
| 3 |
-
size 59768
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_03.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:5823b138fa96edbff4e42c46d3de1eed62e76c27040a623388865450ab207901
|
| 3 |
-
size 52245
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_04.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:1c2ecddb71a94cea2fedffecee8babd2e20d949aa63e0c45073e9565c7ebaca0
|
| 3 |
-
size 71053
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_05.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:d143cccfe5d0c48dd7f2f039fb244bacc3c346741b9179866e7b6258e6df6f3d
|
| 3 |
-
size 82338
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_06.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:7d1a9b7680628a28f861105a30f66d9b32b1e2f5d228282c84e3b743e5747aa8
|
| 3 |
-
size 50155
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_07.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:dd5afef021a9e411b217b81fa90d8d6760e014aab881ead835b6d205a153d316
|
| 3 |
-
size 49737
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_08.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:892987c43bbc5b99242fd554d8ae99015ccb0849ed98000f2f8b4aa1af52bb73
|
| 3 |
-
size 58514
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_004_piece_09.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:873cdbbb4978f991636e332a5dc5dad869c9d754ad1a1872ffb2136a16342079
|
| 3 |
-
size 90379
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_005_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:2ef96ddf79d08895258099e26255c97cb6cff1dd493dc83970f25759788b120c
|
| 3 |
-
size 45975
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_005_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:c9ef26e060ca1db607b9362413332a2f3abd24992348565abc785a299a513266
|
| 3 |
-
size 32182
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_005_piece_02.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:85864c7d35a0cb0e57f796a760c653d2a44fb2b6e17812a7ab36332fcd835bc2
|
| 3 |
-
size 28839
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_005_piece_03.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:6eb99945c52d876ed1c4efbe6ecf4dc68ed199fc4bbbfd6c654806c6fc30adca
|
| 3 |
-
size 33436
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_005_piece_04.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:21c220fd9848e6d77f2fb977bc79c44e7df817acfc6a1e934b989a85e99b6f59
|
| 3 |
-
size 32600
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:1fb27d35047bb161899fcf97d84bc906ae17dcc5328230bc78e62b9c91316bbb
|
| 3 |
-
size 46811
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:975e3dc752f7477e7a514f57cc0a78f8bd8aaeefd991d7155d763fe27faf4974
|
| 3 |
-
size 25913
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_02.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:e5683b45230fb533fd2793b8bc1dd28f00005b228fa02fc0ef74adae24e6044a
|
| 3 |
-
size 23823
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_03.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:0d244ffdd82258c463048e5c7739d629f0d0d9e11f5fae8a39beb5121c25ee89
|
| 3 |
-
size 25077
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_04.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:4d01f72dc5ad26b83a1af2e69788fa51a164101a08b51fb0028cadd66c17b4d6
|
| 3 |
-
size 25495
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_05.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3881dd3a52388c87a166100276b975c909821ea2ed5df9fde9ea0d317ff0ed61
|
| 3 |
-
size 24659
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_006_piece_06.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b8836ad3b13c0f167b3440948212a5ea33215989074275dd59542e07d3f37dd4
|
| 3 |
-
size 21733
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_007_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:384454cc204eec379e8c53aba0eddcff8a4a727bd91cc9891077d0c886418698
|
| 3 |
-
size 53080
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_007_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:faa75a6c25c33bc530d574534debc1cda01e8780276a8bda3df93f8ddfaa38bd
|
| 3 |
-
size 129567
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_007_piece_02.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:9531cb7e9b1cc2a5758ee920ab7182d7a5be60e845e965c4821b34376e2a9288
|
| 3 |
-
size 133746
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_009_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:8a5b4467fbdb87518d42a31bad1ed7b4a3d747cdafaf2b79cbadd5d9312f1a10
|
| 3 |
-
size 47647
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_009_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b0deea352ddba234530ed7ba0cbb9058fc800ca0fb40ded8d3999bcb44170255
|
| 3 |
-
size 22569
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_009_piece_02.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:97d098d3294bcceda1ef9425afa47945d7d38dbe1bfc5d0680fdd3addd8033db
|
| 3 |
-
size 20897
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_009_piece_03.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:4c12fd9818d9f1923828e2a3dc839fb297b1f2120497f0d6f00900054384fdd9
|
| 3 |
-
size 25495
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_009_piece_04.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ccbb125b0451f64214a84f9e79d4b7cfc70d243c5822ecb57d447eba3fa7083e
|
| 3 |
-
size 27585
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_009_piece_05.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:a755929366404882216e8f397a126e40cf3c9ca490afd65ac65ae907dd885528
|
| 3 |
-
size 28003
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_010_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:f7d47bf1be6e08099577644d3123d508064fc076b057c37f659db2c8d2e6b68d
|
| 3 |
-
size 83173
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_010_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:5b6519fab7096620a24834db703a5e8ecf43d3b857ced4e779c00952b77ec922
|
| 3 |
-
size 87353
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_00.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:555ec8a313d6de63854fdb07c4e4d0a89e6ee6e96b99a483ef2fa9eafede9209
|
| 3 |
-
size 53498
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_01.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:be324c440873e425aef730222704af1e3f681d9a025396df748e24f98e1aee4a
|
| 3 |
-
size 23405
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_02.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:ba6abf1c452b6dfba05484e2dde2422274cbfdac03d83dd016dd9ed6ec1f4d4a
|
| 3 |
-
size 23823
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_03.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:4538306b11fb20ea1b5e279fd4f71314051480e04d14ee608dd636c63d9bb3b3
|
| 3 |
-
size 32600
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_04.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:1813f326124a81b5a8a6dac0967fffac8f21b316597fd59a0ee9c577f5a967e4
|
| 3 |
-
size 29257
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_05.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:d40d3aabfb6296af236f622b39bcbbcb2d095d948c69f56882acec9780b97642
|
| 3 |
-
size 29257
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_06.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:0fb841e233e063d5fbfb36a27ff2270bc3d970da883fb3d5459f29f8ec4bce58
|
| 3 |
-
size 30093
|
|
|
|
|
|
|
|
|
|
|
|
data/10005_一年级上册/audios/page_011_piece_07.mp3
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:1786b7c7835750b76eb8e707ba40a4483ebcc8c21a14e9a1b34ae1a32097ac7c
|
| 3 |
-
size 28839
|
|
|
|
|
|
|
|
|
|
|
|