企业用印管理系统全流程设计:印章台账+BPM审批+外借冲突检测——从"随便盖章"到"一印一审"
🌐 文档地址:ruoyioffice.com |📦 源码1:ruoyi-office-vben |📦 源码2:ruoyi-office |📦 源码3:ruoyi-office |💬 微信:17156169080(备注「RuoYi Office」)
印章是企业经营活动中最敏感的法律凭证——一个公章盖错地方,轻则合同纠纷,重则法律责任。但大量企业的印章管理还停留在"找行政拿章、签字登记、盖完还回来"的阶段:印章借走三天没人追,同一枚章被两个部门同时借走,离职员工还能以保管人身份用印。RuoYi Office 用 2 张表、5 种状态机、1 套 BPM 审批流程 + 外借时间冲突检测算法,构建了从印章建档→用印申请→审批→用印→外借→归还的全生命周期管理闭环。
引言:用印管理到底难在哪?
"不就是登记一下谁盖了什么章吗?"——初次接到需求的开发者大多这么想。但真正动手时会发现,用印管理的复杂度远超"登记盖章"四个字:
印章种类多,权限不同:公章能代表公司签合同,财务章只能盖在财务文件上,合同专用章只能用于合同。不同类型的印章审批层级和使用范围完全不同。
用章方式分两种:现场用印(拿文件到行政处盖章,盖完就走)和外借用章(把印章带走,用完再还)。外借涉及借出时间、归还追踪、逾期提醒,复杂度翻倍。
外借时间冲突:一枚印章在同一时间段只能借给一个人。如果 A 部门借了公章 3.20-3.25,B 部门想借 3.22-3.28,系统必须检测到时间重叠并拒绝。
审批流程不能跳过:任何用印操作都必须走审批,审批通过后才能用章。审批中的申请单也要纳入冲突检测范围——防止"审批还没通过就把章借走了"。
状态流转复杂:现场用印审批通过即完成;外借用印还要经历"外借中→已归还"的后续流程,可能还会逾期。
本文以 RuoYi Office 的用印管理模块为例,完整拆解其业务建模、数据结构、冲突检测算法、前后端实现方案。
一、业务设计:从一次用印申请说起
1.1 业务全景
一次完整的用印业务涉及三个角色、四个阶段:
| 阶段 | 角色 | 操作 | 系统行为 |
|---|---|---|---|
| 建档 | 行政管理员 | 录入印章信息(编号/名称/类型/分类/保管人) | 创建台账记录,状态为「在库」 |
| 申请 | 普通员工 | 选择印章、填写用途和方式 → 提交审批 | 创建申请单,发起BPM流程 |
| 审批 | 审批人 | 审核用印事由、文件信息 | 审批通过后,用印状态变为「待处理」 |
| 用印/归还 | 行政管理员 | 现场盖章或登记外借,外借到期后确认归还 | 状态流转至「已完成」或「已归还」 |
1.2 现场用印 vs 外借用章:两条分叉路
这是本方案最核心的业务抽象——用章方式决定了申请单的生命周期长度:

系统在申请单层面定义了用章方式(useMode),在审批通过后自动判断:
- 现场用印(
useMode = 1):管理员确认用印后,状态直接变为「已完成」,流程结束 - 外借用章(
useMode = 2):管理员登记借出后,状态变为「外借中」,等待后续归还确认;如果超过预计归还时间仍未归还,状态变为「已逾期」
这种设计用一个字段值就实现了两条截然不同的流转路径,避免了为两种用章方式各写一套业务逻辑。
1.3 五种用印状态的生命周期
每张用印申请单都有独立的用印状态,由 SealUseStatusEnum 定义:
| 状态码 | 状态名 | 触发条件 | 可执行操作 |
|---|---|---|---|
0 | 待处理 | BPM审批通过 | 管理员可确认用印或登记外借 |
1 | 已完成 | 现场用印确认完成 | 无(终态) |
2 | 外借中 | 外借用章登记借出 | 可归还 |
3 | 已归还 | 外借用章归还确认 | 无(终态) |
4 | 已逾期 | 超过预计归还时间未归还 | 可归还(补归还) |
上方的业务流转全景图已完整展示了五种状态的流转路径(参见 1.2 节配图),下面用表格说明具体的状态转换规则:
1.4 印章分类与类型体系
印章台账通过两个维度对印章进行分类管理:
| 维度 | 字典编码 | 典型值 | 用途 |
|---|---|---|---|
| 印章分类 | OA_SEAL_CLS | 行政类 / 业务类 / 财务类 / 其他类 | 台账左侧导航筛选 |
| 印章类型 | OA_SEAL_TYPE | 公章 / 合同专用章 / 财务专用章 / 发票专用章 / 法人章 | 标识印章用途和法律效力 |
两个维度正交组合,一枚"行政类·公章"和一枚"财务类·财务专用章"在台账中清晰区分,搜索和审批时一目了然。
二、系统设计:两个子模块的协作
2.1 模块组成
RuoYi Office 的用印管理位于 OA 办公管理 → 用印管理 目录下,由两个紧密协作的子模块组成:
| 子模块 | 菜单名称 | 功能定位 | 面向角色 |
|---|---|---|---|
| 印章台账 | 印章信息管理 | 印章主数据管理:编号、名称、类型、分类、保管人、状态 | 行政管理员 |
| 用印申请 | 用印申请管理 | 员工发起用印申请、走BPM审批、外借归还 | 全体员工 |
2.2 核心设计决策
| 决策点 | 方案 | 理由 |
|---|---|---|
| 数据模型 | 台账+申请单两张表 | 台账管"有哪些章",申请单管"谁用了什么章" |
| 冲突检测范围 | 审批中 + 外借中 | 审批中的单据也占用时间段,防止审批后才发现冲突 |
| 冲突检测时机 | 提交时校验 | 保存不校验(允许草稿),提交审批时才严格检查 |
| 印章选择范围 | 仅在库印章 | SealSelectModal 固定 status: 0,停用/使用中的章不可选 |
| 冗余存储 | 申请单冗余印章编号/名称/保管人 | 列表展示不需要 JOIN 台账表,历史数据不受台账修改影响 |
| 审批集成 | FlowBillService标准接口 | 复用BPM框架回调,状态同步零耦合 |
| 我的单据 | 后端强制注入creator | 数据安全,员工只能看到自己的申请 |
| 流程变量 | 额外传入 sealUseMode | 审批流程可根据用章方式走不同分支 |
三、PC 端功能实现
3.1 印章台账
台账页面是用印管理的"数据基石",采用左侧分类导航 + 右侧表格列表的经典布局。


▲ 印章台账:左侧按字典 OA_SEAL_CLS 动态渲染分类菜单(行政类、业务类、财务类、其他类),点击分类自动筛选;右侧表格展示印章编号、名称、类型、状态、保管人等关键信息
台账设计要点:
- 分类导航联动:左侧
Menu组件绑定selectedSealCls状态,切换分类时自动触发gridApi.query(),查询参数中注入sealCls过滤条件 - 状态三色标签:通过字典
oa_seal_status渲染为绿色标签(在库)、灰色标签(停用)、橙色标签(使用中) - 类型/分类双维度:
sealType和sealCls分别通过CellDict组件渲染,支持两个维度的独立筛选 - 保管人联动:新增/编辑时选择保管人自动填充部门信息(
keeperName+keeperDeptName) - 印章照片:
CellImage组件展示印章照片缩略图,支持点击放大预览 - 导出Excel:支持按当前筛选条件导出印章数据
分类导航的前端实现简洁高效:
const selectedSealCls = ref<null | number>(null);
const sealClsOptions = getDictOptions(DICT_TYPE.OA_SEAL_CLS, 'number');
watch(selectedSealCls, () => {
onRefresh();
});
function handleSelectSealCls(sealCls: null | number) {
selectedSealCls.value = sealCls;
}
查询时将分类条件合并到请求参数中:
query: async ({ page }, formValues) => {
const queryParams = {
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
...(selectedSealCls.value === null
? {}
: { sealCls: selectedSealCls.value }),
};
return await getSealPage(queryParams);
},
3.2 用印申请列表
申请列表展示当前用户创建的所有用印申请单,支持新建、查看详情、删除操作。

▲ 用印申请列表:支持按单据编号、流程状态、印章名称、用章类型、用章方式、用印状态等多维度搜索筛选。列表默认只显示当前登录用户的申请单
列表设计要点:
- 我的单据过滤:后端
getSealApplyBillPage自动注入creator = 当前用户ID,前端也传入creator和companyId双重保障 - 单据编号链接:点击编号列自动跳转到详情页,编号格式为
OA110-{YYYYMMDD}{5位流水} - 双状态展示:同时展示「流程状态」(审批中/已通过/已拒绝)和「用印状态」(待处理/已完成/外借中/已归还/已逾期),通过
CellDict渲染为不同颜色标签 - 删除约束:删除前校验流程状态,审批中的单据不可删除
- 批量删除:勾选多条记录一键批量删除,同样遵守状态约束
- 印章选择搜索:搜索区的印章字段使用
HelpInput组件,点击弹出SealSelectModal选择印章
3.3 用印申请详情
详情页是申请单的核心交互页面,集 基本信息表单、印章信息、时间信息、附件管理 于一体。
「基本信息」区域:
| 字段 | 组件 | 说明 |
|---|---|---|
| 印章名称 | HelpInput | 点击弹出SealSelectModal选择印章,选中后自动回填编号/名称/类型/保管人 |
| 用章类型 | Select | 合同用章/证明用章/公函用章/其他用章(字典 oa_seal_use_type) |
| 用章方式 | Select | 现场用印/外借用章(字典 oa_seal_use_mode) |
| 文件标题 | Input | 需要用印的文件标题 |
| 文件类型 | Input | 文件类型描述 |
| 文件份数 | InputNumber | 需要盖章的文件份数 |
| 合同金额 | InputNumber | 用章类型=合同时显示 |
| 合同对方 | Input | 用章类型=合同时显示 |
| 预计用章时间 | DatePicker | 必填 |
| 预计归还时间 | DatePicker | 用章方式=外借时显示且必填 |
| 是否紧急 | Select | 否/是 |
| 用章事由 | TextArea | 必填,审批人以此判断合理性 |
| 备注 | TextArea | 选填 |
印章选择弹窗 SealSelectModal:
弹窗内部是一个独立的分页表格,支持按编号、名称、类型搜索过滤。选择一枚印章后点击确认,选中的印章信息自动回填到表单中。弹窗强制只展示状态为"在库"的印章(status: 0),停用和使用中的印章不可被选择:
query: async ({ page }, formValues) => {
const queryParams = {
pageNo: page.currentPage,
pageSize: page.pageSize,
status: 0, // 只显示在库状态的印章
...formValues,
};
return await getSealPage(queryParams);
},
条件字段联动:
- 当
useType = 1(合同用章)时,动态显示「合同金额」和「合同对方」字段 - 当
useMode = 2(外借用章)时,动态显示「预计归还时间」字段 - 审批流程到达「申请人归还印章」节点时,显示「实际归还时间」字段供填写
- 审批通过后所有字段自动切换为只读模式
四、流程设计:BPM 审批驱动状态流转
4.1 流程编排
用印申请使用 Flowable 引擎编排,流程定义 Key 为 oa_seal_apply_bill。流程设计兼顾了现场用印和外借用章两种场景:
- 员工填写用印信息(选择印章 + 填写事由/方式) → 提交流程
- 部门负责人审批
- 行政/法务审批
- 审批通过,用印状态变为「待处理」
- 行政管理员确认用印(现场用印 → 已完成)或登记外借(外借 → 外借中)
- 外借用章到期后,申请人归还印章 → 管理员确认 → 已归还
流程变量的妙用:提交时额外传入 sealUseMode(用章方式),Flowable 流程可以根据这个变量走不同的审批分支——比如外借用章增加法务审批节点,现场用印只需部门负责人审批即可。
4.2 FlowBillService 回调驱动状态变化
申请单服务实现了 FlowBillService<OaBillTypeEnum> 接口,BPM 引擎在流程状态变化时自动回调 updateProcessStatus:
@Override
public void updateProcessStatus(String businessKey, Integer status) {
Long id = Long.parseLong(businessKey);
log.info("[updateProcessStatus] 更新用印申请单流程状态,id: {}, status: {}", id, status);
// 校验用印申请单存在
validateSealApplyBillExists(id);
// 更新流程状态
SealApplyBillDO updateObj = new SealApplyBillDO();
updateObj.setId(id);
updateObj.setProcessStatus(status);
// 如果审批通过,设置用印状态为待处理
if (APPROVE.getStatus().equals(status)) {
updateObj.setUseStatus(SealUseStatusEnum.PENDING.getStatus());
}
sealApplyBillMapper.updateById(updateObj);
}
关键设计:
- 审批通过时,
useStatus自动设为PENDING(待处理),管理员可以在后续操作中确认用印或登记外借 - 审批拒绝/撤回时,只更新
processStatus,useStatus保持为空——申请单在业务层面"从未生效" - 整个状态流转由 BPM 框架通过
processDefinitionKey匹配FlowBillService实现类,零硬编码
4.3 用印状态的后续流转
审批通过后,用印状态通过一组 markAs* 方法驱动:
| 方法 | 目标状态 | 触发场景 |
|---|---|---|
markAsCompleted | COMPLETED(1) | 现场用印确认完成 |
markAsBorrowed | BORROWED(2) | 外借用章登记借出 |
markAsReturned | RETURNED(3) | 外借用章归还确认 |
markAsOverdue | OVERDUE(4) | 超过预计归还时间未归还 |
每个方法底层都调用 updateUseStatus,实现简洁统一:
@Override
public void updateUseStatus(Long id, Integer useStatus) {
validateSealApplyBillExists(id);
SealApplyBillDO updateObj = new SealApplyBillDO();
updateObj.setId(id);
updateObj.setUseStatus(useStatus);
sealApplyBillMapper.updateById(updateObj);
}
@Override
public void markAsCompleted(Long id) {
updateUseStatus(id, SealUseStatusEnum.COMPLETED.getStatus());
}
@Override
public void markAsBorrowed(Long id) {
updateUseStatus(id, SealUseStatusEnum.BORROWED.getStatus());
}
五、后端核心实现
5.1 两层数据模型
用印管理使用 2 张表实现完整的业务数据存储:
oa_seal(印章台账)
└── 1:N ──▶ oa_seal_apply_bill(用印申请单)
通过 seal_id 关联
与办公用品管理不同,用印管理没有明细行表——因为每次用印申请只针对一枚印章,不存在"一次申请盖多枚章"的场景。这种一对一的关系让数据模型更加简洁。
5.2 单据编号生成
每张申请单都有唯一的单据编号,格式为 OA110-{YYYYMMDD}{5位流水}(如 OA110-2026032700001)。编号在首次保存或提交时自动生成:
if (StringUtils.isBlank(saveReqVO.getBillCode())) {
saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
SystemEnum.OA, OaBillTypeEnum.OA_SEAL_APPLY_BILL));
}
110 是 OA 模块中用印申请单的类型编码,由 OaBillTypeEnum.OA_SEAL_APPLY_BILL 定义。流水号基于 Redis 自增,key 为 bill_code:OA:110:{yyyyMMdd},过期时间 2 天,保证分布式环境下编号唯一且递增。
5.3 提交流程:外借校验是关键
提交用印申请是整个业务中逻辑最密集的环节——需要生成编号、校验外借冲突、保存数据、发起BPM流程、保存附件,全部在一个方法中完成:
@Override
public Long submitSealApplyBill(SealApplyBillSaveReqVO saveReqVO) {
// 1. 生成单据编号
if (StringUtils.isBlank(saveReqVO.getBillCode())) {
saveReqVO.setBillCode(BillCodeUtils.generateBillCode(
SystemEnum.OA, OaBillTypeEnum.OA_SEAL_APPLY_BILL));
}
// 2. 外借时间冲突校验(仅外借用章需要)
if (saveReqVO.getUseMode() != null && saveReqVO.getUseMode() == 2) {
validateTimeConflict(saveReqVO);
}
// 3. 保存申请单(状态设为审批中)
SealApplyBillDO sealApplyBill = BeanUtils.toBean(saveReqVO, SealApplyBillDO.class)
.setProcessStatus(BpmTaskStatusEnum.RUNNING.getStatus());
sealApplyBillMapper.insertOrUpdate(sealApplyBill);
// 4. 发起 BPM 流程实例
Map<String, Object> variables = BpmProcessVariableUtils.buildBillVariables(saveReqVO);
variables.put(PV_SEAL_USE_MODE, saveReqVO.getUseMode());
String processInstanceId = processInstanceApi.submitProcessInstance(
Long.valueOf(saveReqVO.getCreator()),
new BpmProcessInstanceCreateReqDTO()
.setProcessDefinitionKey("oa_seal_apply_bill")
.setVariables(variables)
.setBusinessKey(String.valueOf(sealApplyBill.getId()))
).getCheckedData();
// 5. 回写流程实例ID
sealApplyBillMapper.updateById(new SealApplyBillDO()
.setId(sealApplyBill.getId())
.setProcessInstanceId(processInstanceId));
// 6. 保存附件信息
if (saveReqVO.getAttachments() != null) {
attachmentService.saveAttachmentList(
OaBillTypeEnum.OA_SEAL_APPLY_BILL.getTypeCode(),
sealApplyBill.getId(), saveReqVO.getAttachments());
}
return sealApplyBill.getId();
}
注意:提交流程时只校验冲突,不改变印章状态。印章状态(在库/使用中)的变更由管理员在确认用印或登记外借时手动触发,这样即使审批被撤回,印章状态也不会出现不一致。
5.4 外借时间冲突检测:防止一章多借
validateTimeConflict 是用印管理中最复杂的校验逻辑——需要判断同一枚印章在申请的外借时间段内,是否已有其他冲突的申请单:
private void validateTimeConflict(SealApplyBillSaveReqVO saveReqVO) {
if (saveReqVO.getSealId() == null
|| saveReqVO.getExpectedUseTime() == null
|| saveReqVO.getExpectedReturnTime() == null) {
return;
}
// 预计用章时间不能晚于预计归还时间
if (saveReqVO.getExpectedUseTime().isAfter(saveReqVO.getExpectedReturnTime())) {
throw exception(SEAL_TIME_CONFLICT);
}
// 查询同一印章在相同时间段内的冲突申请单
List<SealApplyBillDO> conflictBills = sealApplyBillMapper.selectList(
new LambdaQueryWrapperX<SealApplyBillDO>()
.eq(SealApplyBillDO::getSealId, saveReqVO.getSealId())
.eq(SealApplyBillDO::getUseMode, 2) // 仅检查外借用章
.ne(saveReqVO.getId() != null, SealApplyBillDO::getId, saveReqVO.getId())
.and(wrapper -> wrapper
// 场景1:审批中的申请单时间重合
.and(sub -> sub
.eq(SealApplyBillDO::getProcessStatus, RUNNING.getStatus())
.and(time -> time
.and(t -> t.le(SealApplyBillDO::getExpectedUseTime,
saveReqVO.getExpectedUseTime())
.ge(SealApplyBillDO::getExpectedReturnTime,
saveReqVO.getExpectedUseTime()))
.or(t -> t.le(SealApplyBillDO::getExpectedUseTime,
saveReqVO.getExpectedReturnTime())
.ge(SealApplyBillDO::getExpectedReturnTime,
saveReqVO.getExpectedReturnTime()))
.or(t -> t.ge(SealApplyBillDO::getExpectedUseTime,
saveReqVO.getExpectedUseTime())
.le(SealApplyBillDO::getExpectedReturnTime,
saveReqVO.getExpectedReturnTime()))
))
// 场景2:已通过且外借中的申请单时间重合
.or(sub -> sub
.eq(SealApplyBillDO::getProcessStatus, APPROVE.getStatus())
.eq(SealApplyBillDO::getUseStatus,
SealUseStatusEnum.BORROWED.getStatus())
// ... 同样的三种时间重叠判断
)
)
);
if (!conflictBills.isEmpty()) {
throw exception(SEAL_TIME_CONFLICT);
}
}
冲突检测的三种时间重叠模式:
已有区间: |=====已有外借=====|
新申请区间:
模式1(左交叉): |====新====| → 新开始在已有区间内
模式2(右交叉): |====新====| → 新结束在已有区间内
模式3(包含): |==========新==========| → 新完全包含已有
检测的两类冲突源:
| 冲突源 | 条件 | 含义 |
|---|---|---|
| 审批中的申请单 | processStatus = RUNNING | 还在审批但已占用时间段 |
| 已通过且外借中 | processStatus = APPROVE + useStatus = BORROWED | 印章实际正在外借 |
已完成、已归还、已拒绝的申请单不参与冲突检测——它们不再占用印章的时间资源。编辑自己的申请单时通过 .ne(SealApplyBillDO::getId, saveReqVO.getId()) 排除自身,避免"自己跟自己冲突"。
5.5 删除时清理流程与附件
删除申请单时,需要同时清理 BPM 流程实例和附件:
@Override
public void deleteSealApplyBill(Long id) {
SealApplyBillDO bill = sealApplyBillMapper.selectById(id);
if (bill == null) {
throw exception(SEAL_APPLY_BILL_NOT_EXISTS);
}
// 清理 BPM 流程实例(尽力清理,不阻塞删除)
if (StringUtils.isNotBlank(bill.getProcessInstanceId())) {
try {
processInstanceApi.deleteProcessInstance(
Long.valueOf(bill.getCreator()), bill.getProcessInstanceId());
} catch (Exception e) {
log.warn("[deleteSealApplyBill] 清理流程实例失败: {}", e.getMessage());
}
}
// 清理附件和主表
attachmentService.deleteAttachmentByBusiness(
OaBillTypeEnum.OA_SEAL_APPLY_BILL.getTypeCode(), id);
sealApplyBillMapper.deleteById(id);
}
流程清理使用 try-catch 包裹,即使流程引擎出错也不影响业务数据删除——这是一种"尽力清理"的防御性设计。
六、RuoYi Office 的创新设计
6.1 外借冲突检测:审批中也算占用
很多用印系统只检测"当前正在外借中"的印章,忽略了审批中的申请。RuoYi Office 将审批中的外借申请也纳入冲突检测范围:
- 传统方案:只检测
useStatus = 外借中→ A 审批中、B 也能申请同一时段 → 两个都通过后印章冲突 - RuoYi Office:检测
processStatus = 审批中+useStatus = 外借中→ 从审批阶段就防止冲突
这种设计虽然可能导致"A 审批被拒绝后,B 才能提交同一时段"的小延迟,但彻底杜绝了"两张审批通过的申请单争抢同一枚章"的严重问题。
6.2 现场用印不检测冲突
只有外借用章(useMode = 2)才触发时间冲突检测。现场用印是"到行政处盖一下就走",不存在印章被带走的问题,多人可以在同一天现场用同一枚章——这符合真实业务场景。
6.3 流程变量驱动审批分支
提交时额外传入 sealUseMode 流程变量,Flowable 流程可以据此设计不同的审批路径:
processInstanceVariables.put(PV_SEAL_USE_MODE, saveReqVO.getUseMode());
典型的分支策略:
- 现场用印(
sealUseMode = 1):部门负责人 → 行政确认 → 完成 - 外借用章(
sealUseMode = 2):部门负责人 → 法务审核 → 行政登记 → 申请人归还 → 完成
外借涉及印章离开公司,风险更高,增加法务审核节点是合理的。
6.4 条件字段的前端动态控制
申请表单不是所有字段一次性全部展示,而是根据用户选择动态显示:
- 选择「合同用章」→ 显示合同金额、合同对方
- 选择「外借用章」→ 显示预计归还时间
- 流程到达「归还」节点 → 显示实际归还时间(可编辑)
- 审批通过后 → 所有字段切换为只读
这种渐进式表单设计减少了用户的认知负担,只在需要时展示相关字段。
6.5 印章信息冗余保障历史追溯
申请单中冗余存储了 sealNo(编号)、sealName(名称)、sealType(类型)、keeperName(保管人)等印章信息。即使后续印章台账中的保管人更换了、名称修改了,历史申请单中的数据不受影响——"2026 年 3 月这次盖章时,保管人是张三"这一事实永远可查。
七、数据结构
7.1 表结构:oa_seal(印章台账)
| 字段 | 类型 | 说明 |
|---|---|---|
id | bigint | 主键 |
company_id / company_name | bigint / varchar | 所属公司 |
seal_no | varchar(100) | 印章编号 |
seal_name | varchar(200) | 印章名称 |
seal_type | bigint | 印章类型(字典 oa_seal_type) |
seal_cls | bigint | 印章分类(字典 oa_seal_cls) |
keeper_id | bigint | 保管人ID |
keeper_name | varchar(100) | 保管人名称 |
keeper_dept_id / keeper_dept_name | bigint / varchar | 保管部门 |
purchase_date | date | 购买日期 |
enable_date | date | 启用日期 |
disable_date | date | 停用日期 |
pic_url | varchar(500) | 印章照片 |
sort | int | 排序 |
status | int | 状态(0在库 / 1停用 / 2使用中) |
remark | varchar(500) | 备注 |
7.2 表结构:oa_seal_apply_bill(用印申请单)
| 字段 | 类型 | 说明 |
|---|---|---|
id | bigint | 主键 |
bill_code | varchar(50) | 单据编号(OA110-YYYYMMDD00001) |
process_instance_id | varchar(64) | Flowable 流程实例 ID |
process_status | int | 流程状态(BpmTaskStatusEnum) |
seal_id | bigint | 关联印章ID(外键) |
seal_no / seal_name | varchar | 印章编号/名称(冗余) |
seal_type | int | 印章类型(冗余) |
keeper_id / keeper_name | bigint / varchar | 保管人(冗余) |
keeper_dept_id / keeper_dept_name | bigint / varchar | 保管部门(冗余) |
cause | varchar(500) | 用章事由 |
use_type | int | 用章类型(1合同/2证明/3公函/4其他) |
use_mode | int | 用章方式(1现场/2外借) |
document_title | varchar(200) | 文件标题 |
document_type | varchar(100) | 文件类型 |
document_count | int | 文件份数 |
contract_amount | decimal(10,2) | 合同金额(合同用章时填写) |
contract_party | varchar(200) | 合同对方(合同用章时填写) |
expected_use_time | datetime | 预计用章时间 |
actual_use_time | datetime | 实际用章时间 |
expected_return_time | datetime | 预计归还时间(外借时填写) |
actual_return_time | datetime | 实际归还时间(外借时填写) |
use_status | int | 用印状态(0待处理/1已完成/2外借中/3已归还/4已逾期) |
is_urgent | int | 是否紧急(0否/1是) |
creator_name | varchar(100) | 申请人姓名 |
company_id / company_name | bigint / varchar | 所属公司 |
dept_id / dept_name | bigint / varchar | 所属部门 |
remark | varchar(500) | 备注 |
7.3 设计要点
- 冗余存储:申请单冗余了台账的
sealNo、sealName、sealType、keeperName等字段,避免列表查询时 JOIN 台账表。即使台账信息后续修改,已有申请单的历史数据不受影响 - 合同字段条件存储:
contractAmount和contractParty仅在useType = 1(合同用章)时有值,其他用章类型下为 null,不浪费存储空间 - 四个时间字段:
expectedUseTime+expectedReturnTime用于冲突检测和计划管理,actualUseTime+actualReturnTime用于实际操作记录,两组时间形成"计划 vs 实际"的对照 - 双状态字段:
processStatus跟踪审批流转,useStatus跟踪用印业务流转,两者独立但有因果关系(审批通过才触发用印状态初始化)
八、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 2 表简洁模型 | 台账+申请单,一次申请对应一枚章 | 数据模型简洁,无需明细行 |
| 5 种状态机 | SealUseStatusEnum 驱动用印流转 | 精确跟踪每张申请的全生命周期 |
| 现场/外借分流 | useMode 一个字段控制两条路径 | 一套代码覆盖两种用章场景 |
| 外借冲突检测 | validateTimeConflict 三模式时间段比对 | 防止同一章同一时段被多人借用 |
| 审批中也检测 | 冲突范围含 RUNNING 状态 | 从审批阶段就杜绝冲突 |
| 流程变量分支 | sealUseMode 驱动审批路径 | 外借增加法务审核,灵活可配 |
| 条件字段联动 | 合同字段/归还字段按需显示 | 减少认知负担,渐进式表单 |
| 分类导航联动 | 字典驱动菜单 + 查询/导出联动 | 可扩展、交互流畅 |
| FlowBillService 标准化 | 统一接口接收 BPM 回调 | 新增单据只需实现接口 |
| 我的单据过滤 | 后端强制注入 creator | 数据安全,用户只看自己的 |
| 删除级联清理 | 流程+附件+主表 | 数据一致性,无孤儿记录 |
| 冗余保障追溯 | 申请单冗余印章/保管人信息 | 历史数据不受台账修改影响 |
九、快速体验
操作路径:OA → 用印管理
推荐体验流程:
- 建台账:进入「印章信息管理」,新增几枚印章(如公章/在库、合同专用章/在库),设置类型和分类
- 分类导航:点击左侧「行政类」「财务类」等分类,观察右侧表格自动筛选
- 发起申请(现场用印):进入「用印申请管理」,点击「新建」,选择一枚在库印章,用章方式选「现场用印」,填写事由,提交审批
- 发起申请(外借用章):再新建一张,用章方式选「外借用章」,填写预计用章时间和预计归还时间
- 测试冲突检测:用同一枚印章再发起一张外借申请,时间段与步骤 4 重叠,观察系统报错
- 审批:到流程中心的待办任务中找到申请,审批通过
- 观察状态:审批通过后,用印状态自动变为「待处理」
- 确认用印:现场用印确认后状态变为「已完成」;外借登记后变为「外借中」
- 归还:对外借的申请执行归还操作,观察状态变为「已归还」
源码仓库:
| 平台 | 地址 |
|---|---|
| GitCode(后端) | gitcode.com/zhouzhongya… |
| GitCode(前端) | gitcode.com/zhouzhongya… |
| GitHub(后端) | github.com/yuqing2026/… |
结语
用印管理看似是 OA 系统中一个"小而精"的模块,但它承载的是企业最高等级的法律风险管控。2 张表的简洁模型让数据职责清晰,5 种状态机让每次用印都有据可查,外借冲突检测算法让"一章多借"成为不可能,流程变量驱动的审批分支让现场用印和外借用章走不同的审核路径。
这套设计模式不仅适用于用印管理,还可以推广到其他"资源独占+时间段预约"类的场景(如会议室预约、公车调度、设备外借等)。核心思想是:用时间段重叠检测保护独占资源,用 BPM 审批确保合规性,用状态机驱动业务全生命周期。
如果你正在设计类似的资源管理模块,或者对外借冲突检测算法感兴趣,欢迎参考源码实现。
📚 想要体验 RuoYi Office 的强大功能?
🌐 在线演示:ruoyioffice.com/web/(账号 admin / admin123)
📦 GitCode 开源:gitcode.com/zhouzhongya…
💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」
⭐ 如果觉得不错,请给个 Star 支持一下!