guzhi/app/core/init_app.py
邹方成 f536178428 feat: 新增交易记录管理功能与统一上传接口
feat(交易记录): 新增交易记录管理页面与API接口
feat(上传): 添加统一上传接口支持自动识别文件类型
feat(用户管理): 为用户模型添加备注字段并更新相关接口
feat(邮件): 实现SMTP邮件发送功能并添加测试脚本
feat(短信): 增强短信服务配置灵活性与日志记录

fix(发票): 修复发票列表时间筛选功能
fix(nginx): 调整上传大小限制与超时配置

docs: 添加多个功能模块的说明文档
docs(估值): 补充估值计算流程与API提交数据说明

chore: 更新依赖与Docker镜像版本
2025-11-20 20:53:09 +08:00

450 lines
14 KiB
Python

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()