import shutil from aerich import Command from fastapi import FastAPI from fastapi.middleware import Middleware from fastapi.middleware.cors import CORSMiddleware from tortoise.expressions import Q from app.api import api_router from app.controllers.api import api_controller from app.controllers.user import UserCreate, user_controller from app.core.exceptions import ( DoesNotExist, DoesNotExistHandle, HTTPException, HttpExcHandle, IntegrityError, IntegrityHandle, RequestValidationError, RequestValidationHandle, ResponseValidationError, ResponseValidationHandle, ) from app.log import logger from app.models.admin import Api, Menu, Role from app.models.invoice import Invoice, PaymentReceipt from app.schemas.menus import MenuType from app.settings.config import settings from .middlewares import BackGroundTaskMiddleware, HttpAuditLogMiddleware def make_middlewares(): middleware = [ Middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=settings.CORS_ALLOW_CREDENTIALS, allow_methods=settings.CORS_ALLOW_METHODS, allow_headers=settings.CORS_ALLOW_HEADERS, ), Middleware(BackGroundTaskMiddleware), Middleware( HttpAuditLogMiddleware, methods=["GET", "POST", "PUT", "DELETE"], exclude_paths=[ "/api/v1/base/access_token", "/docs", "/openapi.json", "/static", # 排除静态文件路径 ], ), ] return middleware def register_exceptions(app: FastAPI): app.add_exception_handler(DoesNotExist, DoesNotExistHandle) app.add_exception_handler(HTTPException, HttpExcHandle) app.add_exception_handler(IntegrityError, IntegrityHandle) app.add_exception_handler(RequestValidationError, RequestValidationHandle) app.add_exception_handler(ResponseValidationError, ResponseValidationHandle) def register_routers(app: FastAPI, prefix: str = "/api"): app.include_router(api_router, prefix=prefix) async def init_superuser(): user = await user_controller.model.exists() if not user: await user_controller.create_user( UserCreate( username="admin", email="admin@admin.com", password="123456", is_active=True, is_superuser=True, ) ) async def init_menus(): menus = await Menu.exists() if not menus: parent_menu = await Menu.create( menu_type=MenuType.CATALOG, name="系统管理", path="/system", order=1, parent_id=0, icon="carbon:gui-management", is_hidden=False, component="Layout", keepalive=False, redirect="/system/user", ) children_menu = [ Menu( menu_type=MenuType.MENU, name="用户管理", path="user", order=1, parent_id=parent_menu.id, icon="material-symbols:person-outline-rounded", is_hidden=False, component="/system/user", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="角色管理", path="role", order=2, parent_id=parent_menu.id, icon="carbon:user-role", is_hidden=False, component="/system/role", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="菜单管理", path="menu", order=3, parent_id=parent_menu.id, icon="material-symbols:list-alt-outline", is_hidden=False, component="/system/menu", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="API管理", path="api", order=4, parent_id=parent_menu.id, icon="ant-design:api-outlined", is_hidden=False, component="/system/api", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="部门管理", path="dept", order=5, parent_id=parent_menu.id, icon="mingcute:department-line", is_hidden=False, component="/system/dept", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="审计日志", path="auditlog", order=6, parent_id=parent_menu.id, icon="ph:clipboard-text-bold", is_hidden=False, component="/system/auditlog", keepalive=False, ), ] await Menu.bulk_create(children_menu) # 创建系统数据管理菜单 data_menu = await Menu.create( menu_type=MenuType.CATALOG, name="系统数据", path="/data", order=2, parent_id=0, icon="carbon:data-base", is_hidden=False, component="Layout", keepalive=False, redirect="/data/industry", ) data_children_menu = [ Menu( menu_type=MenuType.MENU, name="行业修正", path="industry", order=1, parent_id=data_menu.id, icon="carbon:industry", is_hidden=False, component="/data/industry", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="政策匹配", path="policy", order=2, parent_id=data_menu.id, icon="carbon:policy", is_hidden=False, component="/data/policy", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="ESG关联", path="esg", order=3, parent_id=data_menu.id, icon="carbon:earth-southeast-asia", is_hidden=False, component="/data/esg", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="行业基准", path="index", order=4, parent_id=data_menu.id, icon="carbon:chart-line", is_hidden=False, component="/data/index", keepalive=False, ), ] await Menu.bulk_create(data_children_menu) await Menu.create( menu_type=MenuType.MENU, name="一级菜单", path="/top-menu", order=2, parent_id=0, icon="material-symbols:featured-play-list-outline", is_hidden=False, component="/top-menu", keepalive=False, redirect="", ) # 创建交易管理菜单 transaction_menu = await Menu.create( menu_type=MenuType.CATALOG, name="交易管理", path="/transaction", order=3, parent_id=0, icon="carbon:wallet", is_hidden=False, component="Layout", keepalive=False, redirect="/transaction/invoice", ) transaction_children = [ Menu( menu_type=MenuType.MENU, name="发票管理", path="invoice", order=1, parent_id=transaction_menu.id, icon="mdi:file-document-outline", is_hidden=False, component="/transaction/invoice", keepalive=False, ), Menu( menu_type=MenuType.MENU, name="交易记录", path="receipts", order=2, parent_id=transaction_menu.id, icon="mdi:receipt-text-outline", is_hidden=False, component="/transaction/receipts", keepalive=False, ), ] await Menu.bulk_create(transaction_children) async def init_apis(): apis = await api_controller.model.exists() if not apis: await api_controller.refresh_api() async def init_db(): command = Command(tortoise_config=settings.TORTOISE_ORM) try: await command.init_db(safe=True) except FileExistsError: pass await command.init() try: await command.migrate() except AttributeError: logger.warning("unable to retrieve model history from database, model history will be created from scratch") shutil.rmtree("migrations") await command.init_db(safe=True) await command.upgrade(run_in_transaction=True) async def init_roles(): roles = await Role.exists() if not roles: admin_role = await Role.create( name="管理员", desc="管理员角色", ) user_role = await Role.create( name="普通用户", desc="普通用户角色", ) # 分配所有API给管理员角色 all_apis = await Api.all() await admin_role.apis.add(*all_apis) # 分配所有菜单给管理员和普通用户 all_menus = await Menu.all() await admin_role.menus.add(*all_menus) await user_role.menus.add(*all_menus) # 为普通用户分配基本API basic_apis = await Api.filter(Q(method__in=["GET"]) | Q(tags="基础模块")) await user_role.apis.add(*basic_apis) async def init_demo_transactions(): """ 创建开发环境演示用的发票与交易记录(付款凭证)数据。 功能: - 在无现有付款凭证数据时,批量生成若干 `Invoice` 与关联的 `PaymentReceipt`。 - 仅在调试模式下执行,避免污染生产环境。 参数: 无 返回: `None`,异步执行插入操作。 """ if not settings.DEBUG: return has_receipt = await PaymentReceipt.exists() if has_receipt: return demo_invoices = [] demo_payloads = [ { "ticket_type": "electronic", "invoice_type": "normal", "phone": "13800000001", "email": "demo1@example.com", "company_name": "演示科技有限公司", "tax_number": "91310000MA1DEMO01", "register_address": "上海市浦东新区演示路 100 号", "register_phone": "021-88880001", "bank_name": "招商银行上海分行", "bank_account": "6214830000000001", "status": "pending", "wechat": "demo_wechat_01", }, { "ticket_type": "paper", "invoice_type": "special", "phone": "13800000002", "email": "demo2@example.com", "company_name": "示例信息技术股份有限公司", "tax_number": "91310000MA1DEMO02", "register_address": "北京市海淀区知春路 66 号", "register_phone": "010-66660002", "bank_name": "中国银行北京分行", "bank_account": "6216610000000002", "status": "invoiced", "wechat": "demo_wechat_02", }, { "ticket_type": "electronic", "invoice_type": "special", "phone": "13800000003", "email": "demo3@example.com", "company_name": "华夏制造有限公司", "tax_number": "91310000MA1DEMO03", "register_address": "广州市天河区高新大道 8 号", "register_phone": "020-77770003", "bank_name": "建设银行广州分行", "bank_account": "6227000000000003", "status": "rejected", "wechat": "demo_wechat_03", }, { "ticket_type": "paper", "invoice_type": "normal", "phone": "13800000004", "email": "demo4@example.com", "company_name": "泰岳网络科技有限公司", "tax_number": "91310000MA1DEMO04", "register_address": "杭州市滨江区科技大道 1 号", "register_phone": "0571-55550004", "bank_name": "农业银行杭州分行", "bank_account": "6228480000000004", "status": "refunded", "wechat": "demo_wechat_04", }, { "ticket_type": "electronic", "invoice_type": "normal", "phone": "13800000005", "email": "demo5@example.com", "company_name": "星云数据有限公司", "tax_number": "91310000MA1DEMO05", "register_address": "成都市高新区软件园 9 号楼", "register_phone": "028-33330005", "bank_name": "工商银行成都分行", "bank_account": "6222020000000005", "status": "pending", "wechat": "demo_wechat_05", }, ] for payload in demo_payloads: inv = await Invoice.create(**payload) demo_invoices.append(inv) for idx, inv in enumerate(demo_invoices, start=1): await PaymentReceipt.create( invoice=inv, url=f"https://example.com/demo-receipt-{idx}-a.png", note="DEMO 凭证 A", verified=(inv.status == "invoiced"), ) if idx % 2 == 0: await PaymentReceipt.create( invoice=inv, url=f"https://example.com/demo-receipt-{idx}-b.png", note="DEMO 凭证 B", verified=False, ) async def init_data(): await init_db() await init_superuser() await init_menus() await init_apis() await init_roles() await init_demo_transactions()