更新记录

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,完成权限允许/拒绝、照片、视频、单次、批次、撤销、取消、超时、水印、原片、缩略图、校验、查看、导出、解压和清理闭环。测试记录应包含插件版本、设备型号、系统版本、自定义基座版本、日期和结果,不要仅用“编译成功”代替运行验收。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android 按功能申请相机、麦克风和位置权限;iOS 按功能显示对应系统授权说明

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

仅处理调用方明确传入的业务字段、可选位置与品牌标识,以及用户主动拍摄的媒体文件和本地归档;插件不联网、不上传、不内置统计

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。