软件建模E01:Object-Role Modeling,从事实与约束开始
预留 R1 由 P1 提交,P2 审批了 R1。这两句话里的参与方属于同一种对象类型,但扮演不同角色。如果只画出 Party 与 Reservation 之间的一条关联,自审批是否允许、每项预留能有几个请求人,仍然没有答案。 这里的 ORM 指 Object-Role Modeling,即对象角色建模;它与把程序对象映射到关系数据库的 Object-Relational Mapping 同名缩写。前者讨论可以表达哪些事实、哪些事实组合合法,后者处理对象与表之间的存取映射。本篇实验实现有限的事实集合检查,没有运行 ORM 建模工具或数据库映射框架。 事实类型与具体事实 “预留由参与方提交”是事实类型,R1 由 P1 提交是它的一个实例。事实类型规定两个位置及其允许的对象类型,位置就是角色。同一个 Party 类型可以填入请求人位置,也可以填入审批人位置;角色的位置含义不由对象名称决定。 Halpin 的 ORM 2 记法说明以带读法的角色序列表达谓词,并用连接限定能参与该角色的对象类型。本篇只借用事实、角色和约束的概念;图采用普通流程图标记,并非完整 ORM 2 图形语法。ORM...
JPA 持久化 09:采购审批综合应用
采购审批综合应用:从一次合法调用到可恢复的终态 采购申请的页面显示“已下单”,数据库却没有订单,这不一定是 ORM 映射错误。状态改变、订单插入、事务提交、调用方收到响应是四个不同的时刻。要回答“审批成功后重试会不会多下一单”,不能只看 persist() 是否返回;还要回答同一租户以外能否读到申请,两次并发提交谁输,以及事务失败后是否有人仍拿着旧的 EntityManager 继续做事。本篇把前面 00–08 篇的实体、映射、查询和事务放进同一条采购链路,并严格区分代码已有的断言与尚未做的集成实验。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。 示例冻结为 Java 21、Jakarta Persistence 3.2、Hibernate ORM 7.1.36.Final、pgJDBC 42.7.7、PostgreSQL 16;入口为 工程说明、业务实现、测试源码 和 实验说明。已有代码是最小实验工程,不是可直接上线的采购服务:它没有 HTTP 身份认证、幂等请求表、跨行预算锁或完整异常分类。以下的“预期”不是运行记录。...
Java EE 企业应用 01:把采购审批需求写成可检查的规则
“审批通过”究竟改变了什么 某部门提出一笔采购:申请包含两份纸品与三支笔,提交人希望审批通过后生成订单。若只写“支持申请和审批”,开发者可以实现一条更新状态的接口,却无法回答客户端重试是否会生成两张订单、另一个部门能否审批、用户输入总额与服务器报价冲突时应采信哪个值。真正可检查的需求必须说明输入、操作者、前置状态、允许的变化和失败后留在数据库里的结果。第 00 章已有 Open Liberty 部署及健康接口 200 ready 的原始证据,只证实最小容器路由。本章定义采购规则并核对现有领域实现,不假装已经有认证用户的采购 HTTP 接口。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 这里的“部门”抽象为租户,示例租户为 tenant-one 与 tenant-two,没有真实人员数据。采购由草稿、提交、批准、下单构成主路径;拒绝后终止。供应商选择、预算冻结、库存、支付、税务、财务记账与通知送达均不在当前规则集里;把这些词写进需求并不能令系统拥有相应能力。业务确定性先于组件选择:哪一...
Java EE 企业应用 24:认证身份怎样进入采购业务
一笔采购,两个完全不同的“身份” 采购系统现有 POST /procurement/api/lab/requests 接收 X-Lab-Tenant: tenant-one。发送者改为 tenant-two,服务器就按另一个租户操作:这只是隔离实验的输入参数,不是登录,不证明用户属于该租户。JAVAEE_DEMO_MODE=true 只让教学路径可用,也不是安全开关。第 00–05 篇建立的 WAR、CDI 用例和 JDBC 适配器均未接入实际认证。不能把第 05 篇的 DataSource 正常连接解释为有身份的审批已成功。本章描述替换入口所需的契约;未修改累计工程,认证、注销、会话更新及失败登录均 NOT_RUN。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 采购员张某(示例标识 buyer-17)创建草稿时,服务器至少要回答三个不同问题:请求是否被认证,认证后的稳定主体标识是什么,该主体此刻能在什么组织内操作。密码只解决第一个问题的一种实现,Principal#getName(...
Java EE 企业应用 25:角色许可为何不能代替对象权限
审批员有角色,却不能批这张单 租户 tenant-one 的审批员有 APPROVER 角色。一张申请属于 tenant-two,另一张在 tenant-one 却超出其 5000 元审批上限。两张都应被拒。角色回答“可以发起哪类动作”,对象检查回答“可以对哪一张申请在当前状态做动作”。第 24 篇要求先有可验证 principal;现有 LabRequestsResource.approve 仅从 X-Lab-Tenant 获取自报租户,没有认证/角色/额度检查。本章是待接入的授权设计,不把教学端点的状态转换结果冒充合法审批。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 最小权限矩阵先固定主体与动作:匿名对采购操作拒绝;申请人只创建/提交自己未提交的草稿;审批员在已授权租户及额度内批准或拒绝已提交申请;订单操作员仅对批准单下单;管理员负责组织管理,不默认绕过财务分离职责。当前 schema 没有 created_by、审批额度配置或不可变的决策者字段,故“只提交自己的申请”在现有工...
Java EE 企业应用 27:CSRF、XSS、SQL 注入与 CORS 各挡在哪里
同一张审批单有四条不同的攻击路径 审批员在浏览器里已经登录。一封邮件诱导访问第三方页面,使浏览器携站点 Cookie 向采购系统提交审批,这是 CSRF;申请备注里的 <script> 被审批页面当 HTML 输出是 XSS;客户端在搜索框输入 x' OR 1=1 --,SQL 字符串拼接引起越权查询是 SQL 注入;开放 Access-Control-Allow-Origin 只是允许特定浏览器脚本读取跨源响应,不授予审批权限,也不会自动阻止跨站请求。四者影响面不同,不能用“一律转义输入”概括。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 现有 LabApprovalPage 与 /servlet/lab-session 是隔离教学入口,不是正式登录审批页;LabRequestsResource 无可信认证。第 24–26 篇拟议的身份和对象限制接入以前,不能把这些页面或 REST 端点称为受保护。以下代码/HTTP 协议是计划中的受保护入口,未接入工程;正常与攻击演练...
Java EE 企业应用 26:租户上下文从哪里来,SQL 才能相信它
申请 ID 唯一,查询仍然必须带租户 tenant-one 的采购员猜到 tenant-two 的申请 ID。若 DAO 用 SELECT ... WHERE id = ? 加载对象、业务检查后才过滤,未授权信息可能已进入响应、日志或后续订单写入。当前 JdbcRequestStore.find 的请求表读取已使用 id + tenant_id,但 LabRequestsResource 中 tenant_id 源自客户端可伪造的 X-Lab-Tenant;SQL 带租户条件不等于已获得可信租户。当前实验接口不能对公网开放。本章规定身份、组织关联、SQL 与异步消息的全链约束;相关真实跨租户安全验收均 NOT_RUN。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 在用户认证成功后,服务端以稳定 principal ID 查询可访问的租户集合。单租户账户由受信映射选定;多租户账户可提交租户选择值,但服务端必须验证集合成员资格,并为每个操作创建不可变的 AuthorizedTenant(...
Java EE 企业应用 28:审批审计何时与业务一起提交
204 成功却没有审计行,算不算成功 一笔采购被批准,purchase_request.status 已变为 APPROVED,审批记录或审计事件却未写入。以后无法重建是谁、在什么权限和版本下做了决定。打印一行“approve succeeded”不能代替持久化审计;日志可丢、可延迟,也不能天然与数据库事务原子提交。现有累计工程的 ProcurementUseCases.approve 执行状态迁移,schema 还没有审批记录表或业务审计表。本章给出要增加的结构和故障策略;以下代码、正常/拒绝/回滚审计实验均 NOT_RUN。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 审计记录至少要有受认证操作者稳定 ID、可信租户 ID、申请 ID、操作类型、决策结果、发生时间、请求关联 ID、决定时的申请版本及原因码;审批业务记录另存决定者及决定时额度(如需追溯)。时间由服务端生成并按 UTC/明确时区持久化。拒绝尝试可能涉及不存在的对象:记录受限的请求 ID 和原因码即可,不能为“确认该对...
Java EE 企业应用 29:附件与导出在哪些资源边界失败
一张审批单上的报价文件不能按文件名取 采购员上传报价附件 supplier-quote.pdf;另一个租户猜测 URL 为 /files/supplier-quote.pdf。若下载只检验文件名,其他租户就能读到报价,即便采购详情查询的 SQL 已经带租户。文件、附件元数据与采购申请必须共享受信租户和对象权限。当前示例 WAR 没有附件元数据表、上传/下载/导出正式路由,也没有认证入口。本章只提出需要实现的服务与验收;所有上传、下载、断连及恶意输入实验为 NOT_RUN。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 元数据建议用 tenant_id + attachment_id + request_id + storage_key + byte_count + media_type + state + uploader_id。storage_key 由服务器生成随机值,和客户端文件名脱钩;下载先验证 principal 对目标申请的读取权限,再按租户和附件 ID 查元数据,最后按服...
Java EE 企业应用 E02:邮件提交成功离收件人看到还差几步
SMTP 接受了一封“订单已生成”邮件,然后客户端超时 采购单生成后,系统要给申请人发通知。消费者调用 SMTP 发送,邮件服务器可能已回复成功,但客户端在读回复时断线。消费者只看到 I/O 错误,不知道服务器是否接收;若立刻无限重试,可能送出多封;若把这次记为“已送达”,实际 SMTP 服务器可能根本没有接受。此处有至少四种不同事实:订单业务事务已提交、outbox 任务已被消费者领取、SMTP 服务器接受消息、最终邮箱确实到达并可读。只有最后一项才能叫最终送达,而 Jakarta Mail 的一次发送返回不能保证这一点。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 001-initial.sql 只包含申请、明细和订单;后续 002/003 已有 outbox 与本地消费收据,但没有邮件收件人表、SMTP 发送尝试账本、SMTP 资源或本地邮件测试服务。主线第 23、30–31 篇的局部消息实验不等于邮件送达。本章没有在同名目录放模拟 SMTP 输出,实验均保持 NOT_RUN。...










