[{"data":1,"prerenderedAt":699},["ShallowReactive",2],{"article-other\u002Farchitectural-thoughts":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"tags":11,"body":13,"_type":693,"_id":694,"_source":695,"_file":696,"_stem":697,"_extension":698},"\u002Farticles\u002Fother\u002Farchitectural-thoughts","other",false,"","AI 制药平台的架构思想：服务拆分、数据隔离与资源调度","梳理 AI 制药平台的轻量服务化架构，理解 Java 与 Python AI 服务的职责划分、异构资源调度、逻辑删除和多租户数据隔离。","2026-08-08",[12],"随笔",{"type":14,"children":15,"toc":682},"root",[16,28,34,41,105,110,142,147,160,215,233,241,251,301,311,351,356,366,384,451,456,475,480,494,504,521,654,670,676],{"type":17,"tag":18,"props":19,"children":20},"element","ol",{},[21],{"type":17,"tag":22,"props":23,"children":24},"li",{},[25],{"type":26,"value":27},"text","Java 独占任务状态机是什么意思？",{"type":17,"tag":29,"props":30,"children":31},"p",{},[32],{"type":26,"value":33},"批量任务“现在进行到哪一步、是否成功失败、能不能重试”这些状态，只能由 Java 主后端管理和保存；Python AI 服务只负责计算。",{"type":17,"tag":35,"props":36,"children":38},"h2",{"id":37},"ai制药平台的架构思想",[39],{"type":26,"value":40},"AI制药平台的架构思想",{"type":17,"tag":42,"props":43,"children":44},"ul",{},[45,50,55,68],{"type":17,"tag":22,"props":46,"children":47},{},[48],{"type":26,"value":49},"分布式 + 服务化架构，不是完整意义上的微服务",{"type":17,"tag":22,"props":51,"children":52},{},[53],{"type":26,"value":54},"异构资源调度",{"type":17,"tag":22,"props":56,"children":57},{},[58],{"type":17,"tag":59,"props":60,"children":62},"pre",{"code":61},"[GPU 机器]  ai-service (Python 推理)\n                ▲ HTTP\n[大内存机器] java-backend ──┬──▶ [DB 机器] PostgreSQL + Redis\n                ▲ HTTP      \n[Nginx\u002FCDN]  frontend (静态资源)\n",[63],{"type":17,"tag":64,"props":65,"children":66},"code",{"__ignoreMap":7},[67],{"type":26,"value":61},{"type":17,"tag":22,"props":69,"children":70},{},[71],{"type":17,"tag":42,"props":72,"children":73},{},[74,85,95],{"type":17,"tag":22,"props":75,"children":76},{},[77,79],{"type":26,"value":78},"AI 服务吃 ",{"type":17,"tag":80,"props":81,"children":82},"strong",{},[83],{"type":26,"value":84},"GPU\u002F显存",{"type":17,"tag":22,"props":86,"children":87},{},[88,90],{"type":26,"value":89},"Java 业务吃 ",{"type":17,"tag":80,"props":91,"children":92},{},[93],{"type":26,"value":94},"内存\u002FCPU",{"type":17,"tag":22,"props":96,"children":97},{},[98,100],{"type":26,"value":99},"数据库吃 ",{"type":17,"tag":80,"props":101,"children":102},{},[103],{"type":26,"value":104},"快磁盘\u002FIO",{"type":17,"tag":35,"props":106,"children":108},{"id":107},"微服务",[109],{"type":26,"value":107},{"type":17,"tag":18,"props":111,"children":112},{},[113,132,137],{"type":17,"tag":22,"props":114,"children":115},{},[116,118,123,125,130],{"type":26,"value":117},"微服务是按",{"type":17,"tag":80,"props":119,"children":120},{},[121],{"type":26,"value":122},"业务领域",{"type":26,"value":124},"拆（订单服务、用户服务、支付服务……），你是按",{"type":17,"tag":80,"props":126,"children":127},{},[128],{"type":26,"value":129},"技术职责",{"type":26,"value":131},"拆（Java 管业务 \u002F Python 管 AI 计算）。你更像\"前后端分离 + 后端按技术分层\"，是 SOA（面向服务架构）的轻量形态。",{"type":17,"tag":22,"props":133,"children":134},{},[135],{"type":26,"value":136},"微服务讲究\"每个服务一个库\"（database per service）",{"type":17,"tag":22,"props":138,"children":139},{},[140],{"type":26,"value":141},"真正的微服务通常还配套服务注册发现、API 网关、配置中心、链路追踪、独立 CI\u002FCD。你现在没有这些（也不需要）。",{"type":17,"tag":35,"props":143,"children":145},{"id":144},"数据库字段",[146],{"type":26,"value":144},{"type":17,"tag":148,"props":149,"children":151},"h3",{"id":150},"deleted-字段-逻辑删除软删除",[152,158],{"type":17,"tag":64,"props":153,"children":155},{"className":154},[],[156],{"type":26,"value":157},"deleted",{"type":26,"value":159}," 字段 = 逻辑删除（软删除）",{"type":17,"tag":29,"props":161,"children":162},{},[163,168,170,176,178,183,185,190,192,197,199,205,207,213],{"type":17,"tag":80,"props":164,"children":165},{},[166],{"type":26,"value":167},"它解决什么问题",{"type":26,"value":169},"：普通的 ",{"type":17,"tag":64,"props":171,"children":173},{"className":172},[],[174],{"type":26,"value":175},"DELETE FROM molecules WHERE id=1",{"type":26,"value":177}," 是",{"type":17,"tag":80,"props":179,"children":180},{},[181],{"type":26,"value":182},"物理删除",{"type":26,"value":184},"，数据真没了，找不回来。逻辑删除是",{"type":17,"tag":80,"props":186,"children":187},{},[188],{"type":26,"value":189},"假删",{"type":26,"value":191},"——不真删行，只把 ",{"type":17,"tag":64,"props":193,"children":195},{"className":194},[],[196],{"type":26,"value":157},{"type":26,"value":198}," 字段从 ",{"type":17,"tag":64,"props":200,"children":202},{"className":201},[],[203],{"type":26,"value":204},"0",{"type":26,"value":206}," 改成 ",{"type":17,"tag":64,"props":208,"children":210},{"className":209},[],[211],{"type":26,"value":212},"1",{"type":26,"value":214},"，标记\"这条已删除\"。",{"type":17,"tag":29,"props":216,"children":217},{},[218,223,225,231],{"type":17,"tag":80,"props":219,"children":220},{},[221],{"type":26,"value":222},"这个框架的配置",{"type":26,"value":224},"（",{"type":17,"tag":64,"props":226,"children":228},{"className":227},[],[229],{"type":26,"value":230},"application.yml",{"type":26,"value":232},"）：",{"type":17,"tag":59,"props":234,"children":236},{"code":235},"logic-delete-field: deleted        # 用 deleted 这一列标记\nlogic-delete-value: 1              # 1 = 已删除\nlogic-not-delete-value: 0          # 0 = 正常\n",[237],{"type":17,"tag":64,"props":238,"children":239},{"__ignoreMap":7},[240],{"type":26,"value":235},{"type":17,"tag":29,"props":242,"children":243},{},[244,249],{"type":17,"tag":80,"props":245,"children":246},{},[247],{"type":26,"value":248},"机制（对你透明）",{"type":26,"value":250},"：",{"type":17,"tag":42,"props":252,"children":253},{},[254,275,296],{"type":17,"tag":22,"props":255,"children":256},{},[257,259,265,267,273],{"type":26,"value":258},"你调用 MyBatis-Plus 的 ",{"type":17,"tag":64,"props":260,"children":262},{"className":261},[],[263],{"type":26,"value":264},"deleteById(1)",{"type":26,"value":266},"，它实际执行的不是 DELETE，而是 ",{"type":17,"tag":64,"props":268,"children":270},{"className":269},[],[271],{"type":26,"value":272},"UPDATE molecules SET deleted=1 WHERE id=1",{"type":26,"value":274},"。",{"type":17,"tag":22,"props":276,"children":277},{},[278,280,286,288,294],{"type":26,"value":279},"你写 ",{"type":17,"tag":64,"props":281,"children":283},{"className":282},[],[284],{"type":26,"value":285},"SELECT * FROM molecules",{"type":26,"value":287},"，框架自动改写成 ",{"type":17,"tag":64,"props":289,"children":291},{"className":290},[],[292],{"type":26,"value":293},"SELECT * FROM molecules WHERE deleted=0",{"type":26,"value":295},"，已删的行你查不到。",{"type":17,"tag":22,"props":297,"children":298},{},[299],{"type":26,"value":300},"所以你平时写代码感觉就是\"删了就没了\"，但数据其实还在库里。",{"type":17,"tag":29,"props":302,"children":303},{},[304,309],{"type":17,"tag":80,"props":305,"children":306},{},[307],{"type":26,"value":308},"为什么企业都这么做",{"type":26,"value":310},"：误删能恢复、保留历史审计、关联数据不断链、满足合规（很多业务数据法律上不允许真删）。",{"type":17,"tag":29,"props":312,"children":313},{},[314,319,321,327,329,334,336,341,343,349],{"type":17,"tag":80,"props":315,"children":316},{},[317],{"type":26,"value":318},"一个你要知道的坑",{"type":26,"value":320},"（正好影响 molecules）：逻辑删除下，\"删掉的旧行还占在表里\"。如果你给 ",{"type":17,"tag":64,"props":322,"children":324},{"className":323},[],[325],{"type":26,"value":326},"canonical_smiles",{"type":26,"value":328}," 建普通唯一索引，删掉阿司匹林后再想重新录入，会因为旧的那条（deleted=1）还在而",{"type":17,"tag":80,"props":330,"children":331},{},[332],{"type":26,"value":333},"唯一冲突报错",{"type":26,"value":335},"。PostgreSQL 的正解是用",{"type":17,"tag":80,"props":337,"children":338},{},[339],{"type":26,"value":340},"部分唯一索引",{"type":26,"value":342}," ",{"type":17,"tag":64,"props":344,"children":346},{"className":345},[],[347],{"type":26,"value":348},"UNIQUE (tenant_id, canonical_smiles) WHERE deleted=0",{"type":26,"value":350},"——只对\"未删除\"的行约束唯一。这点我会写进给 codex 的任务清单。",{"type":17,"tag":35,"props":352,"children":354},{"id":353},"多租户",[355],{"type":26,"value":353},{"type":17,"tag":29,"props":357,"children":358},{},[359,364],{"type":17,"tag":80,"props":360,"children":361},{},[362],{"type":26,"value":363},"多租户是什么",{"type":26,"value":365},"：一套系统、一个数据库，同时服务多个\"租户\"（可以理解成多个公司\u002F组织），租户之间数据互相隔离、彼此看不见。典型场景是 SaaS——A 公司和 B 公司用同一个系统，但各自只能看到自己的数据。",{"type":17,"tag":29,"props":367,"children":368},{},[369,374,376,382],{"type":17,"tag":80,"props":370,"children":371},{},[372],{"type":26,"value":373},"这个框架用的是\"行级多租户\"",{"type":26,"value":375},"（共享库、共享表，靠一列 ",{"type":17,"tag":64,"props":377,"children":379},{"className":378},[],[380],{"type":26,"value":381},"tenant_id",{"type":26,"value":383}," 区分）：",{"type":17,"tag":42,"props":385,"children":386},{},[387,397,430],{"type":17,"tag":22,"props":388,"children":389},{},[390,392],{"type":26,"value":391},"需要隔离的表加一列 ",{"type":17,"tag":64,"props":393,"children":395},{"className":394},[],[396],{"type":26,"value":381},{"type":17,"tag":22,"props":398,"children":399},{},[400,402,408,410,416,418],{"type":26,"value":401},"拦截器（",{"type":17,"tag":64,"props":403,"children":405},{"className":404},[],[406],{"type":26,"value":407},"TenantLineInnerInterceptor",{"type":26,"value":409}," + ",{"type":17,"tag":64,"props":411,"children":413},{"className":412},[],[414],{"type":26,"value":415},"OinsistTenantLineHandler",{"type":26,"value":417},"）在",{"type":17,"tag":80,"props":419,"children":420},{},[421,423,428],{"type":26,"value":422},"每条 SQL 自动加 ",{"type":17,"tag":64,"props":424,"children":426},{"className":425},[],[427],{"type":26,"value":381},{"type":26,"value":429}," 条件",{"type":17,"tag":22,"props":431,"children":432},{},[433,435,441,443,449],{"type":26,"value":434},"当前租户哪来的？前端请求头带 ",{"type":17,"tag":64,"props":436,"children":438},{"className":437},[],[439],{"type":26,"value":440},"Tenant-Id",{"type":26,"value":442},"（默认 ",{"type":17,"tag":64,"props":444,"children":446},{"className":445},[],[447],{"type":26,"value":448},"000000",{"type":26,"value":450},"），后端取出来用",{"type":17,"tag":29,"props":452,"children":453},{},[454],{"type":26,"value":455},"举例，你写：",{"type":17,"tag":59,"props":457,"children":461},{"code":458,"language":459,"meta":7,"className":460,"style":7},"SELECT * FROM molecules WHERE mol_weight > 100\n","sql","language-sql shiki shiki-themes github-dark",[462],{"type":17,"tag":64,"props":463,"children":464},{"__ignoreMap":7},[465],{"type":17,"tag":466,"props":467,"children":470},"span",{"class":468,"line":469},"line",1,[471],{"type":17,"tag":466,"props":472,"children":473},{},[474],{"type":26,"value":458},{"type":17,"tag":29,"props":476,"children":477},{},[478],{"type":26,"value":479},"拦截器自动改写成：",{"type":17,"tag":59,"props":481,"children":483},{"code":482,"language":459,"meta":7,"className":460,"style":7},"SELECT * FROM molecules WHERE mol_weight > 100 AND tenant_id = '当前租户'\n",[484],{"type":17,"tag":64,"props":485,"children":486},{"__ignoreMap":7},[487],{"type":17,"tag":466,"props":488,"children":489},{"class":468,"line":469},[490],{"type":17,"tag":466,"props":491,"children":492},{},[493],{"type":26,"value":482},{"type":17,"tag":29,"props":495,"children":496},{},[497,502],{"type":17,"tag":80,"props":498,"children":499},{},[500],{"type":26,"value":501},"你写业务代码时几乎感知不到 tenant_id 的存在",{"type":26,"value":503},"，它由框架自动加。",{"type":17,"tag":29,"props":505,"children":506},{},[507,512,514,519],{"type":17,"tag":80,"props":508,"children":509},{},[510],{"type":26,"value":511},"是不是每张表都要加？——不是，分两类",{"type":26,"value":513},"（这正是 ",{"type":17,"tag":64,"props":515,"children":517},{"className":516},[],[518],{"type":26,"value":415},{"type":26,"value":520}," 里那个白名单的意义）：",{"type":17,"tag":522,"props":523,"children":524},"table",{},[525,549],{"type":17,"tag":526,"props":527,"children":528},"thead",{},[529],{"type":17,"tag":530,"props":531,"children":532},"tr",{},[533,539,544],{"type":17,"tag":534,"props":535,"children":536},"th",{},[537],{"type":26,"value":538},"类型",{"type":17,"tag":534,"props":540,"children":541},{},[542],{"type":26,"value":543},"加 tenant_id 吗",{"type":17,"tag":534,"props":545,"children":546},{},[547],{"type":26,"value":548},"例子",{"type":17,"tag":550,"props":551,"children":552},"tbody",{},[553,603],{"type":17,"tag":530,"props":554,"children":555},{},[556,567,572],{"type":17,"tag":557,"props":558,"children":559},"td",{},[560,565],{"type":17,"tag":80,"props":561,"children":562},{},[563],{"type":26,"value":564},"租户隔离表",{"type":26,"value":566},"（各租户私有的业务数据）",{"type":17,"tag":557,"props":568,"children":569},{},[570],{"type":26,"value":571},"✅ 加",{"type":17,"tag":557,"props":573,"children":574},{},[575,581,583,589,590,596,597],{"type":17,"tag":64,"props":576,"children":578},{"className":577},[],[579],{"type":26,"value":580},"sys_user",{"type":26,"value":582},"、",{"type":17,"tag":64,"props":584,"children":586},{"className":585},[],[587],{"type":26,"value":588},"sys_role",{"type":26,"value":582},{"type":17,"tag":64,"props":591,"children":593},{"className":592},[],[594],{"type":26,"value":595},"molecules",{"type":26,"value":582},{"type":17,"tag":64,"props":598,"children":600},{"className":599},[],[601],{"type":26,"value":602},"prediction_tasks",{"type":17,"tag":530,"props":604,"children":605},{},[606,616,629],{"type":17,"tag":557,"props":607,"children":608},{},[609,614],{"type":17,"tag":80,"props":610,"children":611},{},[612],{"type":26,"value":613},"全局共享表",{"type":26,"value":615},"（所有租户公用的系统\u002F字典数据）",{"type":17,"tag":557,"props":617,"children":618},{},[619,621,627],{"type":26,"value":620},"❌ 不加，进 ",{"type":17,"tag":64,"props":622,"children":624},{"className":623},[],[625],{"type":26,"value":626},"IGNORE_TABLES",{"type":26,"value":628}," 白名单",{"type":17,"tag":557,"props":630,"children":631},{},[632,638,640,646,648],{"type":17,"tag":64,"props":633,"children":635},{"className":634},[],[636],{"type":26,"value":637},"sys_menu",{"type":26,"value":639},"（菜单全系统一套）、",{"type":17,"tag":64,"props":641,"children":643},{"className":642},[],[644],{"type":26,"value":645},"sys_tenant",{"type":26,"value":647},"（租户表自己）、",{"type":17,"tag":64,"props":649,"children":651},{"className":650},[],[652],{"type":26,"value":653},"sys_role_menu",{"type":17,"tag":29,"props":655,"children":656},{},[657,662,664,669],{"type":17,"tag":80,"props":658,"children":659},{},[660],{"type":26,"value":661},"规则",{"type":26,"value":663},"：默认就是\"加 tenant_id\"（拦截器默认拦截所有表），",{"type":17,"tag":80,"props":665,"children":666},{},[667],{"type":26,"value":668},"只有当这张表是全系统共享的字典\u002F系统表时，才把它放进白名单豁免",{"type":26,"value":274},{"type":17,"tag":42,"props":671,"children":672},{},[673],{"type":17,"tag":22,"props":674,"children":675},{},[],{"type":17,"tag":677,"props":678,"children":679},"style",{},[680],{"type":26,"value":681},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":683,"depth":683,"links":684},2,[685,686,687,692],{"id":37,"depth":683,"text":40},{"id":107,"depth":683,"text":107},{"id":144,"depth":683,"text":144,"children":688},[689],{"id":150,"depth":690,"text":691},3,"deleted 字段 = 逻辑删除（软删除）",{"id":353,"depth":683,"text":353},"markdown","content:articles:other:architectural-thoughts.md","content","articles\u002Fother\u002Farchitectural-thoughts.md","articles\u002Fother\u002Farchitectural-thoughts","md",1789465115005]