更新记录
0.2.0(2026-09-06)
- 新增同一原生相机页连续采集,可设置 2 至 20 项上限,并支持撤销上一项、手动完成和达到上限自动完成。
- 新增 schema 2 批次清单、每项独立清单、压缩归档与归档 SHA-256。
- 新增批次全部文件校验、压缩归档系统导出和已结束会话私有文件清理接口。
- 新增巡检、交付、维修、勘察四类内置水印模板,以及可选品牌标识像素水印。
- Android 与 iOS 保持原有单次拍摄接口兼容;示例页新增单次/批次切换和批次结果操作。
- iOS 文件摘要改为 64KB 分块计算,避免大视频校验时一次性读入内存;Android 延续分块摘要与已修复的相机页引用释放策略。
- 通过 HBuilderX 5.24 Android/iOS 插件模块和双端整项目清缓存编译,批次功能仍待双端真机验证。
- 完善插件说明、完整 API、文件与压缩归档、权限隐私、常见问题、水印与清单规范、测试矩阵和当前验证记录;明确区分源码、编译、模拟器、真机与市场发布结论。
0.1.0(2026-08-30)
- 建立 uni-app x 示例工程与 UTS API 插件骨架。
- 定义拍摄、能力查询、取消和本地一致性校验接口。
- 定义版本化清单、稳定错误对象和 Android/iOS 平台边界。
- 实现 Android CameraX 与 iOS AVFoundation 原生全屏拍摄页。
- 实现照片方向处理、五档像素水印、原片策略、缩略图、SHA-256 清单与本地校验。
- 实现短视频限时录制、可选录音及 Android Media3/iOS AVVideoComposition 像素水印转码。
- 实现显式设备定位、取消、超时、单会话状态机和失败清理。
- 通过 HBuilderX 5.24 双端插件模块与示例工程编译,待自定义基座真机验证。
- 示例页改为全中文,并统一为浅灰页面、白色卡片和绿色状态反馈。
- 增加照片/短视频原生查看和系统文件面板导出,能力直接内置且无其他插件依赖。
- 移除 Android/iOS 原生拍摄页顶部的英文副标题,仅显示工单号或“现场记录”。
- 修复 Android 相机页销毁后 CameraX
Preview继续持有PreviewView与页面上下文的内存泄漏,并拦截页面销毁后的异步相机初始化。
平台兼容性
uni-app x(4.0)
| Chrome | Safari | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|---|---|
| × | × | 8.0 | 0.2.0 | 13 | 0.2.0 | × | × |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| × | × | × | √ |
工程取证水印相机
xs-proof-camera 是面向 uni-app x App 的原生取证相机 UTS 插件。它使用一套接口封装 Android 与 iOS 的照片、短视频和连续批次采集,把业务字段写入最终媒体像素,并输出可校验的单项清单、批次清单和压缩归档。
当前版本:0.2.0
当前版本已完成 Android、iOS 源码实现,并通过 HBuilderX 5.24 插件模块和示例项目编译。Android API 34 模拟器曾验证
0.1.0单次照片、查看、导出和相机页泄漏修复;0.2.0批次能力与双端真实设备仍待验收。本文把源码实现、编译结果、模拟器结果和真机结论分开标记。
核心特性
| 能力 | 说明 |
|---|---|
| 双端统一接口 | Android 8.0 及以上、iOS 13.0 及以上使用相同 UTS 类型和回调结构 |
| 原生相机页面 | 支持拍照、短视频、前后摄像头、闪光灯或补光灯、点击对焦和双指缩放 |
| 像素级水印 | 时间、工单、操作人、坐标、地址和自定义字段写入最终照片或视频画面 |
| 连续批次采集 | 同一页面连续采集 2~20 项,支持撤销上一项、手动完成和达到上限自动完成 |
| 本地一致性校验 | 每项媒体计算 SHA-256,可单项校验,也可按批次清单校验全部媒体 |
| 版本化清单 | 单项使用 schema 1;批次使用 schema 2,保留插件版本、文件信息和拍摄上下文 |
| 压缩归档 | 批次完成后生成压缩归档与归档摘要,方便统一上传或交付 |
| 内置模板 | 提供巡检、交付、维修和勘察四类样式,批次模式可增加品牌标识 |
| 原片与缩略图 | 可选保留拍摄原片,可选生成 JPEG 缩略图 |
| 查看与导出 | 插件内置本地媒体查看,并通过系统文件面板复制媒体或压缩归档 |
| 私有文件清理 | 业务处理完成后,可按会话编号删除插件私有目录并返回释放空间 |
| 本地离线 | 插件不联网、不上传、不解析地址,也不内置广告或统计组件 |
文件摘要只能发现插件输出后媒体是否发生变化,不能证明拍摄现场、时间、位置或调用方传入字段一定真实,也不代表司法鉴定或电子签名。
平台支持
开发要求
| 项目 | 要求 |
|---|---|
| 开发框架 | uni-app x App |
| HBuilderX | 5.0 及以上 |
| uni-app x | 4.0 及以上 |
| Android | Android 8.0 / API 26 及以上 |
| iOS | iOS 13.0 及以上 |
插件不支持 Web、小程序和 HarmonyOS,也不会在这些平台模拟降级拍摄。
双端实现差异
| 项目 | Android | iOS |
|---|---|---|
| 相机实现 | CameraX 1.6.1 | AVFoundation |
| 视频水印 | Media3 Transformer 1.11.0 | AVVideoComposition 与 Core Animation |
| 照片水印 | Bitmap / Canvas | Core Graphics |
| 视频容器 | MP4 | 通常为 MP4,系统回退时可能为 MOV |
| 文件查看 | 插件内置照片或视频页面 | 系统快速预览 |
| 文件导出 | 系统文件创建面板 | 系统文档选择器 |
| 压缩归档 | 系统压缩流 | 本地无压缩写入 |
| 摘要读取 | 64KB 分块 | 64KB 分块 |
iOS 当前压缩归档采用无压缩存储,每个条目和整个归档均受 4GB 范围限制。品牌标识应传入真实、可读取的本地图片路径;两端对无效图片文件的底层处理不同,业务接入时应先验证路径。
安装与运行
把插件目录放入项目的 uni_modules/xs-proof-camera,然后从插件根目录导入公开接口:
import {
getProofCameraCapabilities,
getProofWatermarkPreset,
openProofCamera,
openProofCameraBatch,
cancelProofCamera,
verifyProofAsset,
verifyProofBatch,
previewProofAsset,
exportProofAsset,
exportProofArchive,
cleanupProofSession
} from '@/uni_modules/xs-proof-camera'
不要从 utssdk 或 Android、iOS 子目录导入内部类,否则版本升级时不能保证兼容。
必须使用自定义基座
插件包含原生权限、原生活动、Android 依赖和 iOS 系统框架。标准基座不包含这些内容,无法验证本插件。请在 HBuilderX 中制作包含本插件的自定义基座,再安装到 Android 模拟器或真实设备。
macOS 上的 iOS 模拟器没有真实摄像头,不能完成实际拍摄。它可以验证工程编译、应用启动和不依赖摄像头的页面;相机、视频、权限和性能仍需物理 iPhone。
原生声明
| 平台 | 声明 | 触发条件 |
|---|---|---|
| Android | CAMERA |
打开任何拍摄会话 |
| Android | RECORD_AUDIO |
视频模式且 recordAudio: true |
| Android | ACCESS_COARSE_LOCATION、ACCESS_FINE_LOCATION |
未传位置且 requestDeviceLocation: true |
| iOS | 相机用途说明 | 打开任何拍摄会话 |
| iOS | 麦克风用途说明 | 视频模式且需要录音 |
| iOS | 使用期间位置用途说明 | 插件需要主动获取设备位置 |
插件不会在页面初始化时主动申请权限。权限流程由明确的拍摄操作触发。更完整的触发规则见 docs/PERMISSIONS_AND_PRIVACY.md。
快速开始
单次拍照
import {
openProofCamera,
previewProofAsset,
verifyProofAsset
} from '@/uni_modules/xs-proof-camera'
let currentSessionId = ''
function takeProofPhoto() {
currentSessionId = openProofCamera({
mode: 'photo',
workOrderId: 'WO-2026-0001',
operatorName: '张三',
fields: [
{ label: '设备', value: 'A-17' },
{ label: '检查结果', value: '正常' }
],
location: null,
requestDeviceLocation: true,
requireLocation: false,
watermark: {
position: 'bottom-card',
showTimestamp: true,
showCoordinates: true,
showAddress: true,
foregroundColor: '#FFFFFF',
backgroundColor: '#102A23',
backgroundOpacity: 0.8,
fontSize: 30,
margin: 28
},
output: {
originalPolicy: 'watermarked-only',
jpegQuality: 90,
maxLongEdge: 4096,
createThumbnail: true,
thumbnailLongEdge: 720
},
timeoutMs: 120000,
maxVideoDurationMs: 30000,
recordAudio: false
}, (result) => {
currentSessionId = ''
console.log('照片', result.filePath)
console.log('清单', result.manifestPath)
verifyProofAsset({
filePath: result.filePath,
manifestPath: result.manifestPath
}, (verifyResult) => {
console.log('一致性校验', verifyResult.valid)
}, (error) => {
console.error(error.code, error.message)
})
previewProofAsset({
filePath: result.filePath,
type: result.type
}, () => {}, (error) => {
console.error(error.code, error.message)
})
}, (error) => {
currentSessionId = ''
console.error(error.code, error.message, error.nativeCode)
})
}
所有参数字段均需显式传入。照片模式仍需传入视频相关字段,但插件会忽略不适用的值。
批次采集
import {
getProofWatermarkPreset,
openProofCameraBatch,
verifyProofBatch,
exportProofArchive
} from '@/uni_modules/xs-proof-camera'
const watermark = getProofWatermarkPreset('inspection')
const batchSessionId = openProofCameraBatch({
mode: 'photo',
workOrderId: 'WO-2026-0002',
operatorName: '李四',
fields: [{ label: '区域', value: '二号机房' }],
location: null,
requestDeviceLocation: true,
requireLocation: false,
watermark: watermark,
output: {
originalPolicy: 'watermarked-only',
jpegQuality: 90,
maxLongEdge: 4096,
createThumbnail: true,
thumbnailLongEdge: 720
},
timeoutMs: 300000,
maxVideoDurationMs: 30000,
recordAudio: false,
maxAssets: 5,
presetId: 'inspection',
logoPath: null,
logoWidthPercent: 16,
logoOpacity: 0.9
}, (result) => {
console.log('项目数', result.assetCount)
console.log('批次清单', result.batchManifestPath)
console.log('压缩归档', result.archivePath)
console.log('归档摘要', result.archiveSha256)
verifyProofBatch({
manifestPath: result.batchManifestPath
}, (verifyResult) => {
console.log('整批有效', verifyResult.valid)
}, (error) => {
console.error(error.code, error.message)
})
exportProofArchive({
filePath: result.archivePath,
fileName: 'WO-2026-0002_取证包.zip',
mimeType: 'application/zip'
}, (exportResult) => {
console.log('已保存', exportResult.saved)
}, (error) => {
console.error(error.code, error.message)
})
}, (error) => {
console.error(error.code, error.message)
})
批次内只能使用同一种媒体类型。用户可以在原生页面撤销上一项或手动完成;达到 maxAssets 后自动完成。取消整个批次时不会返回部分成功结果,并会清理本次会话目录。
接口总览
| 接口 | 返回值 | 说明 |
|---|---|---|
getProofCameraCapabilities() |
ProofCameraCapabilities |
同步读取当前设备真实能力,不申请权限 |
getProofWatermarkPreset(presetId) |
ProofWatermarkOptions |
同步返回内置模板参数 |
openProofCamera(options, onSuccess, onFail) |
string |
打开一次采集并立即返回会话编号;启动失败返回空字符串 |
openProofCameraBatch(options, onSuccess, onFail) |
string |
打开连续采集并立即返回会话编号 |
cancelProofCamera(sessionId) |
void |
取消匹配的活动拍摄会话 |
verifyProofAsset(options, onSuccess, onFail) |
void |
校验一个最终媒体与 schema 1 清单 |
verifyProofBatch(options, onSuccess, onFail) |
void |
校验 schema 2 批次清单中的全部媒体 |
previewProofAsset(options, onSuccess, onFail) |
void |
打开本地照片或短视频 |
exportProofAsset(options, onSuccess, onFail) |
string |
打开系统面板复制一个媒体文件,并返回任务编号 |
exportProofArchive(options, onSuccess, onFail) |
string |
打开系统面板复制压缩归档,并返回任务编号 |
cleanupProofSession(options, onSuccess, onFail) |
void |
删除已结束会话的私有文件 |
详细接口签名、参数范围、返回结构和错误码见 docs/API.md。
能力查询
getProofCameraCapabilities() 不弹出权限框,也不启动相机。建议在页面初始化后读取能力,再决定是否展示照片、视频、切换镜头或闪光灯入口。
| 字段 | 含义 |
|---|---|
platform |
当前平台,值为 android 或 ios |
available |
当前设备是否存在可用相机 |
photoSupported |
当前实现是否支持照片 |
videoSupported |
当前实现是否支持短视频 |
frontCameraAvailable |
是否检测到前置摄像头 |
backCameraAvailable |
是否检测到后置摄像头 |
flashAvailable |
是否检测到闪光灯或补光能力 |
locationSupported |
系统是否提供位置能力;不等于已经授权 |
minimumSystemVersion |
当前平台最低系统版本 |
拍摄参数要点
| 参数 | 有效范围或规则 |
|---|---|
mode |
photo 或 video |
workOrderId |
最长 120 个字符,可为空 |
operatorName |
最长 80 个字符,可为空 |
fields |
最多 20 项;空标签或空值不绘制 |
location |
调用方位置或 null;经度 -180~180,纬度 -90~90 |
requestDeviceLocation |
无调用方位置时是否申请一次设备定位 |
requireLocation |
无有效位置时是否禁止完成拍摄 |
timeoutMs |
10000~600000 毫秒 |
maxVideoDurationMs |
1000~120000 毫秒 |
recordAudio |
仅视频模式生效;权限不可用时当前实现降级为无声视频 |
maxAssets |
批次模式 2~20 |
logoWidthPercent |
批次品牌标识宽度占比 5~30 |
logoOpacity |
批次品牌标识透明度 0~1 |
位置优先级为:调用方传入位置、设备临时定位、无位置。插件获取设备位置时最多等待约 4 秒;失败且 requireLocation: false 时继续无位置拍摄,失败且为 true 时返回位置权限错误。设备定位不会自动产生地址,地址只来自调用方传入的 location.address。
水印与输出
水印布局
top-left:左上。top-right:右上。bottom-left:左下。bottom-right:右下。bottom-card:底部信息卡。
水印按时间、工单、操作人、位置、自定义字段的顺序生成,最多写入 8 行。长文本会截断并显示省略号,不做无限换行。颜色建议使用 #RRGGBB 或 #AARRGGBB;无法解析时会回退到安全颜色,而不是作为参数错误终止。
原片策略
| 值 | 当前行为 |
|---|---|
keep-original |
保留水印处理前的照片或视频,并在 originalPath 返回路径 |
watermarked-only |
只保留最终加水印媒体 |
discard |
当前同样只保留最终加水印媒体,用于表达不保留原片意图 |
discard 与 watermarked-only 在 0.2.0 的文件结果相同,不应依赖两者产生不同目录结构。
内置模板
getProofWatermarkPreset 支持 inspection、delivery、maintenance、survey 和 custom。custom 返回默认巡检样式,调用方可以继续修改。模板只包含视觉参数,不会替调用方生成工单、人员、地址或自定义字段。
完整模板值、字段顺序和清单结构见 docs/WATERMARK_SCHEMA.md。
结果与文件操作
单次结果
ProofCaptureResult 包含会话编号、媒体类型、最终文件路径、可选原片和缩略图路径、单项清单路径、SHA-256、尺寸、视频时长、拍摄时间与位置写入状态。
批次结果
ProofBatchCaptureResult 包含会话编号、schema 2 批次清单路径、压缩归档路径、归档 SHA-256、项目数和每项单次结果。
校验语义
- 文件摘要一致或不一致都属于正常校验结果,通过成功回调返回
valid。 - 媒体缺失、清单无法读取、JSON 无效或 schema 不支持才走失败回调。
- 单项校验只比较调用方指定媒体与单项清单中的摘要。
- 批次校验按批次清单所在目录解析媒体文件,返回通过数、缺失数、变化数和逐项明细。
查看与导出
- 查看成功回调表示系统或插件预览页已打开,不表示用户已经看完。
- 导出只复制文件,不移动或删除应用私有目录中的原文件。
- 用户关闭导出面板不属于失败,成功结果中返回
saved: false、cancelled: true。 - 同一时刻只允许一个导出任务,重复发起返回
BUSY。
清理
成功拍摄后的文件会保留在插件私有目录,不会自动进入系统相册。业务完成上传、归档或导出后,应保存 sessionId 并调用:
cleanupProofSession({
sessionId: finishedSessionId
}, (result) => {
console.log('删除文件数', result.fileCount)
console.log('释放字节数', result.bytesFreed)
}, (error) => {
console.error(error.code, error.message)
})
活动会话不能被清理。接口只允许删除插件私有根目录下合法的直接子目录,不接受任意文件系统路径。文件组成与生命周期见 docs/FILES_AND_ARCHIVES.md。
会话与回调规则
- 同一进程同一时刻只运行一个拍摄会话,第二次打开返回
BUSY。 openProofCamera和openProofCameraBatch正常启动时返回非空会话编号;启动失败时返回空字符串并调用失败回调。- 每个会话只进入一次成功或失败终态。
- 用户关闭相机页、主动取消或超时不会返回部分成功结果。
- 取消未知或已结束的会话编号会被忽略,不影响当前会话。
- 批次撤销上一项时,会删除该项的最终媒体、原片、缩略图和单项清单。
错误处理
失败回调收到稳定结构:
{
code: 'CAMERA_PERMISSION_DENIED',
message: '用户未授予相机权限',
nativeCode: null
}
业务逻辑应依据 code 分支;message 面向开发者,可能随说明优化;nativeCode 仅用于设备诊断。当前稳定错误范围包括参数、权限、设备、取消、中断、渲染、写入、摘要、清单、文件、预览、导出、归档、清理、忙碌和平台不支持。
MICROPHONE_PERMISSION_DENIED、BATCH_EMPTY 和 UNSUPPORTED 已保留在公开类型中,但当前主流程并不一定直接返回这些值;例如录音权限不可用时当前实现优先降级为无声视频。不要把预留错误码当成已验证的运行分支。
常见问题
拍摄后的文件在哪里?
文件保存在应用私有目录。使用 result.filePath、result.manifestPath 或批次结果中的路径访问;需要让用户保存到外部位置时调用导出接口,不要自行猜测系统路径。
为什么系统相册里找不到照片?
插件默认不写系统相册,避免无意泄露现场资料。可以通过 exportProofAsset 让用户选择保存位置,或者由业务层按自身授权流程上传。
iOS 模拟器为什么打不开相机?
iOS 模拟器没有真实摄像头。请使用物理 iPhone;如果暂时没有设备,可先用 Android 模拟器的虚拟场景检查单次照片流程。
哈希校验通过是否代表证据真实?
不是。它只表示当前媒体字节与清单记录的摘要一致,不能证明拍摄对象、时间、坐标或调用方字段真实。
可以直接上传云端吗?
插件不包含网络上传。请在成功回调后由业务层上传媒体、清单和归档,并在服务端保存摘要与接收时间。
更多接入问题见 docs/FAQ.md。
当前限制
- 不支持后台拍摄或后台录制。
- 不支持云上传、断点续传、账号体系和服务端签名。
- 不支持反向地理编码;地址只接受调用方传入。
- 单次模式不提供品牌标识参数,品牌标识当前仅在批次接口开放。
- 自定义水印最终最多渲染 8 行,长文本采用截断策略。
- iOS 无压缩归档不适合超大视频批次,单条目或归档达到 4GB 前应由业务层限制。
- 真实摄像头、真实视频轨道、不同厂商设备和 iPhone 镜头组合仍待完整真机矩阵验证。
文档
| 文档 | 内容 |
|---|---|
docs/API.md |
完整接口、类型、参数范围、回调和错误码 |
docs/WATERMARK_SCHEMA.md |
水印模板、像素渲染和清单 schema |
docs/FILES_AND_ARCHIVES.md |
文件目录、归档、导出和清理 |
docs/PERMISSIONS_AND_PRIVACY.md |
权限、位置、隐私和业务合规建议 |
docs/FAQ.md |
常见问题和故障排查 |
docs/TESTING.md |
双端测试矩阵 |
docs/VERIFICATION.md |
当前版本的已验证证据与待验证边界 |
发布前建议
在业务上线或插件市场发布前,至少使用一台 Android 真机和一台物理 iPhone,完成权限允许/拒绝、照片、视频、单次、批次、撤销、取消、超时、水印、原片、缩略图、校验、查看、导出、解压和清理闭环。测试记录应包含插件版本、设备型号、系统版本、自定义基座版本、日期和结果,不要仅用“编译成功”代替运行验收。

收藏人数:
购买源码授权版(
试用
使用 HBuilder 导入示例项目
赞赏(0)
下载 5
赞赏 0
下载 12683906
赞赏 1956
赞赏
京公网安备:11010802035340号