增加范例

This commit is contained in:
zwf
2026-06-02 16:30:48 +08:00
parent 291e6fcaae
commit f4e7e1b3d2
26 changed files with 9578 additions and 0 deletions
+36
View File
@@ -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"
}
+16
View File
@@ -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!")
+20
View File
@@ -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()
+36
View File
@@ -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"
}
+34
View File
@@ -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!")
+20
View File
@@ -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()
+52
View File
@@ -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"
}
+69
View File
@@ -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)
+20
View File
@@ -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()
+152
View File
@@ -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()
+21
View File
@@ -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"
}
+96
View File
@@ -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()
+45
View File
@@ -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"
}
+11
View File
@@ -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())
+576
View File
@@ -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()
+47
View File
@@ -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)