feat(交易记录): 新增交易记录管理页面与API接口 feat(上传): 添加统一上传接口支持自动识别文件类型 feat(用户管理): 为用户模型添加备注字段并更新相关接口 feat(邮件): 实现SMTP邮件发送功能并添加测试脚本 feat(短信): 增强短信服务配置灵活性与日志记录 fix(发票): 修复发票列表时间筛选功能 fix(nginx): 调整上传大小限制与超时配置 docs: 添加多个功能模块的说明文档 docs(估值): 补充估值计算流程与API提交数据说明 chore: 更新依赖与Docker镜像版本
450 lines
14 KiB
Python
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()
|