Squashed 'paste-framework/' content from commit 34e8684
git-subtree-dir: paste-framework git-subtree-split: 34e8684c4bc3cebbe177509f42ab4ef5b5425a7a
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"app_name": "Hello Paste",
|
||||
|
||||
"logger_desc": "用于日志输出的配置,各服务可以有自己的配置,但要使用独立配置时,必须编写额外代码",
|
||||
"logger": {
|
||||
"default": {
|
||||
"desc": "默认日志配置,该配置小节的名称已经配置在 PASTE 框架中",
|
||||
"basic": {
|
||||
"filename": "logs/root.log",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"level": 20
|
||||
},
|
||||
"filename": "logs/default.log",
|
||||
"name": "Demo",
|
||||
"max_bytes": 20971520,
|
||||
"backup_count": 40
|
||||
}
|
||||
},
|
||||
|
||||
"tornado_desc": "用于 Tornado 服务的配置,每一项后面允许设置多个服务",
|
||||
"tornado": {
|
||||
"demo": {
|
||||
"autoreload": false,
|
||||
"handlers_pkg": "examples.01_hello_world",
|
||||
"port": 9000,
|
||||
"static_path": "static",
|
||||
"template_path": "templates",
|
||||
"swagger_title": "DemoAPI",
|
||||
"swagger_description": "Demo API",
|
||||
"swagger_api_version": "1.0.1",
|
||||
"swagger_contact": "email@qq.com"
|
||||
}
|
||||
},
|
||||
|
||||
"version": "1.0.1"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
from paste.core.logging import echo_log
|
||||
from paste.web.decorators import route
|
||||
from paste.web.handler import RequestHandler
|
||||
|
||||
|
||||
@route("/hello")
|
||||
class HelloHandler(RequestHandler):
|
||||
"""
|
||||
演示一个请求。
|
||||
"""
|
||||
async def get(self):
|
||||
"""
|
||||
常规请求。
|
||||
"""
|
||||
echo_log(f"Received request!")
|
||||
self.response_ok(message="Hello from paste!")
|
||||
@@ -0,0 +1,20 @@
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from paste.core import config
|
||||
from paste.core.logging import set_logger_config
|
||||
from paste.web.application import Application
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 日志配置
|
||||
logger_config_name = 'logger.default'
|
||||
set_logger_config(logger_config_name)
|
||||
# 应用配置
|
||||
demo_config: dict = config.get_config('tornado.demo', {})
|
||||
port = config.get_config('tornado.demo.port', 9000)
|
||||
# 创建应用
|
||||
app = Application(**demo_config)
|
||||
app.listen(port)
|
||||
handlers_pkg = config.get_config('tornado.demo.handlers_pkg')
|
||||
print(f"App {handlers_pkg} is running at http://localhost:{port}")
|
||||
# 启动监听
|
||||
IOLoop.current().start()
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"app_name": "Background Task Demo",
|
||||
|
||||
"logger_desc": "用于日志输出的配置,各服务可以有自己的配置,但要使用独立配置时,必须编写额外代码",
|
||||
"logger": {
|
||||
"default": {
|
||||
"desc": "默认日志配置,该配置小节的名称已经配置在 PASTE 框架中",
|
||||
"basic": {
|
||||
"filename": "logs/root.log",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"level": 20
|
||||
},
|
||||
"filename": "logs/default.log",
|
||||
"name": "Demo",
|
||||
"max_bytes": 20971520,
|
||||
"backup_count": 40
|
||||
}
|
||||
},
|
||||
|
||||
"tornado_desc": "用于 Tornado 服务的配置,每一项后面允许设置多个服务",
|
||||
"tornado": {
|
||||
"demo": {
|
||||
"autoreload": false,
|
||||
"handlers_pkg": "examples.02_background_task",
|
||||
"port": 9000,
|
||||
"static_path": "static",
|
||||
"template_path": "templates",
|
||||
"swagger_title": "DemoAPI",
|
||||
"swagger_description": "Demo API",
|
||||
"swagger_api_version": "1.0.1",
|
||||
"swagger_contact": "email@qq.com"
|
||||
}
|
||||
},
|
||||
|
||||
"version": "1.0.1"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from paste.core import aio_pool
|
||||
from paste.core.logging import echo_log
|
||||
from paste.web.decorators import route
|
||||
from paste.web.handler import RequestHandler
|
||||
|
||||
|
||||
@route("/background")
|
||||
class HelloHandler(RequestHandler):
|
||||
"""
|
||||
演示一个请求,其中包含异步后台任务。
|
||||
"""
|
||||
|
||||
async def background_task(self):
|
||||
"""
|
||||
模拟后台异步处理任务:仅做延时,代表执行数据库写入、消息推送、文件处理等。
|
||||
"""
|
||||
try:
|
||||
for i in range(10):
|
||||
echo_log(f"后台任务开始执行-{i}...")
|
||||
await asyncio.sleep(0.8) # 模拟耗时操作
|
||||
echo_log("后台任务完成:模拟处理完毕。")
|
||||
except Exception as e:
|
||||
echo_log(f"后台任务异常: {e}", level=logging.ERROR)
|
||||
|
||||
async def get(self):
|
||||
"""
|
||||
常规请求,先执行后台任务,再响应前端,但是不等待任务完成。
|
||||
"""
|
||||
echo_log(f"Received request!")
|
||||
await aio_pool.run_background_task(self.background_task())
|
||||
self.response_ok(message="Response from paste!")
|
||||
@@ -0,0 +1,20 @@
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from paste.core import config
|
||||
from paste.core.logging import set_logger_config
|
||||
from paste.web.application import Application
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 日志配置
|
||||
logger_config_name = 'logger.default'
|
||||
set_logger_config(logger_config_name)
|
||||
# 应用配置
|
||||
demo_config: dict = config.get_config('tornado.demo', {})
|
||||
port = config.get_config('tornado.demo.port', 9000)
|
||||
# 创建应用
|
||||
app = Application(**demo_config)
|
||||
app.listen(port)
|
||||
handlers_pkg = config.get_config('tornado.demo.handlers_pkg')
|
||||
print(f"App {handlers_pkg} is running at http://localhost:{port}")
|
||||
# 启动监听
|
||||
IOLoop.current().start()
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"app_name": "Redis Stream Demo",
|
||||
|
||||
"redis_desc": "Redis 数据库连接配置及相关描述",
|
||||
"redis": {
|
||||
"connection": {
|
||||
"url": "redis://:HaitenRedis@20250703@100.64.0.1:3379/2",
|
||||
"max_connections": 1000,
|
||||
"encoding": "utf-8",
|
||||
"decode_responses": true
|
||||
},
|
||||
"streams": {
|
||||
"demo": {
|
||||
"group": "DEMO_PROCESSORS",
|
||||
"consumer": "demo_worker"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"logger_desc": "用于日志输出的配置,各服务可以有自己的配置,但要使用独立配置时,必须编写额外代码",
|
||||
"logger": {
|
||||
"default": {
|
||||
"desc": "默认日志配置,该配置小节的名称已经配置在 PASTE 框架中",
|
||||
"basic": {
|
||||
"filename": "logs/root.log",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"level": 20
|
||||
},
|
||||
"filename": "logs/default.log",
|
||||
"name": "Demo",
|
||||
"max_bytes": 20971520,
|
||||
"backup_count": 40
|
||||
}
|
||||
},
|
||||
|
||||
"tornado_desc": "用于 Tornado 服务的配置,每一项后面允许设置多个服务",
|
||||
"tornado": {
|
||||
"demo": {
|
||||
"autoreload": false,
|
||||
"handlers_pkg": "examples.03_redis_stream",
|
||||
"port": 9000,
|
||||
"static_path": "static",
|
||||
"template_path": "templates",
|
||||
"swagger_title": "DemoAPI",
|
||||
"swagger_description": "Demo API",
|
||||
"swagger_api_version": "1.0.1",
|
||||
"swagger_contact": "email@qq.com"
|
||||
}
|
||||
},
|
||||
|
||||
"version": "1.0.1"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
from paste.core.logging import echo_log
|
||||
from paste.db.redis import StreamActor
|
||||
from paste.web.decorators import route
|
||||
from paste.web.handler import RequestHandler
|
||||
|
||||
@route("/stream")
|
||||
class MessageHandler(RequestHandler):
|
||||
"""
|
||||
演示请求发布 Redis Stream 消息。
|
||||
"""
|
||||
|
||||
# 从配置中加载 Stream 配置路径
|
||||
stream_config_path = "redis.streams.demo"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 初始化 StreamActor 实例(按配置创建)
|
||||
self.actor = StreamActor.new_actor(self.stream_config_path)
|
||||
|
||||
async def post(self):
|
||||
"""
|
||||
接收前端 POST 请求,发布消息到 Redis Stream,立即响应。
|
||||
请求体格式:
|
||||
{
|
||||
"user_id": "123",
|
||||
"event": "login",
|
||||
"data": {"ip": "192.168.1.1"}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 1. 获取请求参数
|
||||
body = self.request_arguments()
|
||||
user_id = body.get("user_id")
|
||||
event = body.get("event")
|
||||
data = body.get("data", {})
|
||||
|
||||
if not user_id or not event:
|
||||
self.response_error(
|
||||
Exception("参数缺失:必须提供 user_id 和 event"),
|
||||
status_code=400,
|
||||
api_status_code=400
|
||||
)
|
||||
return
|
||||
|
||||
# 2. 构造消息数据
|
||||
message_data = {
|
||||
"user_id": user_id,
|
||||
"event": event,
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat() + 'Z',
|
||||
"data": json.dumps(data)
|
||||
}
|
||||
|
||||
# 3. 异步发布消息(立即返回,不等待消费)
|
||||
msg_id = await self.actor.publish(message_data)
|
||||
|
||||
# 4. 响应成功
|
||||
self.response_ok(
|
||||
message="消息已成功发布",
|
||||
message_id=msg_id,
|
||||
stream=self.stream_config_path
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
echo_log('异常', logging.ERROR, True)
|
||||
self.response_error(e, status_code=500, api_status_code=500)
|
||||
@@ -0,0 +1,20 @@
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from paste.core import config
|
||||
from paste.core.logging import set_logger_config
|
||||
from paste.web.application import Application
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 日志配置
|
||||
logger_config_name = 'logger.default'
|
||||
set_logger_config(logger_config_name)
|
||||
# 应用配置
|
||||
demo_config: dict = config.get_config('tornado.demo', {})
|
||||
port = config.get_config('tornado.demo.port', 9000)
|
||||
# 创建应用
|
||||
app = Application(**demo_config)
|
||||
app.listen(port)
|
||||
handlers_pkg = config.get_config('tornado.demo.handlers_pkg')
|
||||
print(f"App {handlers_pkg} is running at http://localhost:{port}")
|
||||
# 启动监听
|
||||
IOLoop.current().start()
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
演示 Redis Stream 消息队列服务。
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import redis
|
||||
|
||||
from paste.core import aio_pool
|
||||
from paste.core.logging import set_logger_config, echo_log, get_logger
|
||||
from paste.db.redis import StreamActor
|
||||
from paste.service.daemonize import DaemonizeService
|
||||
|
||||
logger_config_name = 'logger.default'
|
||||
"""
|
||||
配置文件中日志配置字段名称。
|
||||
"""
|
||||
|
||||
current_event_loop = None
|
||||
"""
|
||||
事件循环对象。
|
||||
"""
|
||||
|
||||
pid_file = os.path.join(os.path.curdir, 'stream_service.pid')
|
||||
"""
|
||||
PID 文件路径。
|
||||
"""
|
||||
|
||||
service_name = 'Redis Stream 消息队列服务'
|
||||
"""
|
||||
服务名称。
|
||||
"""
|
||||
|
||||
# 配置路径:从 config.json 中读取
|
||||
stream_config_path = "redis.streams.demo"
|
||||
|
||||
# 创建 StreamActor 实例
|
||||
stream_actor: Optional[StreamActor] = None
|
||||
|
||||
|
||||
async def process_message(data: dict):
|
||||
"""
|
||||
业务回调:处理每条消息
|
||||
"""
|
||||
user_id = data.get("user_id", "unknown")
|
||||
event = data.get("event", "")
|
||||
stream_data = data.get("data", "")
|
||||
timestamp = data.get("timestamp", "")
|
||||
|
||||
echo_log(f"消费消息: user_id={user_id}, event='{event}', stream_data='{stream_data}', time={timestamp}")
|
||||
|
||||
# 模拟处理:写入数据库、发送邮件、更新缓存...
|
||||
# 示例:记录日志 + 模拟耗时
|
||||
for i in range(10):
|
||||
echo_log(f"后台任务开始执行-{i}...")
|
||||
await asyncio.sleep(0.8)
|
||||
|
||||
echo_log(f"消息处理完成: {user_id}")
|
||||
return True
|
||||
|
||||
|
||||
def current_loop() -> asyncio.AbstractEventLoop:
|
||||
"""
|
||||
这里必须采用方法,在适当的时间点创建事件循环对象,否则会导致服务无法启动。
|
||||
:return: 事件循环对象
|
||||
"""
|
||||
global current_event_loop
|
||||
if current_event_loop is None:
|
||||
current_event_loop = asyncio.new_event_loop()
|
||||
return current_event_loop
|
||||
|
||||
|
||||
def start_service():
|
||||
"""
|
||||
控制台服务方式启动。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
echo_log(f"正在启动{service_name}...")
|
||||
|
||||
try:
|
||||
# 检测 Redis 连接
|
||||
echo_log('检测 Redis 服务...')
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(StreamActor.ping())
|
||||
echo_log('Redis 服务正常.')
|
||||
|
||||
# 创建 StreamActor 监听服务
|
||||
global stream_actor
|
||||
stream_actor = StreamActor.new_actor(stream_config_path)
|
||||
echo_log(f"{service_name}已启动,正在监听{service_name}...")
|
||||
_runner(stream_actor.run_forever(process_message, is_delete=True))
|
||||
except (redis.exceptions.TimeoutError, socket.timeout):
|
||||
echo_log('Redis 服务异常.', level=logging.ERROR, is_log_exc=True)
|
||||
echo_log(f"{service_name}启动失败.")
|
||||
except KeyboardInterrupt:
|
||||
echo_log(msg='KeyboardInterrupt')
|
||||
stop_service()
|
||||
except Exception as e:
|
||||
echo_log(msg=e, level=logging.ERROR, is_log_exc=True)
|
||||
echo_log(f"{service_name}因未知异常启动失败.")
|
||||
|
||||
|
||||
def stop_service():
|
||||
"""
|
||||
停止服务。
|
||||
"""
|
||||
echo_log(f"正在停止{service_name}...")
|
||||
# 停止监听
|
||||
stream_actor.subscribe_stop()
|
||||
# 停止事件循环
|
||||
current_loop().stop()
|
||||
echo_log(f"{service_name}已停止.")
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
驻内存服务方式启动。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
get_logger()
|
||||
ds = DaemonizeService(pid_file=pid_file, name=service_name)
|
||||
ds.set_start_callback(start_service)
|
||||
ds.set_term_callback(stop_service)
|
||||
ds.start()
|
||||
|
||||
|
||||
def stop():
|
||||
"""
|
||||
驻内存服务方式停止。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
get_logger()
|
||||
ds = DaemonizeService(pid_file=pid_file, name=service_name)
|
||||
ds.set_start_callback(start_service)
|
||||
ds.set_term_callback(stop_service)
|
||||
ds.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "start":
|
||||
start_service()
|
||||
elif sys.argv[1] == "stop":
|
||||
stop_service()
|
||||
else:
|
||||
print("用法: python service/stream_service.py start")
|
||||
else:
|
||||
start_service()
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"app_name": "Paste 测试",
|
||||
|
||||
"logger_desc": "用于日志输出的配置,各服务可以有自己的配置,但要使用独立配置时,必须编写额外代码",
|
||||
"logger": {
|
||||
"default": {
|
||||
"desc": "默认日志配置,该配置小节的名称已经配置在 PASTE 框架中",
|
||||
"basic": {
|
||||
"filename": "logs/root.log",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"level": 20
|
||||
},
|
||||
"filename": "logs/default.log",
|
||||
"name": "Demo",
|
||||
"max_bytes": 20971520,
|
||||
"backup_count": 40
|
||||
}
|
||||
},
|
||||
|
||||
"version": "1.0.1"
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
系统服务,用于读取服务配置文件,启动或停止相关的服务。
|
||||
"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from paste.core.logging import echo_log, set_logger_config
|
||||
from paste.service.task_service import TaskService
|
||||
|
||||
logger_config_name = 'logger.default'
|
||||
"""
|
||||
配置文件中日志配置字段名称。
|
||||
"""
|
||||
|
||||
task_serv: Optional[TaskService] = None
|
||||
"""
|
||||
任务服务对象。
|
||||
"""
|
||||
|
||||
pid_file = os.path.join(os.path.curdir, 'task_service.pid')
|
||||
"""
|
||||
PID 文件路径。
|
||||
"""
|
||||
|
||||
service_name = '计划任务服务'
|
||||
"""
|
||||
服务名称。
|
||||
"""
|
||||
|
||||
|
||||
def init_task_service():
|
||||
"""
|
||||
初始化服务对象并安装具体任务。
|
||||
"""
|
||||
global task_serv
|
||||
task_serv = TaskService(service_name=service_name, pid_file=pid_file)
|
||||
|
||||
# 每隔 2 秒钟执行
|
||||
task_serv.add_task(creator=task_serv.create_delay_task, fn=renew_token, delay=2)
|
||||
|
||||
return task_serv
|
||||
|
||||
|
||||
async def renew_token():
|
||||
"""
|
||||
演示更新 Token
|
||||
"""
|
||||
echo_log(f"执行:更新 Token.")
|
||||
|
||||
_renewed = False
|
||||
for i in range(2):
|
||||
echo_log(f"后台任务开始执行-{i}...")
|
||||
await asyncio.sleep(0.5) # 模拟耗时操作
|
||||
_renewed = True
|
||||
echo_log(f"更新处理完成,{'已' if _renewed else '未'}更新.")
|
||||
|
||||
|
||||
def start_service():
|
||||
"""
|
||||
控制台服务方式启动。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
_ts = init_task_service()
|
||||
_ts.start_service(env_check=False)
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
驻内存服务方式启动。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
_ts = init_task_service()
|
||||
_ts.start()
|
||||
|
||||
|
||||
def stop():
|
||||
"""
|
||||
驻内存服务方式停止。
|
||||
"""
|
||||
set_logger_config(logger_config_name)
|
||||
_ts = init_task_service()
|
||||
_ts.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "start":
|
||||
start_service()
|
||||
elif sys.argv[1] == "stop":
|
||||
stop()
|
||||
else:
|
||||
print("用法: python service/tsk_service.py start")
|
||||
else:
|
||||
start_service()
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"app_name": "Paste 测试",
|
||||
|
||||
"db_engine_desc": "数据库连接信息,包含普通连接、异步连接以及连接选项,其中连接选项的配置必须对应 create_engine 或 create_async_engine 方法参数,后面加 _xx 后缀的,仅用于保存信息",
|
||||
"db_engine": {
|
||||
"engine": "mysql+pymysql://haiten:HaitenDB%4020250702@100.64.0.1:3360/haiten",
|
||||
"async_engine": "mysql+aiomysql://haiten:HaitenDB%4020250702@100.64.0.1:3360/haiten",
|
||||
"engine_option": {
|
||||
"echo": false,
|
||||
"pool_pre_ping": true,
|
||||
"pool_size": 20,
|
||||
"max_overflow": 200
|
||||
}
|
||||
},
|
||||
|
||||
"logger_desc": "用于日志输出的配置,各服务可以有自己的配置,但要使用独立配置时,必须编写额外代码",
|
||||
"logger": {
|
||||
"default": {
|
||||
"desc": "默认日志配置,该配置小节的名称已经配置在 PASTE 框架中",
|
||||
"basic": {
|
||||
"filename": "logs/root.log",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"level": 20
|
||||
},
|
||||
"filename": "logs/default.log",
|
||||
"name": "Demo",
|
||||
"max_bytes": 20971520,
|
||||
"backup_count": 40
|
||||
}
|
||||
},
|
||||
|
||||
"rbac_desc": "RBAC 基础信息配置",
|
||||
"rbac": {
|
||||
"table": {
|
||||
"assignment": "hat_auth_assignment",
|
||||
"item": "hat_auth_item",
|
||||
"item_child": "hat_auth_item_child",
|
||||
"rule": "hat_auth_rule",
|
||||
"user": "hat_user"
|
||||
},
|
||||
"user_class": "paste.rbac.rbac_user.RbacUser"
|
||||
},
|
||||
|
||||
"version": "1.0.1"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
from paste.core import aio_pool
|
||||
from paste.core.logging import set_logger_config
|
||||
from paste.db import gen_models
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 日志配置
|
||||
logger_config_name = 'logger.default'
|
||||
set_logger_config(logger_config_name)
|
||||
# 生成模型代码
|
||||
_runner = aio_pool.get_aio_runner()
|
||||
_runner(gen_models.sqlacodegen())
|
||||
@@ -0,0 +1,576 @@
|
||||
# coding: utf-8
|
||||
from sqlalchemy import CheckConstraint, Column, Date, DateTime, Float, ForeignKey, Index, String, TIMESTAMP, Text, text
|
||||
from sqlalchemy.dialects.mysql import BIGINT, INTEGER, MEDIUMTEXT
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
metadata = Base.metadata
|
||||
|
||||
|
||||
class HatArticle(Base):
|
||||
__tablename__ = 'hat_article'
|
||||
__table_args__ = {'comment': '文章'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
title = Column(String(300), comment='标题')
|
||||
content = Column(MEDIUMTEXT, comment='内容')
|
||||
cover_image = Column(String(300), nullable=False, server_default=text("''"), comment='封面图片路径')
|
||||
overview = Column(String(300), nullable=False, server_default=text("''"), comment='概述')
|
||||
type = Column(String(50), nullable=False, server_default=text("'采编'"), comment='类型:原创、转载、首发、采编')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
|
||||
class HatArticleCategory(Base):
|
||||
__tablename__ = 'hat_article_category'
|
||||
__table_args__ = {'comment': '文章类别表'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
category_name = Column(String(64), nullable=False, unique=True, server_default=text("''"), comment='类别名称')
|
||||
parent_id = Column(BIGINT(20), nullable=False, server_default=text("0"), comment='父类别ID(默认为0)')
|
||||
description = Column(String(500), nullable=False, server_default=text("''"), comment='类别描述')
|
||||
sort_order = Column(INTEGER(11), nullable=False, server_default=text("1"), comment='排序值')
|
||||
status = Column(INTEGER(11), nullable=False, index=True, server_default=text("0"), comment='状态(默认0,锁定1)')
|
||||
created_at = Column(TIMESTAMP, nullable=False, server_default=text("current_timestamp()"))
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"))
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"))
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"))
|
||||
|
||||
|
||||
class HatClass(Base):
|
||||
__tablename__ = 'hat_classes'
|
||||
__table_args__ = {'comment': '班级表'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
name = Column(String(100), nullable=False, comment='班级名称')
|
||||
year = Column(String(100), nullable=False, comment='班级年份')
|
||||
adviser = Column(String(255), nullable=False, comment='辅导员')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
|
||||
class HatClassroom(Base):
|
||||
__tablename__ = 'hat_classroom'
|
||||
__table_args__ = {'comment': '教室'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
name = Column(String(50), nullable=False, comment='教室名称')
|
||||
total = Column(INTEGER(10), nullable=False, server_default=text("60"), comment='容纳人数')
|
||||
description = Column(String(500), comment='描述')
|
||||
|
||||
|
||||
class HatCourse(Base):
|
||||
__tablename__ = 'hat_course'
|
||||
__table_args__ = {'comment': '课程表,在专业教学计划表,学分对接表中关联'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
name = Column(String(255), nullable=False, unique=True, comment='课程名称')
|
||||
name_en = Column(String(255), comment='英文名称')
|
||||
code = Column(String(50), nullable=False, comment='课程代码')
|
||||
material = Column(String(1000), comment='所选教材')
|
||||
description = Column(Text, comment='课程描述')
|
||||
category = Column(String(50), nullable=False, comment='授课方(中方课程或外方课程)')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
|
||||
class HatCourseSchedule(Base):
|
||||
__tablename__ = 'hat_course_schedule'
|
||||
__table_args__ = {'comment': '课表'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
academic_year = Column(String(32), nullable=False, comment='学年')
|
||||
semester = Column(String(64), nullable=False, comment='上课学期')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
|
||||
class HatEnrollStudent(Base):
|
||||
__tablename__ = 'hat_enroll_student'
|
||||
__table_args__ = (
|
||||
Index('hat_enroll_student_id_card_number_phone_un', 'id_card_number', 'phone', unique=True),
|
||||
{'comment': '学生报名信息表'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
student_number = Column(String(50), nullable=False, index=True, comment='学号')
|
||||
name = Column(String(128), nullable=False, index=True, comment='姓名')
|
||||
gender = Column(String(10), nullable=False, comment='性别')
|
||||
native_place = Column(String(50), comment='籍贯')
|
||||
id_card_number = Column(String(50), nullable=False, comment='身份证')
|
||||
province = Column(String(128), nullable=False, index=True, comment='省')
|
||||
city = Column(String(128), comment='市')
|
||||
date_of_birth = Column(Date, comment='出生年月')
|
||||
politics_status = Column(String(50), comment='政治面貌')
|
||||
nation = Column(String(128), comment='民族')
|
||||
house_address = Column(String(255), nullable=False, comment='家庭地址')
|
||||
post_code = Column(String(10), nullable=False, comment='邮政编码')
|
||||
phone = Column(String(50), nullable=False, comment='学生手机')
|
||||
educational_level = Column(String(128), comment='文化程度')
|
||||
school_of_graduation = Column(String(128), index=True, comment='毕业学校')
|
||||
graduate_date = Column(Date, comment='毕业日期')
|
||||
awards = Column(String(128), comment='奖励')
|
||||
hobby = Column(String(128), comment='爱好特长')
|
||||
cee_id = Column(String(50), comment='准考证号')
|
||||
cee_scores = Column(String(50), comment='高考总分')
|
||||
cee_english = Column(String(50), comment='英语成绩')
|
||||
cee_chinese = Column(String(50), comment='语文成绩')
|
||||
cee_math = Column(String(50), comment='数学成绩')
|
||||
cee_type = Column(String(50), comment='高考科类')
|
||||
ielts = Column(String(50), comment='雅思成绩')
|
||||
no_cee_reasons = Column(String(128), comment='不参加高考原因')
|
||||
major = Column(String(128), comment='首选专业')
|
||||
major2 = Column(String(128), comment='次选专业')
|
||||
major3 = Column(String(128), comment='再选专业')
|
||||
accommodation = Column(String(10), comment='是否住宿')
|
||||
allocate = Column(String(50), comment='服从调配')
|
||||
abroad = Column(String(50), comment='出国留学')
|
||||
parent_name1 = Column(String(128), nullable=False, comment='家长姓名')
|
||||
parent_phone1 = Column(String(50), nullable=False, comment='电话')
|
||||
relation1 = Column(String(10), comment='关系')
|
||||
parent_name2 = Column(String(128), comment='家长姓名')
|
||||
parent_phone2 = Column(String(50), comment='电话')
|
||||
relation2 = Column(String(10), comment='关系')
|
||||
information_source = Column(String(128), comment='信息来源')
|
||||
referrer = Column(String(128), comment='推荐人')
|
||||
admission_major = Column(String(128), comment='录取专业')
|
||||
admission_at = Column(DateTime, comment='录取时间')
|
||||
intensive_training = Column(String(50), comment='强化训练')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
remark = Column(String(255), comment='备注')
|
||||
created_at = Column(DateTime, nullable=False, comment='创建时间')
|
||||
|
||||
|
||||
class HatMajor(Base):
|
||||
__tablename__ = 'hat_major'
|
||||
__table_args__ = {'comment': '专业'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
name = Column(String(255), nullable=False, unique=True, comment='专业名称')
|
||||
name_en = Column(String(255), comment='英文名称')
|
||||
code = Column(String(255), nullable=False, comment='专业代码')
|
||||
description = Column(Text, comment='介绍')
|
||||
discipline = Column(String(255), nullable=False, comment='学科门类')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
|
||||
class HatPerson(Base):
|
||||
__tablename__ = 'hat_person'
|
||||
__table_args__ = (
|
||||
Index('hat_person_name_cer_idx', 'name', 'cer_no', 'cer_type_name'),
|
||||
{'comment': '企业人员'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
name = Column(String(100), nullable=False, server_default=text("''"), comment='姓名')
|
||||
sex = Column(String(1), nullable=False, server_default=text("''"), comment='性别')
|
||||
cer_type_name = Column(String(100), nullable=False, server_default=text("''"), comment='身份证件类型')
|
||||
cer_no = Column(String(40), nullable=False, server_default=text("''"), comment='证件号')
|
||||
tel = Column(String(110), nullable=False, server_default=text("''"), comment='联系电话')
|
||||
school = Column(String(200), nullable=False, server_default=text("''"), comment='毕业院校')
|
||||
edu_bac = Column(String(20), nullable=False, server_default=text("''"), comment='文化程度')
|
||||
major = Column(String(30), nullable=False, server_default=text("''"), comment='所学专业')
|
||||
lite_deg = Column(String(2), nullable=False, server_default=text("''"), comment='文化程度')
|
||||
edu_bac_code = Column(String(30), nullable=False, server_default=text("''"), comment='学历')
|
||||
title = Column(String(40), nullable=False, server_default=text("''"), comment='职称')
|
||||
com_addr = Column(String(512), nullable=False, server_default=text("''"), comment='通信地址')
|
||||
postal_code = Column(String(6), nullable=False, server_default=text("''"), comment='邮编编码')
|
||||
email = Column(String(100), nullable=False, server_default=text("''"), comment='电子邮箱')
|
||||
status = Column(String(64), nullable=False, server_default=text("''"), comment='状态')
|
||||
avatar = Column(String(300), server_default=text("''"), comment='头像')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
|
||||
|
||||
class HatStudent(Base):
|
||||
__tablename__ = 'hat_student'
|
||||
__table_args__ = {'comment': '学生信息表'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
student_number = Column(String(50), nullable=False, comment='学号')
|
||||
name = Column(String(128), nullable=False, comment='姓名')
|
||||
gender = Column(String(10), nullable=False, comment='性别')
|
||||
native_place = Column(String(50), comment='籍贯')
|
||||
id_card_number = Column(String(50), nullable=False, comment='身份证')
|
||||
province = Column(String(128), nullable=False, comment='省')
|
||||
city = Column(String(128), comment='市')
|
||||
date_of_birth = Column(Date, comment='出生年月')
|
||||
politics_status = Column(String(50), comment='政治面貌')
|
||||
nation = Column(String(128), comment='民族')
|
||||
house_address = Column(String(255), nullable=False, comment='家庭地址')
|
||||
post_code = Column(String(10), nullable=False, comment='邮政编码')
|
||||
phone = Column(String(50), nullable=False, comment='学生手机')
|
||||
educational_level = Column(String(128), comment='文化程度')
|
||||
school_of_graduation = Column(String(128), comment='毕业学校')
|
||||
graduate_time = Column(Date, comment='毕业时间')
|
||||
awards = Column(String(128), comment='奖励')
|
||||
hobby = Column(String(128), comment='爱好特长')
|
||||
cee_id = Column(String(50), comment='准考证号')
|
||||
cee_scores = Column(String(50), comment='高考总分')
|
||||
cee_english = Column(String(50), comment='英语成绩')
|
||||
cee_chinese = Column(String(50), comment='语文成绩')
|
||||
cee_math = Column(String(50), comment='数学成绩')
|
||||
cee_type = Column(String(50), comment='高考科类')
|
||||
ielts = Column(String(50), comment='雅思成绩')
|
||||
major = Column(String(128), comment='专业')
|
||||
allocate = Column(String(50), comment='服从调配')
|
||||
abroad = Column(String(50), comment='出国留学')
|
||||
parent_name1 = Column(String(128), nullable=False, comment='家长姓名')
|
||||
parent_phone1 = Column(String(50), nullable=False, comment='电话')
|
||||
relation1 = Column(String(10), comment='关系')
|
||||
parent_name2 = Column(String(128), comment='家长姓名')
|
||||
parent_phone2 = Column(String(50), comment='电话')
|
||||
relation2 = Column(String(10), comment='关系')
|
||||
intensive_training = Column(String(50), comment='强化训练')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, comment='创建时间')
|
||||
|
||||
|
||||
class HatUser(Base):
|
||||
__tablename__ = 'hat_user'
|
||||
__table_args__ = {'comment': '用户'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
username = Column(String(255), nullable=False, unique=True, comment='用户名')
|
||||
password_hash = Column(String(255), nullable=False, comment='密码')
|
||||
password_reset_token = Column(String(255), comment='重置标记')
|
||||
auth_key = Column(String(255), comment='授权码')
|
||||
status = Column(INTEGER(11), nullable=False, server_default=text("0"), comment='用户状态')
|
||||
type = Column(String(64), nullable=False, server_default=text("'user'"), comment='用户类型')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='更新时间')
|
||||
|
||||
|
||||
class HatArticlePublish(Base):
|
||||
__tablename__ = 'hat_article_publish'
|
||||
__table_args__ = (
|
||||
Index('hat_article_publish_un', 'article_id', 'category_id', unique=True),
|
||||
{'comment': '文章发布表'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
article_id = Column(ForeignKey('hat_article.id', ondelete='CASCADE'), nullable=False, comment='文章ID')
|
||||
category_id = Column(ForeignKey('hat_article_category.id', ondelete='CASCADE'), nullable=False, index=True, comment='文章类别ID')
|
||||
sort_order = Column(INTEGER(11), nullable=False, index=True, server_default=text("0"), comment='排序')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
article = relationship('HatArticle')
|
||||
category = relationship('HatArticleCategory')
|
||||
|
||||
|
||||
class HatClassesStudent(Base):
|
||||
__tablename__ = 'hat_classes_student'
|
||||
__table_args__ = (
|
||||
Index('classes_id_student_id_un', 'class_id', 'student_id', unique=True),
|
||||
{'comment': '班级学生关系表'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
class_id = Column(ForeignKey('hat_classes.id', ondelete='CASCADE'), nullable=False, comment='班级编号')
|
||||
student_id = Column(ForeignKey('hat_student.id', ondelete='CASCADE'), nullable=False, index=True, comment='学生编号')
|
||||
created_at = Column(DateTime, nullable=False, comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, comment='更新时间')
|
||||
|
||||
_class = relationship('HatClass')
|
||||
student = relationship('HatStudent')
|
||||
|
||||
|
||||
class HatCourseScheduleDetail(Base):
|
||||
__tablename__ = 'hat_course_schedule_detail'
|
||||
__table_args__ = {'comment': '课表明细'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
schedule_id = Column(ForeignKey('hat_course_schedule.id', ondelete='CASCADE'), nullable=False, index=True, comment='课表')
|
||||
course_id = Column(ForeignKey('hat_course.id'), nullable=False, index=True, comment='课程')
|
||||
classroom_id = Column(ForeignKey('hat_classroom.id'), nullable=False, index=True, comment='教室')
|
||||
teacher = Column(String(255), nullable=False, comment='任课老师')
|
||||
week_day = Column(INTEGER(20), nullable=False, comment='上课日期,0~1为周日~周六')
|
||||
sequence = Column(INTEGER(20), nullable=False, comment='序号,从1开始,代表是一天中的第几节课')
|
||||
|
||||
classroom = relationship('HatClassroom')
|
||||
course = relationship('HatCourse')
|
||||
schedule = relationship('HatCourseSchedule')
|
||||
|
||||
|
||||
class HatExamPaper(Base):
|
||||
__tablename__ = 'hat_exam_paper'
|
||||
__table_args__ = {'comment': '考卷'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
name = Column(String(255), nullable=False, comment='考卷名称')
|
||||
course_id = Column(ForeignKey('hat_course.id'), nullable=False, index=True, comment='考试课程')
|
||||
score = Column(Float(asdecimal=True), server_default=text("0"), comment='分值')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
course = relationship('HatCourse')
|
||||
|
||||
|
||||
class HatExamination(Base):
|
||||
__tablename__ = 'hat_examination'
|
||||
__table_args__ = {'comment': '考务表'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
course_id = Column(ForeignKey('hat_course.id'), nullable=False, index=True, comment='考试课程')
|
||||
academic_year = Column(String(200), nullable=False, comment='学年')
|
||||
semester = Column(String(200), nullable=False, comment='学期')
|
||||
exam_time = Column(DateTime, nullable=False, comment='考试时间')
|
||||
classroom_id = Column(ForeignKey('hat_classroom.id'), index=True, comment='考场教室')
|
||||
exam_paper_id = Column(BIGINT(20), comment='考卷')
|
||||
exam_format = Column(String(50), nullable=False, comment='考试形式,线下、线上')
|
||||
exam_method = Column(String(50), nullable=False, comment='考试方式,开卷、闭卷')
|
||||
exam_type = Column(String(50), nullable=False, comment='考试性质,入学、期中、期末、补考、重修')
|
||||
time_length = Column(INTEGER(20), server_default=text("60"), comment='考试时长')
|
||||
chief_examiner = Column(String(50), nullable=False, comment='主考老师')
|
||||
invigilator = Column(String(50), comment='监考老师')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
classroom = relationship('HatClassroom')
|
||||
course = relationship('HatCourse')
|
||||
|
||||
|
||||
class HatQuestionMaterial(Base):
|
||||
__tablename__ = 'hat_question_material'
|
||||
__table_args__ = {'comment': '考题素材(考题用到的素材,目前仅支持文字素材,可增加附件用于增加其他素材,如图片、声音等)'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
title = Column(String(255), nullable=False, comment='标题')
|
||||
content = Column(Text, nullable=False, comment='文章内容')
|
||||
course_id = Column(ForeignKey('hat_course.id'), nullable=False, index=True, comment='课程')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
course = relationship('HatCourse')
|
||||
|
||||
|
||||
class HatStudentPortrait(Base):
|
||||
__tablename__ = 'hat_student_portrait'
|
||||
__table_args__ = {'comment': '学生头像'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
student_id = Column(ForeignKey('hat_student.id', ondelete='CASCADE'), nullable=False, unique=True, comment='考生')
|
||||
portrait = Column(String(500), nullable=False, comment='头像')
|
||||
created_at = Column(DateTime, nullable=False, comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, comment='更新时间')
|
||||
|
||||
student = relationship('HatStudent')
|
||||
|
||||
|
||||
class HatStudentUnusual(Base):
|
||||
__tablename__ = 'hat_student_unusual'
|
||||
__table_args__ = {'comment': '学生异动'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
student_id = Column(ForeignKey('hat_student.id'), nullable=False, index=True, comment='考生')
|
||||
type = Column(String(100), nullable=False, comment='异动类型')
|
||||
memo = Column(String(1024), comment='备注')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
student = relationship('HatStudent')
|
||||
|
||||
|
||||
class HatUserPerson(Base):
|
||||
__tablename__ = 'hat_user_person'
|
||||
__table_args__ = {'comment': '用户人员'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='ID')
|
||||
user_id = Column(ForeignKey('hat_user.id'), nullable=False, unique=True, comment='用户ID')
|
||||
person_id = Column(ForeignKey('hat_person.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, index=True, comment='人员ID')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='更新时间')
|
||||
|
||||
person = relationship('HatPerson')
|
||||
user = relationship('HatUser')
|
||||
|
||||
|
||||
class HatEnrollStudentExam(Base):
|
||||
__tablename__ = 'hat_enroll_student_exam'
|
||||
__table_args__ = (
|
||||
Index('hat_enroll_student_exam_un', 'examination_id', 'student_id', unique=True),
|
||||
{'comment': '参加入学考试的学生'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
examination_id = Column(ForeignKey('hat_examination.id'), nullable=False, comment='考务编号')
|
||||
student_id = Column(ForeignKey('hat_enroll_student.id'), nullable=False, index=True, comment='考生')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
examination = relationship('HatExamination')
|
||||
student = relationship('HatEnrollStudent')
|
||||
|
||||
|
||||
class HatEnrollStudentScore(Base):
|
||||
__tablename__ = 'hat_enroll_student_score'
|
||||
__table_args__ = (
|
||||
CheckConstraint('json_valid(`answer`)'),
|
||||
CheckConstraint('json_valid(`question_score`)'),
|
||||
Index('hat_enroll_student_score_un', 'examination_id', 'student_id', unique=True),
|
||||
{'comment': '入学考试成绩'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
examination_id = Column(ForeignKey('hat_examination.id'), nullable=False, comment='考务安排')
|
||||
student_id = Column(ForeignKey('hat_enroll_student.id'), nullable=False, index=True, comment='考生')
|
||||
started_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='考试开始时间')
|
||||
submit_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='交卷时间')
|
||||
submit_method = Column(String(16), nullable=False, server_default=text("'N'"), comment='交卷方式')
|
||||
answer = Column(Text, nullable=False, comment='答案(JSON数据)')
|
||||
question_score = Column(Text, comment='各题得分')
|
||||
test_score = Column(String(100), nullable=False, comment='考试成绩')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='更新时间')
|
||||
|
||||
examination = relationship('HatExamination')
|
||||
student = relationship('HatEnrollStudent')
|
||||
|
||||
|
||||
class HatQuestion(Base):
|
||||
__tablename__ = 'hat_question'
|
||||
__table_args__ = {'comment': '考题(可关联到素材表,对关联到同一素材的所有考题,按照时间先后排序)'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
question = Column(String(1800), nullable=False, comment='提问')
|
||||
options = Column(String(1800), comment='选项(json)')
|
||||
answer = Column(String(1800), nullable=False, comment='答案(json)')
|
||||
category = Column(String(50), nullable=False, comment='题型(判断题、单项选择题、多项选择题、不定项选择题、填空题、完形填空、阅读理解、简答题、论述题、作文)')
|
||||
course_id = Column(ForeignKey('hat_course.id'), nullable=False, index=True, comment='课程')
|
||||
material_id = Column(ForeignKey('hat_question_material.id'), index=True, comment='素材')
|
||||
score = Column(Float(asdecimal=True), server_default=text("0"), comment='分值')
|
||||
difficulty = Column(Float(asdecimal=True), server_default=text("1"), comment='难度系数')
|
||||
status = Column(INTEGER(20), nullable=False, server_default=text("10"), comment='当前状态')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
course = relationship('HatCourse')
|
||||
material = relationship('HatQuestionMaterial')
|
||||
|
||||
|
||||
class HatStudentAttendance(Base):
|
||||
__tablename__ = 'hat_student_attendance'
|
||||
__table_args__ = {'comment': '学生考勤'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
student_id = Column(ForeignKey('hat_student.id'), nullable=False, index=True, comment='考生')
|
||||
schedule_detail_id = Column(ForeignKey('hat_course_schedule_detail.id'), nullable=False, index=True, comment='课表明细编号')
|
||||
sequence = Column(INTEGER(20), nullable=False, comment='序号')
|
||||
time_at = Column(DateTime, nullable=False, comment='考勤时间')
|
||||
type = Column(String(100), nullable=False, comment='考勤类型')
|
||||
memo = Column(String(1024), comment='备注')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
schedule_detail = relationship('HatCourseScheduleDetail')
|
||||
student = relationship('HatStudent')
|
||||
|
||||
|
||||
class HatStudentExam(Base):
|
||||
__tablename__ = 'hat_student_exam'
|
||||
__table_args__ = (
|
||||
Index('hat_student_exam_un', 'examination_id', 'student_id', unique=True),
|
||||
{'comment': '参加考试的学生'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
examination_id = Column(ForeignKey('hat_examination.id', ondelete='CASCADE'), nullable=False, comment='考务编号')
|
||||
student_id = Column(ForeignKey('hat_student.id', ondelete='CASCADE'), nullable=False, index=True, comment='考生')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
created_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='创建者')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='修改时间')
|
||||
updated_by = Column(String(64), nullable=False, server_default=text("'API'"), comment='修改者')
|
||||
|
||||
examination = relationship('HatExamination')
|
||||
student = relationship('HatStudent')
|
||||
|
||||
|
||||
class HatStudentScore(Base):
|
||||
__tablename__ = 'hat_student_score'
|
||||
__table_args__ = (
|
||||
CheckConstraint('json_valid(`answer`)'),
|
||||
CheckConstraint('json_valid(`question_score`)'),
|
||||
Index('hat_student_score_un', 'examination_id', 'student_id', unique=True),
|
||||
{'comment': '考试成绩'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
examination_id = Column(ForeignKey('hat_examination.id'), nullable=False, comment='考务安排')
|
||||
student_id = Column(ForeignKey('hat_student.id'), nullable=False, index=True, comment='考生')
|
||||
started_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='考试开始时间')
|
||||
submit_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='交卷时间')
|
||||
submit_method = Column(String(16), nullable=False, server_default=text("'N'"), comment='交卷方式')
|
||||
answer = Column(Text, nullable=False, comment='答案(JSON数据)')
|
||||
question_score = Column(Text, comment='各题得分')
|
||||
test_score = Column(String(100), nullable=False, comment='考试成绩')
|
||||
created_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, server_default=text("current_timestamp()"), comment='更新时间')
|
||||
|
||||
examination = relationship('HatExamination')
|
||||
student = relationship('HatStudent')
|
||||
|
||||
|
||||
class HatStudentUnusualAttachment(Base):
|
||||
__tablename__ = 'hat_student_unusual_attachment'
|
||||
__table_args__ = {'comment': '学生异动附件'}
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
unusual_id = Column(ForeignKey('hat_student_unusual.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, index=True, comment='异动编号')
|
||||
name = Column(String(255), nullable=False, comment='附件名称')
|
||||
created_at = Column(DateTime, nullable=False, comment='创建时间')
|
||||
updated_at = Column(DateTime, nullable=False, comment='更新时间')
|
||||
|
||||
unusual = relationship('HatStudentUnusual')
|
||||
|
||||
|
||||
class HatExamPaperQuestion(Base):
|
||||
__tablename__ = 'hat_exam_paper_question'
|
||||
__table_args__ = (
|
||||
Index('hat_exam_paper_question_un', 'exam_paper_id', 'question_id', unique=True),
|
||||
{'comment': '考卷考题'}
|
||||
)
|
||||
|
||||
id = Column(BIGINT(20), primary_key=True, comment='系统编号')
|
||||
exam_paper_id = Column(ForeignKey('hat_exam_paper.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, comment='考卷')
|
||||
question_id = Column(ForeignKey('hat_question.id'), nullable=False, index=True, comment='考题')
|
||||
sort = Column(INTEGER(20), nullable=False, server_default=text("0"), comment='排序')
|
||||
|
||||
exam_paper = relationship('HatExamPaper')
|
||||
question = relationship('HatQuestion')
|
||||
@@ -0,0 +1,153 @@
|
||||
import numpy as np
|
||||
import os
|
||||
import traceback
|
||||
from paste.chart.bar import (
|
||||
gen_vertical_bars,
|
||||
gen_horizontal_stacked_bars,
|
||||
gen_percent_stacked_bars
|
||||
)
|
||||
|
||||
|
||||
class ChartBarExample:
|
||||
"""
|
||||
图表测试管理器:封装对 paste.chart.bar 中三个函数的调用。
|
||||
不修改任何参数结构,仅提供清晰的调用封装与输出管理。
|
||||
"""
|
||||
|
||||
def __init__(self, output_directory="./charts"):
|
||||
"""
|
||||
初始化测试器,定义所有测试数据。
|
||||
数据结构完全匹配原始函数调用方式。
|
||||
"""
|
||||
self.output_directory = output_directory
|
||||
os.makedirs(self.output_directory, exist_ok=True)
|
||||
|
||||
# 纵向堆叠柱形图数据(直接对应 gen_vertical_bars 参数)
|
||||
self.primary_vals = [10, 20, 15, 25, 18]
|
||||
self.nested_vals = [5, 8, 3, 10, 6]
|
||||
self.x_labels_vert = ['产品1', '产品2', '产品3', '产品4', '产品5']
|
||||
self.group_labels_vert = ['销售量', '退货量']
|
||||
|
||||
# 横向堆叠柱形图数据(直接对应 gen_horizontal_stacked_bars 参数)
|
||||
self.data_matrix = np.array([
|
||||
[10, 20, 15],
|
||||
[15, 12, 18],
|
||||
[8, 16, 10],
|
||||
[12, 14, 13]
|
||||
])
|
||||
self.x_labels_hori = ['线上销售', '门店销售', '批发销售']
|
||||
self.y_labels_hori = ['北京', '上海', '广州', '深圳']
|
||||
self.y_data_unit_hori = '万元'
|
||||
self.title_hori = '销售构成'
|
||||
|
||||
# 百分比堆叠柱形图数据(直接对应 gen_percent_stacked_bars 参数)
|
||||
self.data_percent = {
|
||||
'A组': [10, 20, 15, 18],
|
||||
'B组': [5, 10, 5, 8],
|
||||
'C组': [3, 7, 10, 4]
|
||||
}
|
||||
self.x_labels_percent = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
self.title_percent = '季度占比'
|
||||
|
||||
def generate_vertical_bars(self) -> str:
|
||||
"""调用 gen_vertical_bars,参数完全一致"""
|
||||
try:
|
||||
return gen_vertical_bars(
|
||||
self.primary_vals,
|
||||
self.nested_vals,
|
||||
self.x_labels_vert,
|
||||
self.group_labels_vert
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"纵向堆叠柱形图生成失败: {e}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def generate_horizontal_stacked_bars(self) -> str:
|
||||
"""调用 gen_horizontal_stacked_bars,参数完全一致"""
|
||||
try:
|
||||
return gen_horizontal_stacked_bars(
|
||||
self.data_matrix,
|
||||
self.x_labels_hori,
|
||||
self.y_labels_hori,
|
||||
self.y_data_unit_hori,
|
||||
self.title_hori
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"横向堆叠柱形图生成失败: {e}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def generate_percent_stacked_bars(self) -> str:
|
||||
"""调用 gen_percent_stacked_bars,参数完全一致"""
|
||||
try:
|
||||
return gen_percent_stacked_bars(
|
||||
self.data_percent,
|
||||
self.x_labels_percent,
|
||||
self.title_percent
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"百分比堆叠柱形图生成失败: {e}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def save_svg(self, svg_data: str, filename: str) -> None:
|
||||
"""
|
||||
将 SVG 的 base64 Data URL 写入文件(保留原始 SVG 格式)。
|
||||
注意:svg_data 是 "data:image/svg+xml;base64,...",需提取真实 SVG 内容。
|
||||
"""
|
||||
if not svg_data or not isinstance(svg_data, str):
|
||||
print(f"生成的 SVG 数据无效(为空或非字符串): {filename}")
|
||||
return
|
||||
|
||||
# 提取 base64 编码部分(去除 data URL 前缀)
|
||||
if svg_data.startswith("data:image/svg+xml;base64,"):
|
||||
base64_content = svg_data[len("data:image/svg+xml;base64,"):]
|
||||
try:
|
||||
# 解码 base64 得到原始 SVG 字符串
|
||||
import base64
|
||||
svg_content = base64.b64decode(base64_content).decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"解码 base64 失败: {e}")
|
||||
svg_content = svg_data # 退化为直接写入
|
||||
else:
|
||||
# 如果不是标准格式,直接写入(兼容调试)
|
||||
svg_content = svg_data
|
||||
|
||||
filepath = os.path.join(self.output_directory, filename)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(svg_content)
|
||||
print(f"已保存: {filepath}")
|
||||
|
||||
def run(self) -> None:
|
||||
"""按顺序执行所有图表生成与保存"""
|
||||
print("开始生成图表...")
|
||||
try:
|
||||
print("生成纵向堆叠柱形图...")
|
||||
svg1 = self.generate_vertical_bars()
|
||||
self.save_svg(svg1, "vertical_bars.svg")
|
||||
|
||||
print("生成横向堆叠柱形图...")
|
||||
svg2 = self.generate_horizontal_stacked_bars()
|
||||
self.save_svg(svg2, "horizontal_stacked_bars.svg")
|
||||
|
||||
print("生成百分比堆叠柱形图...")
|
||||
svg3 = self.generate_percent_stacked_bars()
|
||||
self.save_svg(svg3, "percent_stacked_bars.svg")
|
||||
|
||||
print("\n所有图表已成功生成。")
|
||||
print(f"输出目录: {self.output_directory}")
|
||||
print("文件列表:")
|
||||
print(" - vertical_bars.svg")
|
||||
print(" - horizontal_stacked_bars.svg")
|
||||
print(" - percent_stacked_bars.svg")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n测试失败: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# 程序入口
|
||||
if __name__ == "__main__":
|
||||
tester = ChartBarExample()
|
||||
tester.run()
|
||||
@@ -0,0 +1,110 @@
|
||||
import os
|
||||
import traceback
|
||||
from paste.chart.pie import gen_pie
|
||||
|
||||
|
||||
class ChartPieExample:
|
||||
"""
|
||||
环形图测试管理器:封装对 paste.chart.pie.gen_pie 的调用。
|
||||
数据结构严格匹配函数参数要求,支持扩展更多测试用例。
|
||||
"""
|
||||
|
||||
def __init__(self, output_directory="./charts"):
|
||||
"""
|
||||
初始化测试器,定义所有测试数据。
|
||||
数据结构完全匹配 gen_pie 函数的参数要求。
|
||||
"""
|
||||
self.output_directory = output_directory
|
||||
os.makedirs(self.output_directory, exist_ok=True)
|
||||
|
||||
# 构造符合 gen_pie 要求的 DataFrame 数据(模拟真实业务场景)
|
||||
# 假设业务场景:网络设备统计(服务器、交换机、路由器等)
|
||||
import pandas as pd
|
||||
self.data_df = pd.DataFrame({
|
||||
'device_count': [35, 28, 22, 15, 10], # value_column
|
||||
'percentage': ['35.2%', '28.1%', '22.0%', '15.0%', '9.7%'], # percentage_column
|
||||
'device_type': ['服务器', '交换机', '路由器', '防火墙', 'AP'] # legend_labels
|
||||
})
|
||||
|
||||
# 测试参数
|
||||
self.value_column = 'device_count'
|
||||
self.percentage_column = 'percentage'
|
||||
self.legend_labels = 'device_type'
|
||||
self.color_palette = 'BuPu' # 可尝试 'viridis', 'Set3', 'plasma'
|
||||
self.dpi = 128
|
||||
|
||||
# 输出文件名
|
||||
self.filename = "pie_chart.svg"
|
||||
|
||||
def generate_pie_chart(self) -> str:
|
||||
"""
|
||||
调用 gen_pie 函数,参数完全一致。
|
||||
注意:gen_pie 接收的是 pandas.DataFrame,不是列表。
|
||||
"""
|
||||
try:
|
||||
svg_data = gen_pie(
|
||||
data_df=self.data_df,
|
||||
value_column=self.value_column,
|
||||
percentage_column=self.percentage_column,
|
||||
legend_labels=self.legend_labels,
|
||||
color_palette=self.color_palette,
|
||||
dpi=self.dpi
|
||||
)
|
||||
if not svg_data or not isinstance(svg_data, str):
|
||||
raise ValueError("gen_pie 返回的 SVG 数据为空或类型错误")
|
||||
return svg_data
|
||||
except Exception as e:
|
||||
print(f"环形图生成失败: {e}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def save_svg(self, svg_data: str, filename: str) -> None:
|
||||
"""
|
||||
将 SVG 的 base64 Data URL 写入文件(保留原始 SVG 格式)。
|
||||
注意:svg_data 是 "data:image/svg+xml;base64,...",需提取真实 SVG 内容。
|
||||
"""
|
||||
if not svg_data or not isinstance(svg_data, str):
|
||||
print(f"生成的 SVG 数据无效(为空或非字符串): {filename}")
|
||||
return
|
||||
|
||||
# 提取 base64 编码部分(去除 data URL 前缀)
|
||||
if svg_data.startswith("data:image/svg+xml;base64,"):
|
||||
base64_content = svg_data[len("data:image/svg+xml;base64,"):]
|
||||
try:
|
||||
# 解码 base64 得到原始 SVG 字符串
|
||||
import base64
|
||||
svg_content = base64.b64decode(base64_content).decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"解码 base64 失败: {e}")
|
||||
svg_content = svg_data # 退化为直接写入
|
||||
else:
|
||||
# 如果不是标准格式,直接写入(兼容调试)
|
||||
svg_content = svg_data
|
||||
|
||||
filepath = os.path.join(self.output_directory, filename)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(svg_content)
|
||||
print(f"已保存: {filepath}")
|
||||
|
||||
def run(self) -> None:
|
||||
"""按顺序执行图表生成与保存"""
|
||||
print("开始生成环形图...")
|
||||
try:
|
||||
print("正在生成环形图...")
|
||||
svg_data = self.generate_pie_chart()
|
||||
self.save_svg(svg_data, self.filename)
|
||||
|
||||
print(f"\n环形图已成功生成。")
|
||||
print(f"输出目录: {self.output_directory}")
|
||||
print(f"文件列表:")
|
||||
print(f" - {self.filename}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n测试失败: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# 程序入口
|
||||
if __name__ == "__main__":
|
||||
tester = ChartPieExample()
|
||||
tester.run()
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Charts</title>
|
||||
</head>
|
||||
<body>
|
||||
<img src="./horizontal_stacked_bars.svg" alt="">
|
||||
<img src="./lines.svg" alt="">
|
||||
<img src="./percent_stacked_bars.svg" alt="">
|
||||
<img src="./pie_chart.svg" alt="">
|
||||
<img src="./splines.svg" alt="">
|
||||
<img src="./vertical_bars.svg" alt="">
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 50 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 36 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 34 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 37 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 36 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,124 @@
|
||||
import pandas as pd
|
||||
import os
|
||||
from paste.chart.line import gen_lines, gen_splines
|
||||
|
||||
|
||||
class LineChartExample:
|
||||
"""
|
||||
折线图与平滑曲线测试管理器。
|
||||
封装 gen_lines 和 gen_splines 的调用,统一输出管理。
|
||||
"""
|
||||
|
||||
def __init__(self, output_directory="./charts"):
|
||||
"""
|
||||
初始化测试器,定义所有测试数据。
|
||||
使用 'ME' 替代 'M' 以兼容 pandas >=2.0。
|
||||
"""
|
||||
self.output_directory = output_directory
|
||||
os.makedirs(self.output_directory, exist_ok=True)
|
||||
|
||||
# =========================
|
||||
# gen_lines 测试数据(使用 'ME' 替代废弃的 'M')
|
||||
# =========================
|
||||
# 时间序列数据:3个年份,每月最后一个交易日(12个月)
|
||||
dates_2022 = pd.date_range('2022-01-01', periods=12, freq='ME')
|
||||
dates_2023 = pd.date_range('2023-01-01', periods=12, freq='ME')
|
||||
dates_2024 = pd.date_range('2024-01-01', periods=12, freq='ME')
|
||||
|
||||
self.data_dict_lines = {
|
||||
'2022': pd.Series([100 + i * 2 for i in range(12)], index=dates_2022),
|
||||
'2023': pd.Series([110 + i * 1.5 for i in range(12)], index=dates_2023),
|
||||
'2024': pd.Series([120 + i * 1 for i in range(12)], index=dates_2024),
|
||||
}
|
||||
|
||||
self.color_palette_lines = 'BuPu'
|
||||
self.dpi_lines = 128
|
||||
|
||||
# =========================
|
||||
# gen_splines 测试数据(数值索引,无频率问题)
|
||||
# =========================
|
||||
self.data_dict_splines = {
|
||||
'A组': pd.Series([10, 15, 12, 18, 16], index=[0, 1, 2, 3, 4]),
|
||||
'B组': pd.Series([12, 14, 13, 17, 15], index=[0, 1, 2, 3, 4]),
|
||||
'C组': pd.Series([8, 20, 10, 22, 11], index=[0, 1, 2, 3, 4]),
|
||||
}
|
||||
|
||||
self.total_splines = pd.Series({
|
||||
'A组': 71,
|
||||
'B组': 71,
|
||||
'C组': 71
|
||||
})
|
||||
|
||||
self.color_palette_splines = 'viridis'
|
||||
self.dpi_splines = 128
|
||||
self.smooth_points = 100
|
||||
self.spline_k = 3
|
||||
self.markevery = 30
|
||||
|
||||
def generate_lines(self) -> str:
|
||||
"""调用 gen_lines,参数顺序完全匹配原始函数"""
|
||||
return gen_lines(
|
||||
self.data_dict_lines,
|
||||
self.color_palette_lines,
|
||||
self.dpi_lines
|
||||
)
|
||||
|
||||
def generate_splines(self) -> str:
|
||||
"""调用 gen_splines,参数顺序完全匹配原始函数"""
|
||||
return gen_splines(
|
||||
self.data_dict_splines,
|
||||
self.total_splines,
|
||||
None,
|
||||
self.color_palette_splines,
|
||||
self.dpi_splines
|
||||
)
|
||||
|
||||
def save_svg(self, svg_data: str, filename: str) -> None:
|
||||
"""
|
||||
将 SVG 的 base64 Data URL 写入文件(保留原始 SVG 格式)。
|
||||
注意:svg_data 是 "data:image/svg+xml;base64,...",需提取真实 SVG 内容。
|
||||
"""
|
||||
if not svg_data or not isinstance(svg_data, str):
|
||||
print(f"生成的 SVG 数据无效(为空或非字符串): {filename}")
|
||||
return
|
||||
|
||||
# 提取 base64 编码部分(去除 data URL 前缀)
|
||||
if svg_data.startswith("data:image/svg+xml;base64,"):
|
||||
base64_content = svg_data[len("data:image/svg+xml;base64,"):]
|
||||
try:
|
||||
# 解码 base64 得到原始 SVG 字符串
|
||||
import base64
|
||||
svg_content = base64.b64decode(base64_content).decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"解码 base64 失败: {e}")
|
||||
svg_content = svg_data # 退化为直接写入
|
||||
else:
|
||||
# 如果不是标准格式,直接写入(兼容调试)
|
||||
svg_content = svg_data
|
||||
|
||||
filepath = os.path.join(self.output_directory, filename)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(svg_content)
|
||||
print(f"已保存: {filepath}")
|
||||
|
||||
def run(self) -> None:
|
||||
"""按顺序生成并保存所有图表"""
|
||||
print("生成折线图...")
|
||||
svg1 = self.generate_lines()
|
||||
self.save_svg(svg1, "lines.svg")
|
||||
|
||||
print("生成平滑曲线图...")
|
||||
svg2 = self.generate_splines()
|
||||
self.save_svg(svg2, "splines.svg")
|
||||
|
||||
print("\n所有图表已生成。")
|
||||
print(f"输出目录: {self.output_directory}")
|
||||
print("文件列表:")
|
||||
print(" - lines.svg")
|
||||
print(" - splines.svg")
|
||||
|
||||
|
||||
# 程序入口
|
||||
if __name__ == "__main__":
|
||||
tester = LineChartExample()
|
||||
tester.run()
|
||||
@@ -0,0 +1,47 @@
|
||||
## 批量接口调用范例
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph 真实场景
|
||||
S1["爬虫<br/>1000个页面"]
|
||||
S2["批量下单<br/>500个订单"]
|
||||
S3["数据同步<br/>5个系统"]
|
||||
S4["压测<br/>自己服务器"]
|
||||
end
|
||||
|
||||
subgraph Paste方案
|
||||
P1["new_http_request × N"]
|
||||
P2["async_concurrency 一发"]
|
||||
P3["统一处理响应"]
|
||||
end
|
||||
|
||||
S1 --> P1 --> P2 --> P3
|
||||
S2 --> P1 --> P2 --> P3
|
||||
S3 --> P1 --> P2 --> P3
|
||||
S4 --> P1 --> P2 --> P3
|
||||
```
|
||||
|
||||
### 支持的能力
|
||||
|
||||
- GET / POST / JSON / Form / 文件上传
|
||||
- 自动随机 User-Agent
|
||||
- 自动提取 Cookie
|
||||
- 批量并发控制
|
||||
- 自动重试
|
||||
- 统一响应/错误处理
|
||||
|
||||
### 代码量
|
||||
|
||||
- Java: 200-300 行
|
||||
- Paste: **20 行**
|
||||
|
||||
### 示例
|
||||
|
||||
```python
|
||||
# 准备1000个请求
|
||||
for i in range(1000):
|
||||
req = new_http_request(url, method="POST", body={"id": i})
|
||||
await queue.put(req)
|
||||
|
||||
# 批量发出
|
||||
await async_concurrency(queue, con_count=50)
|
||||
Reference in New Issue
Block a user