From b7b34f47ee8cc5963bd3ddb06f3bdb53a3b65ec2 Mon Sep 17 00:00:00 2001 From: Misaka Date: Mon, 6 Jan 2025 14:25:39 +0800 Subject: [PATCH] =?UTF-8?q?Feat=20=E5=A2=9E=E5=8A=A0=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - web/.dockerignore | 1 + web/Dockerfile | 24 ++++ web/Dockerfile.zh | 23 ++++ web/README.md | 3 + web/app.py | 140 +++++++++++++++++++++ web/nginx.conf | 37 ++++++ web/public/favicon.ico | Bin 0 -> 9931 bytes web/public/index.html | 265 +++++++++++++++++++++++++++++++++++++++ web/public/utils/api.js | 109 ++++++++++++++++ web/public/utils/http.js | 22 ++++ web/public/utils/util.js | 8 ++ web/requirements.txt | 5 + web/utils/logger.py | 32 +++++ 14 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 web/.dockerignore create mode 100644 web/Dockerfile create mode 100644 web/Dockerfile.zh create mode 100644 web/README.md create mode 100644 web/app.py create mode 100644 web/nginx.conf create mode 100644 web/public/favicon.ico create mode 100644 web/public/index.html create mode 100644 web/public/utils/api.js create mode 100644 web/public/utils/http.js create mode 100644 web/public/utils/util.js create mode 100644 web/requirements.txt create mode 100644 web/utils/logger.py diff --git a/.gitignore b/.gitignore index 3ed3100..3cd9bba 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ tmp .vscode .DS_Store -*.html *yml *.log *.sh diff --git a/web/.dockerignore b/web/.dockerignore new file mode 100644 index 0000000..cd0d007 --- /dev/null +++ b/web/.dockerignore @@ -0,0 +1 @@ +*.sh \ No newline at end of file diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..a909e28 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.12-alpine + +RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone && apk add ca-certificates + +RUN apk add --update --no-cache python3 py3-pip nginx \ + && mkdir -p /run/nginx \ + && rm -rf /var/cache/apk/* + +WORKDIR /app + +COPY ./requirements.txt /app/requirements.txt + +RUN pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple \ + && pip config set global.trusted-host mirrors.cloud.tencent.com \ + && pip install --upgrade pip --break-system-packages \ + && pip install --user -r requirements.txt --break-system-packages + +COPY nginx.conf /etc/nginx/nginx.conf + +COPY . /app + +EXPOSE 80 + +CMD ["sh", "-c", "python3 app.py & nginx -g 'daemon off;'"] \ No newline at end of file diff --git a/web/Dockerfile.zh b/web/Dockerfile.zh new file mode 100644 index 0000000..1956c4c --- /dev/null +++ b/web/Dockerfile.zh @@ -0,0 +1,23 @@ +FROM docker.proxy.yangrucheng.top/python:3.12-alpine + +RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone && apk add ca-certificates + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ + && apk add --update --no-cache python3 py3-pip nginx \ + && mkdir -p /run/nginx \ + && rm -rf /var/cache/apk/* + +WORKDIR /app + +COPY ./requirements.txt /app/requirements.txt + +RUN pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple \ + && pip config set global.trusted-host mirrors.cloud.tencent.com \ + && pip install --upgrade pip --break-system-packages \ + && pip install --user -r requirements.txt --break-system-packages + +COPY nginx.conf /etc/nginx/nginx.conf + +COPY . /app + +CMD ["sh", "-c", "python3 app.py & nginx -g 'daemon off;'"] \ No newline at end of file diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..207ee3c --- /dev/null +++ b/web/README.md @@ -0,0 +1,3 @@ +# 小程序的网页入口 + +> 为了减少小程序被封风险而添加的网页入口,没有任何功能 \ No newline at end of file diff --git a/web/app.py b/web/app.py new file mode 100644 index 0000000..2df0795 --- /dev/null +++ b/web/app.py @@ -0,0 +1,140 @@ + +from fastapi.responses import JSONResponse, RedirectResponse, Response +from fastapi import FastAPI, Body, APIRouter, Request +from fastapi.exceptions import RequestValidationError +from starlette.exceptions import HTTPException +from fastapi.staticfiles import StaticFiles +from contextlib import asynccontextmanager +import datetime +import uvicorn +import socket +import httpx + + +@asynccontextmanager +async def lifespan(app: FastAPI): + yield + +app = FastAPI(lifespan=lifespan, redoc_url=None, docs_url=None) + + +@app.get("/api/login") +async def login(request: Request, username: str, password: str): + """ 登录 """ + async with httpx.AsyncClient() as client: + resp = await client.get("https://passport2-api.chaoxing.com/v11/loginregister", params={ + "cx_xxt_passport": "json", + "roleSelect": "true", + "uname": username, + "code": password, + "loginType": "1", + }) + resp2 = JSONResponse(resp.json()) + for key, value in resp.cookies.items(): + resp2.set_cookie(key, value, max_age=3600*24*7) + return resp2 + + +@app.get("/api/get_courses") +async def get_courses(request: Request): + """ 获取课程列表 """ + async with httpx.AsyncClient(cookies=dict(request.cookies)) as client: + resp = await client.get("https://mooc1-api.chaoxing.com/mycourse/backclazzdata", params={ + 'view': 'json', + 'rss': '1', + }) + return JSONResponse(resp.json()) + + +app.mount("/", StaticFiles(directory="public"), name="public") + + +@app.exception_handler(HTTPException) +async def http_exception_handler(request: Request, exc: HTTPException): + if exc.status_code == 404: + return RedirectResponse("/index.html", status_code=302) + else: + return JSONResponse({ + "status": -1, + "msg": exc.detail, + "data": None, + "time": int(datetime.datetime.now().timestamp()), + }, status_code=exc.status_code) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + return JSONResponse({ + "status": -1, + "msg": "参数错误, 请检查参数", + "data": { + "body": exc.body, + "query": { + "raw": str(request.query_params), + "parsed": dict(request.query_params), + }, + "error": exc.errors(), + }, + "time": int(datetime.datetime.now().timestamp()), + }, status_code=422) + + +@app.exception_handler(Exception) +async def exception_handler(request: Request, exc: Exception): + return JSONResponse({ + "status": -1, + "msg": "服务器内部错误, 请联系管理员! 邮箱: admin@misaka-network.top", + "time": int(datetime.datetime.now().timestamp()), + }, status_code=500) + + +@app.middleware("http") +async def add_process_time_header(request: Request, call_next): + starttime = datetime.datetime.now() + response: Response = await call_next(request) + endtime = datetime.datetime.now() + response.headers["X-Process-Time"] = str( + int((endtime - starttime).total_seconds())) + response.headers["X-Client-Host"] = request.client.host + return response + + +def get_localhost(): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + except Exception as e: + return None + else: + return ip + finally: + s.close() + + +if __name__ == "__main__": + starttime = datetime.datetime.now().strftime(r"%Y-%m-%d %H:%M:%S") + from utils.logger import set_log_formatter + set_log_formatter() + import logging as lg + logging = lg.getLogger("uvicorn") + try: + logging.info(f"服务已启动, 请访问 http://{get_localhost()}:8080") + uvicorn.run( + app="app:app", + host="0.0.0.0", + port=8080, + reload=False, + forwarded_allow_ips="*", + log_config=None, + workers=1, + headers=[ + ("Server", "Misaka Network Distributed Server"), + ("X-Powered-By", "Misaka Network Studio"), + ("X-Statement", "This service is provided by Misaka Network Studio. For complaints/cooperation, please email admin@misaka-network.top"), + ("X-Copyright", "© 2024 Misaka Network Studio. All rights reserved."), + ("X-Server-Start-Time", starttime), + ], + ) + except KeyboardInterrupt: + logging.info("Ctrl+C 终止服务") diff --git a/web/nginx.conf b/web/nginx.conf new file mode 100644 index 0000000..32c3393 --- /dev/null +++ b/web/nginx.conf @@ -0,0 +1,37 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + set_real_ip_from 0.0.0.0/0; + real_ip_header X-Forwarded-For; + server_tokens off; + real_ip_recursive on; + + server { + listen 80; + server_name localhost; + + location / { + root /app/public; + index index.html; + } + + location /api { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } +} diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2c96a16ab7802178f5c295d892c2a443069b6154 GIT binary patch literal 9931 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

(AbY)`C}#49$Bm!3|i8 zi~VHV3VBl$EUInbws&36E-$#RA+`uwyF{i6r~p>}1QTX0+DWjys_7{{gALh@$m`g`)rf002ov JPDHLkV1neo&z}GQ literal 0 HcmV?d00001 diff --git a/web/public/index.html b/web/public/index.html new file mode 100644 index 0000000..039d6ce --- /dev/null +++ b/web/public/index.html @@ -0,0 +1,265 @@ + + + + + + + + 学习通签到 · 御坂网络 Misaka + + + + +

+ + + + + + + \ No newline at end of file diff --git a/web/public/utils/api.js b/web/public/utils/api.js new file mode 100644 index 0000000..d6fe55b --- /dev/null +++ b/web/public/utils/api.js @@ -0,0 +1,109 @@ +import http from './http.js'; +import util from './util.js'; + +class API { + constructor(username, password = "") { + this.updatetime = util.getStorage(`cookies-updatetime-${username}`, 0) + this.uid = ""; + this.username = username; + this.password = password; + } + + /** + * 检查登录 + */ + checkLogin = async () => { + if (this.updatetime + 7 * 24 * 3600 * 1000 <= new Date().getTime()) { + console.debug("自动登录 登录过期") + await this.login(); + } + } + + /** + * 登录 + */ + login = async () => { + if (!this.username || !this.password) + return; + const res = await http.get('/api/login', { + "username": this.username, + "password": this.password, + }); + this.updatetime = new Date().getTime(); + util.setStorage(`cookies-updatetime-${this.username}`, this.updatetime); + console.info("登录", res); + await this.getUID(); + return res; + } + + /** + * 获取UID + */ + getUID = () => { + this.uid = ""; + return this.uid; + } + + /** + * 获取课程列表 + */ + getCourses = async () => { + await this.checkLogin(); + const url = '/api/get_courses'; + const res = await http.get(url) + let data = res.channelList.filter(item => item.cataName == '课程').map(item => { + return { + 'courseName': item.content.course ? item.content.course.data[0]?.name : item.content.name, + 'className': item.content.course ? item.content.name : item.content.clazz[0]?.clazzName, + 'teacherName': item.content.course ? item.content.course.data[0]?.teacherfactor : item.content.teacherfactor, + 'courseId': item.content.course ? item.content.course.data[0]?.id : item.content.id, + 'classId': item.content.course ? item.key : item.content.clazz[0]?.clazzId, + 'folder': (res.channelList.find(i => i.catalogId == item.cfid) || {}).content?.folderName || null, // 所在的文件夹 + 'isTeach': !item.content.course, // 是否自己教的课 + 'img': item.content.course ? item.content.course.data[0].imageurl : item.content.imageurl, + }; + }); + data.sort((a, b) => b.isShow - a.isShow); + console.info("获取课程", res, data) + return data; + } + + /** + * 获取小程序链接 + * @param {*} courseId + * @param {*} classId + */ + getWechatUrl = (courseId, classId) => { + const query = Object.entries({ + 'username': this.username, + 'password': this.password, + 'courseId': courseId, + 'classId': classId, + 'package': 'sign', + 'path': '/activity/activity', + }) + .map(([key, value]) => `${key}=${value}`) + .join('&') + + const url = `weixin://dl/business/?appid=wxb42fe32e6e071916&path=pages/share/share&query=${encodeURIComponent(query)}`; + return url; + } + + /** + * 小程序入口 + * @returns + */ + getMiniProgram = () => { + const query = Object.entries({ + 'path': '/packages/sign-package/pages/home/home', + 'appid': 'wx0ba7981861be3afc', + }) + .map(([key, value]) => `${key}=${value}`) + .join('&') + + const url = `weixin://dl/business/?appid=wxb42fe32e6e071916&path=pages/share/share&query=${encodeURIComponent(query)}`; + return url; + } +} + +export default API; \ No newline at end of file diff --git a/web/public/utils/http.js b/web/public/utils/http.js new file mode 100644 index 0000000..232be36 --- /dev/null +++ b/web/public/utils/http.js @@ -0,0 +1,22 @@ +class HTTP { + get = (url, data = {}) => { + return new Promise((resolve, reject) => { + const _url = `${url}?${Object.keys(data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`).join('&')}`; + fetch(_url, { + method: "GET", + headers: { + 'Content-Type': 'application/json', + }, + }).then(res => res.json()).then(res => { + resolve(res); + }).catch(err => { + reject(err); + }); + }); + } + +} + +const http = new HTTP(); + +export default http; \ No newline at end of file diff --git a/web/public/utils/util.js b/web/public/utils/util.js new file mode 100644 index 0000000..160d3e6 --- /dev/null +++ b/web/public/utils/util.js @@ -0,0 +1,8 @@ +export default { + getStorage: (key, defaultValue = null) => { + return localStorage.getItem(key) || defaultValue; + }, + setStorage: (key, value) => { + localStorage.setItem(key, value); + }, +} \ No newline at end of file diff --git a/web/requirements.txt b/web/requirements.txt new file mode 100644 index 0000000..79ed6d5 --- /dev/null +++ b/web/requirements.txt @@ -0,0 +1,5 @@ +httpx +httpx[http2] +starlette +uvicorn +fastapi \ No newline at end of file diff --git a/web/utils/logger.py b/web/utils/logger.py new file mode 100644 index 0000000..593dc48 --- /dev/null +++ b/web/utils/logger.py @@ -0,0 +1,32 @@ +import sys + +def set_log_formatter(): + import logging + " ANSI 转义码设置颜色 " + TIME_COLOR = "\033[32m" + LEVEL_COLOR = "\033[33m" + RESET = "\033[0m" + LOG_FORMATE = \ + f"{TIME_COLOR}%(asctime)s{RESET} - {LEVEL_COLOR}%(levelname)s{RESET} - %(message)s" + + logging.basicConfig( + level=logging.INFO, format=LOG_FORMATE, stream=sys.stdout) + + # uvicorn 日志 + # logger = logging.getLogger("uvicorn") + # logger.handlers = [] + # console_handler = logging.StreamHandler() + # console_handler.setFormatter(logging.Formatter(LOG_FORMATE)) + # logger.addHandler(console_handler) + + # access_logger = logging.getLogger("uvicorn.access") + # access_logger.handlers = [] + # access_handler = logging.StreamHandler() + # access_handler.setFormatter(logging.Formatter(LOG_FORMATE)) + # access_logger.addHandler(access_handler) + + # httpx 日志 + logger = logging.getLogger("httpx") + logger.setLevel(logging.WARNING) + +set_log_formatter() \ No newline at end of file