关键词:移动端,拍照,文件服务,图片字段
一、需求
在某些业务场景下,需要通过拍照功能,给其他单据页面上的图片字段赋值。
本篇文章,以云之家app为案例,在云之家中访问苍穹页面,调起拍照功能,并给其他。
二、思路与方案
移动端拍照,可以通过executeClientCommand执行指令的方式,调起拍照功能。
拍照之后,在customEvent事件中,返回拍照结果,拍照结果是一个字符串,是经过Base64编码的图片资源。
将图片资源解码,然后转成byte数组,调用文件服务进行上传。
查询某个带有图片字段的单据,将上传路径与文件名拼接之后,赋值给图片字段,并且保存。
在idea的断点中,可以看到拍照之后,获取的图片资源是一个 "/9j" 为开头的字符串,这是一个经过base64编码之后的jpg图片,关于"/9j",感兴趣的开发者,可以在必应、百度搜一下相关文档。
三、实现过程
1.开发移动端页面
简单设计一个移动端页面,拖入一个按钮用来触发点击即可。
2.开发插件代码
触发按钮点击时,调用executeClientCommand,调起摄像头
@Override public void click(EventObject evt) { super.click(evt); if (evt.getSource() instanceof Control) { switch (((Control) evt.getSource()).getKey()) { case "kdec_camera": { System.out.println(); HashMap map = new HashMap(); map.put("method", "selectPic"); //selectPic 为云之家方法名 HashMap args = new HashMap(); args.put("type", "camera"); //类型,“camera”代表“相机”,“photo”代表“相册”,不传表示从相机、相册中选择 map.put("args", args); //args 调用该云之家方法需要传递的参数 this.getView().executeClientCommand("callAPPApi", map); } break; } } }
在customEvent事件中,接收拍照结果,转为inputStream,使用FileService进行上传,最后将文件路径保存到图片字段上。
@Override public void customEvent(CustomEventArgs e) { super.customEvent(e); switch (e.getEventName()) { case "selectPic": JSONObject jsonObject = JSONObject.parseObject(e.getEventArgs()); String fileExt = jsonObject.get("fileExt").toString(); String fileData = jsonObject.get("fileData").toString(); FileService fileService = FileServiceFactory.getAttachmentFileService(); // 构建文件名和文件上传路径 SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); String fileName = sdf.format(new Date()) + "." + fileExt; String uploadPath = "/image/test/" + fileName; OutputStream outputStream = new ByteArrayOutputStream(); InputStream inputStream = null; try { // 字符串解码为byte数组 byte[] bytes = decode(fileData); outputStream.write(bytes); inputStream = parse(outputStream); // 文件服务上传 fileService.upload(new FileItem(fileName, uploadPath, inputStream)); // 打印日志 log.info("文件路径为:"+uploadPath); //System.out.println("文件路径为:"+uploadPath); } catch (Exception ex) { // 如果抛异常,将文件路径置空 uploadPath = null; throw new RuntimeException(ex); } finally { try { outputStream.close(); if (inputStream != null) { inputStream.close(); } } catch (IOException ex) { throw new RuntimeException(ex); } } // 保存,判断文件路径为空(在处理Stream的时候、上传的时候抛异常),则不保存 if (uploadPath != null) { saveObject(uploadPath); } break; } }
拍照的结果是一个字符串,是经过Base64编码的图片资源,以下方法可以将图片的字符串转为byte数组
public byte[] decode (String fileData) { // 解密 Base64.Decoder decoder = Base64.getMimeDecoder(); // 去掉base64前缀 data:image/jpeg;base64, fileData = fileData.substring(fileData.indexOf(",", 1) + 1, fileData.length()); byte[] bytes = decoder.decode(fileData); // 处理数据 for (int i = 0; i < bytes.length; ++i) { if (bytes[i] < 0) { bytes[i] += 256; } } return bytes; }
stream(输出流转输入流)转换方法
public ByteArrayInputStream parse(final OutputStream out) throws Exception { ByteArrayOutputStream baos = (ByteArrayOutputStream) out; final ByteArrayInputStream swapStream = new ByteArrayInputStream(baos.toByteArray()); return swapStream; }
保存方法,
注意,这里保存的是我的苍穹环境上的“纺织物”单据上的图片字段,这里仅作为参考,请各位开发者根据自己的业务需求修改代码。
public OperationResult saveObject (String uploadPath) { DynamicObject[] dynamicObjects = BusinessDataServiceHelper.load("kdec_textline_bill", "id,billno,kdec_textline_name,kdec_picturefield", new QFilter("billno", QCP.like, "CSDES-2022-10-14-601").toArray()); if (dynamicObjects.length > 0) { dynamicObjects[0].set("kdec_picturefield", uploadPath); } // 保存到纺织物单据上 OperationResult result = SaveServiceHelper.saveOperate("kdec_textline_bill", dynamicObjects); return result; }
四,效果图
点击按钮
拍照
在日志中找到文件路径
拼接url,http://ip地址:端口号/ierp/attachment/preview.do?path=文件路径。
预览发现,文件已上传成功。
进入“纺织物”单据中,发现该图片已经展示在页面上的图片字段中:
五、开发环境版本
苍穹5.0
六、注意事项
有些开发者,想要在企业微信上使用该功能,企业微信也可以支持拍照,调用之前需要在企业微信的应用管理平台,配置鉴权。
参考文档:https://vip.kingdee.com/link/s/lxhQ6
七、参考资料
camera_and_setpicturefield.zip(9.86KB)
推荐阅读