0001_initial_schema.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. """创建DMS七张核心表。
  2. Revision ID: 0001_initial_schema
  3. Revises:
  4. Create Date: 2026-07-23
  5. """
  6. from __future__ import annotations
  7. from collections.abc import Sequence
  8. from alembic import op
  9. import sqlalchemy as sa
  10. from sqlalchemy.dialects import mysql
  11. revision: str = "0001_initial_schema"
  12. down_revision: str | None = None
  13. branch_labels: str | Sequence[str] | None = None
  14. depends_on: str | Sequence[str] | None = None
  15. def _business_columns() -> list[sa.Column]:
  16. return [
  17. sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
  18. sa.Column("created_by", sa.BigInteger(), nullable=True),
  19. sa.Column("updated_by", sa.BigInteger(), nullable=True),
  20. sa.Column(
  21. "created_at",
  22. mysql.DATETIME(fsp=3),
  23. server_default=sa.text("CURRENT_TIMESTAMP(3)"),
  24. nullable=False,
  25. ),
  26. sa.Column(
  27. "updated_at",
  28. mysql.DATETIME(fsp=3),
  29. server_default=sa.text("CURRENT_TIMESTAMP(3)"),
  30. nullable=False,
  31. ),
  32. sa.Column("deleted_at", mysql.DATETIME(fsp=3), nullable=True),
  33. sa.Column(
  34. "is_deleted",
  35. sa.Boolean(),
  36. server_default=sa.text("0"),
  37. nullable=False,
  38. ),
  39. sa.Column(
  40. "row_version",
  41. sa.Integer(),
  42. server_default=sa.text("0"),
  43. nullable=False,
  44. ),
  45. sa.PrimaryKeyConstraint("id"),
  46. ]
  47. def upgrade() -> None:
  48. op.create_table(
  49. "sys_organization",
  50. *_business_columns(),
  51. sa.Column("parent_id", sa.BigInteger(), nullable=True),
  52. sa.Column("org_code", sa.String(length=64), nullable=False),
  53. sa.Column("org_name", sa.String(length=128), nullable=False),
  54. sa.Column("org_path", sa.String(length=1000), nullable=False),
  55. sa.Column(
  56. "sort_no",
  57. sa.Integer(),
  58. server_default=sa.text("0"),
  59. nullable=False,
  60. ),
  61. sa.Column(
  62. "status",
  63. sa.String(length=20),
  64. server_default=sa.text("'ENABLED'"),
  65. nullable=False,
  66. ),
  67. sa.CheckConstraint(
  68. "status IN ('ENABLED', 'DISABLED')",
  69. name="organization_status",
  70. ),
  71. sa.ForeignKeyConstraint(
  72. ["parent_id"],
  73. ["sys_organization.id"],
  74. name="fk_sys_organization_parent_id_sys_organization",
  75. ondelete="RESTRICT",
  76. ),
  77. sa.UniqueConstraint("org_code", name="uq_sys_organization_org_code"),
  78. )
  79. op.create_index(
  80. "ix_sys_organization_parent_active_sort",
  81. "sys_organization",
  82. ["parent_id", "is_deleted", "sort_no"],
  83. )
  84. op.create_index(
  85. "ix_sys_organization_status_active",
  86. "sys_organization",
  87. ["status", "is_deleted"],
  88. )
  89. op.create_table(
  90. "sys_user",
  91. *_business_columns(),
  92. sa.Column("username", sa.String(length=64), nullable=False),
  93. sa.Column("password_hash", sa.String(length=255), nullable=False),
  94. sa.Column("real_name", sa.String(length=64), nullable=False),
  95. sa.Column("organization_id", sa.BigInteger(), nullable=True),
  96. sa.Column("organization_name", sa.String(length=128), nullable=True),
  97. sa.Column(
  98. "role_code",
  99. sa.String(length=32),
  100. server_default=sa.text("'USER'"),
  101. nullable=False,
  102. ),
  103. sa.Column(
  104. "security_level",
  105. sa.String(length=32),
  106. server_default=sa.text("'INTERNAL'"),
  107. nullable=False,
  108. ),
  109. sa.Column(
  110. "status",
  111. sa.String(length=20),
  112. server_default=sa.text("'ENABLED'"),
  113. nullable=False,
  114. ),
  115. sa.Column(
  116. "auth_version",
  117. sa.Integer(),
  118. server_default=sa.text("0"),
  119. nullable=False,
  120. ),
  121. sa.Column("last_login_at", mysql.DATETIME(fsp=3), nullable=True),
  122. sa.Column("last_login_ip", sa.String(length=64), nullable=True),
  123. sa.CheckConstraint(
  124. "role_code IN ('USER', 'ADMIN', 'AUDITOR')",
  125. name="user_role_code",
  126. ),
  127. sa.CheckConstraint(
  128. "security_level IN "
  129. "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')",
  130. name="user_security_level",
  131. ),
  132. sa.CheckConstraint(
  133. "status IN ('ENABLED', 'DISABLED')",
  134. name="user_status",
  135. ),
  136. sa.ForeignKeyConstraint(
  137. ["organization_id"],
  138. ["sys_organization.id"],
  139. name="fk_sys_user_organization_id_sys_organization",
  140. ondelete="RESTRICT",
  141. ),
  142. sa.UniqueConstraint("username", name="uq_sys_user_username"),
  143. )
  144. op.create_index(
  145. "ix_sys_user_organization_active_status",
  146. "sys_user",
  147. ["organization_id", "is_deleted", "status"],
  148. )
  149. op.create_index(
  150. "ix_sys_user_role_status_active",
  151. "sys_user",
  152. ["role_code", "status", "is_deleted"],
  153. )
  154. op.create_table(
  155. "doc_category",
  156. *_business_columns(),
  157. sa.Column("parent_id", sa.BigInteger(), nullable=True),
  158. sa.Column("category_code", sa.String(length=64), nullable=False),
  159. sa.Column("category_name", sa.String(length=128), nullable=False),
  160. sa.Column(
  161. "category_type",
  162. sa.String(length=32),
  163. server_default=sa.text("'OTHER'"),
  164. nullable=False,
  165. ),
  166. sa.Column("category_path", sa.String(length=1000), nullable=False),
  167. sa.Column(
  168. "sort_no",
  169. sa.Integer(),
  170. server_default=sa.text("0"),
  171. nullable=False,
  172. ),
  173. sa.Column(
  174. "document_count",
  175. sa.Integer(),
  176. server_default=sa.text("0"),
  177. nullable=False,
  178. ),
  179. sa.Column(
  180. "status",
  181. sa.String(length=20),
  182. server_default=sa.text("'ENABLED'"),
  183. nullable=False,
  184. ),
  185. sa.CheckConstraint(
  186. "category_type IN ('SCENE', 'STYLE', 'SITUATION', 'VERSION', 'OTHER')",
  187. name="category_type",
  188. ),
  189. sa.CheckConstraint(
  190. "status IN ('ENABLED', 'DISABLED')",
  191. name="category_status",
  192. ),
  193. sa.ForeignKeyConstraint(
  194. ["parent_id"],
  195. ["doc_category.id"],
  196. name="fk_doc_category_parent_id_doc_category",
  197. ondelete="RESTRICT",
  198. ),
  199. sa.UniqueConstraint("category_code", name="uq_doc_category_category_code"),
  200. )
  201. op.create_index(
  202. "ix_doc_category_parent_active_sort",
  203. "doc_category",
  204. ["parent_id", "is_deleted", "sort_no"],
  205. )
  206. op.create_index(
  207. "ix_doc_category_status_active",
  208. "doc_category",
  209. ["status", "is_deleted"],
  210. )
  211. op.create_index(
  212. "ix_doc_category_type_active",
  213. "doc_category",
  214. ["category_type", "is_deleted"],
  215. )
  216. op.create_table(
  217. "doc_document",
  218. *_business_columns(),
  219. sa.Column("document_name", sa.String(length=255), nullable=False),
  220. sa.Column("summary", sa.Text(), nullable=True),
  221. sa.Column("document_type", sa.String(length=32), nullable=False),
  222. sa.Column(
  223. "document_status",
  224. sa.String(length=32),
  225. server_default=sa.text("'DRAFT'"),
  226. nullable=False,
  227. ),
  228. sa.Column(
  229. "security_level",
  230. sa.String(length=32),
  231. server_default=sa.text("'INTERNAL'"),
  232. nullable=False,
  233. ),
  234. sa.Column(
  235. "visibility_type",
  236. sa.String(length=32),
  237. server_default=sa.text("'CUSTOM'"),
  238. nullable=False,
  239. ),
  240. sa.Column("attachment_type", sa.String(length=32), nullable=True),
  241. sa.Column("category_id", sa.BigInteger(), nullable=True),
  242. sa.Column("category_name", sa.String(length=128), nullable=True),
  243. sa.Column("category_path", sa.String(length=1000), nullable=True),
  244. sa.Column("parent_document_id", sa.BigInteger(), nullable=True),
  245. sa.Column("root_document_id", sa.BigInteger(), nullable=True),
  246. sa.Column("tags", mysql.JSON(), nullable=True),
  247. sa.Column("original_file_name", sa.String(length=255), nullable=False),
  248. sa.Column("file_relative_path", sa.String(length=1000), nullable=False),
  249. sa.Column("file_extension", sa.String(length=20), nullable=False),
  250. sa.Column("mime_type", sa.String(length=128), nullable=True),
  251. sa.Column(
  252. "file_size",
  253. sa.BigInteger(),
  254. server_default=sa.text("0"),
  255. nullable=False,
  256. ),
  257. sa.Column("file_hash", sa.String(length=64), nullable=False),
  258. sa.Column(
  259. "child_count",
  260. sa.Integer(),
  261. server_default=sa.text("0"),
  262. nullable=False,
  263. ),
  264. sa.Column(
  265. "attachment_count",
  266. sa.Integer(),
  267. server_default=sa.text("0"),
  268. nullable=False,
  269. ),
  270. sa.Column(
  271. "view_count",
  272. sa.BigInteger(),
  273. server_default=sa.text("0"),
  274. nullable=False,
  275. ),
  276. sa.Column(
  277. "download_count",
  278. sa.BigInteger(),
  279. server_default=sa.text("0"),
  280. nullable=False,
  281. ),
  282. sa.Column("search_text", sa.Text(), nullable=True),
  283. sa.Column("created_by_name", sa.String(length=64), nullable=True),
  284. sa.Column("updated_by_name", sa.String(length=64), nullable=True),
  285. sa.CheckConstraint(
  286. "document_type IN ('MAIN', 'SUB_PLAN', 'ATTACHMENT')",
  287. name="document_type",
  288. ),
  289. sa.CheckConstraint(
  290. "document_status IN ('DRAFT', 'PUBLISHED', 'ARCHIVED')",
  291. name="document_status",
  292. ),
  293. sa.CheckConstraint(
  294. "security_level IN "
  295. "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')",
  296. name="document_security_level",
  297. ),
  298. sa.CheckConstraint(
  299. "visibility_type IN ('ALL_AUTHENTICATED', 'ORGANIZATION', 'CUSTOM')",
  300. name="document_visibility_type",
  301. ),
  302. sa.CheckConstraint(
  303. "attachment_type IS NULL OR attachment_type IN "
  304. "('POLICY', 'WORK_STANDARD', 'TABLE', 'DIAGRAM', 'OTHER')",
  305. name="document_attachment_type",
  306. ),
  307. sa.CheckConstraint(
  308. "("
  309. "document_type = 'MAIN' "
  310. "AND parent_document_id IS NULL "
  311. "AND category_id IS NOT NULL "
  312. "AND attachment_type IS NULL"
  313. ") OR ("
  314. "document_type = 'SUB_PLAN' "
  315. "AND parent_document_id IS NOT NULL "
  316. "AND root_document_id IS NOT NULL "
  317. "AND category_id IS NOT NULL "
  318. "AND attachment_type IS NULL"
  319. ") OR ("
  320. "document_type = 'ATTACHMENT' "
  321. "AND parent_document_id IS NULL "
  322. "AND root_document_id IS NULL "
  323. "AND category_id IS NULL "
  324. "AND attachment_type IS NOT NULL "
  325. "AND security_level = 'PUBLIC' "
  326. "AND visibility_type = 'ALL_AUTHENTICATED'"
  327. ")",
  328. name="document_type_shape",
  329. ),
  330. sa.ForeignKeyConstraint(
  331. ["category_id"],
  332. ["doc_category.id"],
  333. name="fk_doc_document_category_id_doc_category",
  334. ondelete="RESTRICT",
  335. ),
  336. sa.ForeignKeyConstraint(
  337. ["parent_document_id"],
  338. ["doc_document.id"],
  339. name="fk_doc_document_parent_document_id_doc_document",
  340. ondelete="RESTRICT",
  341. ),
  342. sa.ForeignKeyConstraint(
  343. ["root_document_id"],
  344. ["doc_document.id"],
  345. name="fk_doc_document_root_document_id_doc_document",
  346. ondelete="RESTRICT",
  347. ),
  348. )
  349. op.create_index(
  350. "ix_doc_document_type_active_updated",
  351. "doc_document",
  352. ["document_type", "is_deleted", "updated_at"],
  353. )
  354. op.create_index(
  355. "ix_doc_document_parent_type_active",
  356. "doc_document",
  357. ["parent_document_id", "document_type", "is_deleted"],
  358. )
  359. op.create_index(
  360. "ix_doc_document_root_active",
  361. "doc_document",
  362. ["root_document_id", "is_deleted"],
  363. )
  364. op.create_index(
  365. "ix_doc_document_category_active",
  366. "doc_document",
  367. ["category_id", "is_deleted"],
  368. )
  369. op.create_index(
  370. "ix_doc_document_status_active",
  371. "doc_document",
  372. ["document_status", "is_deleted"],
  373. )
  374. op.create_index(
  375. "ix_doc_document_security_active",
  376. "doc_document",
  377. ["security_level", "is_deleted"],
  378. )
  379. op.create_index("ix_doc_document_file_hash", "doc_document", ["file_hash"])
  380. op.create_index(
  381. "ix_doc_document_creator_active",
  382. "doc_document",
  383. ["created_by", "is_deleted"],
  384. )
  385. op.create_index(
  386. "ix_doc_document_attachment_type_active_updated",
  387. "doc_document",
  388. ["attachment_type", "is_deleted", "updated_at"],
  389. )
  390. op.create_index(
  391. "ix_doc_document_file_extension_active_updated",
  392. "doc_document",
  393. ["file_extension", "is_deleted", "updated_at"],
  394. )
  395. op.create_table(
  396. "doc_attachment_binding",
  397. *_business_columns(),
  398. sa.Column("main_document_id", sa.BigInteger(), nullable=False),
  399. sa.Column("attachment_document_id", sa.BigInteger(), nullable=False),
  400. sa.Column(
  401. "sort_no",
  402. sa.Integer(),
  403. server_default=sa.text("0"),
  404. nullable=False,
  405. ),
  406. sa.Column(
  407. "active_marker",
  408. sa.Integer(),
  409. sa.Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
  410. nullable=True,
  411. ),
  412. sa.ForeignKeyConstraint(
  413. ["attachment_document_id"],
  414. ["doc_document.id"],
  415. name="fk_doc_attachment_binding_attachment_document_id_doc_document",
  416. ondelete="RESTRICT",
  417. ),
  418. sa.ForeignKeyConstraint(
  419. ["main_document_id"],
  420. ["doc_document.id"],
  421. name="fk_doc_attachment_binding_main_document_id_doc_document",
  422. ondelete="RESTRICT",
  423. ),
  424. sa.UniqueConstraint(
  425. "main_document_id",
  426. "attachment_document_id",
  427. "active_marker",
  428. name="uq_doc_attachment_binding_active",
  429. ),
  430. )
  431. op.create_index(
  432. "ix_doc_attachment_binding_main_active_sort",
  433. "doc_attachment_binding",
  434. ["main_document_id", "is_deleted", "sort_no"],
  435. )
  436. op.create_index(
  437. "ix_doc_attachment_binding_attachment_active",
  438. "doc_attachment_binding",
  439. ["attachment_document_id", "is_deleted"],
  440. )
  441. op.create_table(
  442. "doc_permission",
  443. *_business_columns(),
  444. sa.Column("document_id", sa.BigInteger(), nullable=False),
  445. sa.Column("subject_type", sa.String(length=20), nullable=False),
  446. sa.Column("subject_id", sa.BigInteger(), nullable=False),
  447. sa.Column("subject_name", sa.String(length=255), nullable=False),
  448. sa.Column(
  449. "can_view",
  450. sa.Boolean(),
  451. server_default=sa.text("1"),
  452. nullable=False,
  453. ),
  454. sa.Column(
  455. "can_download",
  456. sa.Boolean(),
  457. server_default=sa.text("0"),
  458. nullable=False,
  459. ),
  460. sa.Column(
  461. "can_edit",
  462. sa.Boolean(),
  463. server_default=sa.text("0"),
  464. nullable=False,
  465. ),
  466. sa.Column(
  467. "can_manage_permission",
  468. sa.Boolean(),
  469. server_default=sa.text("0"),
  470. nullable=False,
  471. ),
  472. sa.Column(
  473. "can_delete",
  474. sa.Boolean(),
  475. server_default=sa.text("0"),
  476. nullable=False,
  477. ),
  478. sa.Column(
  479. "active_marker",
  480. sa.Integer(),
  481. sa.Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
  482. nullable=True,
  483. ),
  484. sa.CheckConstraint(
  485. "subject_type IN ('ORG', 'USER')",
  486. name="permission_subject_type",
  487. ),
  488. sa.ForeignKeyConstraint(
  489. ["document_id"],
  490. ["doc_document.id"],
  491. name="fk_doc_permission_document_id_doc_document",
  492. ondelete="RESTRICT",
  493. ),
  494. sa.UniqueConstraint(
  495. "document_id",
  496. "subject_type",
  497. "subject_id",
  498. "active_marker",
  499. name="uq_doc_permission_active",
  500. ),
  501. )
  502. op.create_index(
  503. "ix_doc_permission_document_active",
  504. "doc_permission",
  505. ["document_id", "is_deleted"],
  506. )
  507. op.create_index(
  508. "ix_doc_permission_subject_active",
  509. "doc_permission",
  510. ["subject_type", "subject_id", "is_deleted"],
  511. )
  512. op.create_table(
  513. "sys_audit_log",
  514. sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
  515. sa.Column("user_id", sa.BigInteger(), nullable=True),
  516. sa.Column("username", sa.String(length=64), nullable=True),
  517. sa.Column("real_name", sa.String(length=64), nullable=True),
  518. sa.Column("organization_id", sa.BigInteger(), nullable=True),
  519. sa.Column("organization_name", sa.String(length=128), nullable=True),
  520. sa.Column("action_type", sa.String(length=32), nullable=False),
  521. sa.Column("target_type", sa.String(length=32), nullable=True),
  522. sa.Column("target_id", sa.BigInteger(), nullable=True),
  523. sa.Column("target_name", sa.String(length=255), nullable=True),
  524. sa.Column("operation_result", sa.String(length=20), nullable=False),
  525. sa.Column("failure_reason", sa.String(length=1000), nullable=True),
  526. sa.Column("client_ip", sa.String(length=64), nullable=True),
  527. sa.Column("user_agent", sa.String(length=500), nullable=True),
  528. sa.Column("request_id", sa.String(length=64), nullable=True),
  529. sa.Column("operation_detail", mysql.JSON(), nullable=True),
  530. sa.Column(
  531. "created_at",
  532. mysql.DATETIME(fsp=3),
  533. server_default=sa.text("CURRENT_TIMESTAMP(3)"),
  534. nullable=False,
  535. ),
  536. sa.CheckConstraint(
  537. "action_type IN ("
  538. "'LOGIN', 'LOGOUT', 'VIEW_DOCUMENT', 'DOWNLOAD_DOCUMENT', "
  539. "'UPLOAD_DOCUMENT', 'BATCH_IMPORT', 'EDIT_DOCUMENT', "
  540. "'DELETE_DOCUMENT', 'CREATE_CATEGORY', 'EDIT_CATEGORY', "
  541. "'DELETE_CATEGORY', 'CHANGE_PERMISSION', 'BIND_ATTACHMENT', "
  542. "'UNBIND_ATTACHMENT'"
  543. ")",
  544. name="audit_action_type",
  545. ),
  546. sa.CheckConstraint(
  547. "target_type IS NULL OR target_type IN ("
  548. "'AUTH', 'CATEGORY', 'DOCUMENT', 'ATTACHMENT', "
  549. "'PERMISSION', 'ATTACHMENT_BINDING'"
  550. ")",
  551. name="audit_target_type",
  552. ),
  553. sa.CheckConstraint(
  554. "operation_result IN ('SUCCESS', 'FAILURE')",
  555. name="audit_operation_result",
  556. ),
  557. sa.PrimaryKeyConstraint("id"),
  558. )
  559. op.create_index(
  560. "ix_sys_audit_log_created_at",
  561. "sys_audit_log",
  562. ["created_at"],
  563. )
  564. op.create_index(
  565. "ix_sys_audit_log_user_created",
  566. "sys_audit_log",
  567. ["user_id", "created_at"],
  568. )
  569. op.create_index(
  570. "ix_sys_audit_log_organization_created",
  571. "sys_audit_log",
  572. ["organization_id", "created_at"],
  573. )
  574. op.create_index(
  575. "ix_sys_audit_log_action_created",
  576. "sys_audit_log",
  577. ["action_type", "created_at"],
  578. )
  579. op.create_index(
  580. "ix_sys_audit_log_target_created",
  581. "sys_audit_log",
  582. ["target_type", "target_id", "created_at"],
  583. )
  584. op.create_index(
  585. "ix_sys_audit_log_result_created",
  586. "sys_audit_log",
  587. ["operation_result", "created_at"],
  588. )
  589. op.create_index(
  590. "ix_sys_audit_log_request_id",
  591. "sys_audit_log",
  592. ["request_id"],
  593. )
  594. def downgrade() -> None:
  595. op.drop_table("sys_audit_log")
  596. op.drop_table("doc_permission")
  597. op.drop_table("doc_attachment_binding")
  598. op.drop_table("doc_document")
  599. op.drop_table("doc_category")
  600. op.drop_table("sys_user")
  601. op.drop_table("sys_organization")