uniapp —— 小程序使用百度云OCR鉴别身份证和营业执照
开发上传卡证功能时,必须要鉴别上传的图片,否则功能相当于废的;至于为什么选择百度云,那是因为阿里云又贵又难用,百度云的免费额度更多,并且更成熟专业;且针对于用户的角度,两者没有太大的使用区别,只需要追求一个稳定的服务即可。 开发场景:uniapp -> 微信小程序
零、我先把封装好的代码和步骤放出来,方便下文理解
- 获取百度云
access_token ; - 根据获取到的
token 调用鉴别接口; - 鉴别错误处理;
- 鉴别成功,上传图片。
import { user_profile_baidu } from '@/utils/request/api.js';
const BAIDU_PROFILE = { grant_type: 'client_credentials', client_id: '你的应用 API-Key', client_secret: '你的应用 Secret-Key' }
let BAIDU_REQUEST_API
const BAIDU_LICENSE_API = 'https://aip.baidubce.com/rest/2.0/ocr/v1/business_license'
const BAIDU_IDCARD_API = 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard'
const BAIDU_BUSSINESS_PARAMS = { image: '' }
const BAIDU_IDCARD_PARAMS = { image: '', id_card_side: '' }
const Storage = (key, value, seconds = 3600 * 24 * 29) => {
let nowTime = Date.parse(new Date()) / 1000;
if (key && value) {
let expire = nowTime + Number(seconds);
uni.setStorageSync(key, value + '|' +expire)
} else if (key && !value) {
let val = uni.getStorageSync(key);
if (val) {
let temp = val.split('|')
if (!temp[1] || temp[1] <= nowTime) {
uni.removeStorageSync(key)
return '';
} else {
return temp[0];
}
}
}
}
const ErrExcept = (type, err) => {
switch(type) {
case 'license':
if (err.data.error_code) {
switch(err.data.error_code) {
case 1,2,4:
uni.$u.toast('识别失败,服务器错误')
break;
case 17,18,19:
uni.$u.toast('识别失败,资源超额')
break;
default:
uni.$u.toast('识别失败,请检查图片')
break;
}
throw new Error('server error')
}
break;
case 'card':
if (err.data.image_status != 'normal') {
uni.$u.toast('识别失败,请重新上传正确的证件照!')
throw new Error('image error')
}
break;
case 'card2':
if (err.data.image_status != 'normal') {
uni.$u.toast('识别失败,请重新上传正确的证件照!')
throw new Error('image error')
}
break;
default:
uni.$u.toast('上传成功');
}
}
export const GET_ACCESS_TOKEN = () => {
return new Promise((resolve, reject) => {
try{
let access_token = Storage('access_token');
if (!access_token || access_token == '') {
user_profile_baidu(BAIDU_PROFILE).then(res => {
access_token = res.data.access_token;
Storage('access_token', access_token)
resolve(access_token)
})
} else {
resolve(access_token)
}
}catch(e){
reject(e)
}
})
}
export const BAIDU_OCR = (type, apiName, token) => {
return new Promise((resolve, reject) => {
let pic = uni.chooseImage({
sizeType: ['compressed'],
count: 1,
sourceType: type == 0 ? 'camera' : 'album',
success(res) {
uni.getFileSystemManager().readFile({
filePath: res.tempFilePaths[0],
encoding: 'base64',
success(file) {
uni.showLoading({title: '识别中'})
let baidu_data;
if (apiName == 'license') {
BAIDU_REQUEST_API = BAIDU_LICENSE_API
baidu_data = BAIDU_BUSSINESS_PARAMS
} else {
BAIDU_REQUEST_API = BAIDU_IDCARD_API
baidu_data = BAIDU_IDCARD_PARAMS
if (apiName == 'card') {
baidu_data.id_card_side = 'front'
} else {
baidu_data.id_card_side = 'back'
}
}
baidu_data.image = file.data
uni.request({
url: `${BAIDU_REQUEST_API}?access_token=${token}`,
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded'
},
data: baidu_data,
success(baidu_res) {
uni.hideLoading()
ErrExcept(apiName, baidu_res)
resolve(res.tempFilePaths[0])
},
fail(baidu_res_reject) {
uni.hideLoading()
uni.$u.toast('识别失败,请重新上传')
reject(baidu_res_reject)
}
})
},
fail(baidu_reject) {
uni.$u.toast('读取失败,请重新上传')
reject(baidu_reject)
}
})
},
fail: (err) => {
uni.$u.toast('获取照片失败,请重新上传')
reject(err)
}
})
})
}
一、注册百度云开发者平台
- 注册开发者平台账户《传送地址》;
- 建议进行企业实名认证(可以拥有更多的免费鉴别卡证次数,且不仅是文字识别,其他各项服务也会赠送免费的调用接口次数);文字识别OCR的免费资源情况如下:
- 开通文字识别服务《传送地址》;
- 领取免费资源(建议每一项都勾选全选);
- 创建应用,以获取
API Key 和Secret Key ;
二、获取 access_token (最好是由后端获取递给前端)
access_token 是百度云服务器返回给你代码的鉴权加密串,需要将上文获取到的API Key 和Secret Key 以get 明文请求方式发送,我们知道get 请求会把参数拼接在请求路径里面,容易导致密钥信息泄露;另外,前端获取加密串的时候,会经常性的提示 access_token 已过期,也无法经常保证程序的可用性,用户体验极差。所以为了保持密钥信息的安全性,最好是交由后端请求获取 access_token,处理完之后再递给前端。 access_token 有效期是30天,我们封装一个本地存储函数,定时清除 access_token。
import { user_profile_baidu } from '@/utils/request/api.js';
const BAIDU_PROFILE = { grant_type: 'client_credentials', client_id: '你的应用 API-Key', client_secret: '你的应用 Secret-Key' }
const Storage = (key, value, seconds = 3600 * 24 * 29) => {
let nowTime = Date.parse(new Date()) / 1000;
if (key && value) {
let expire = nowTime + Number(seconds);
uni.setStorageSync(key, value + '|' +expire)
} else if (key && !value) {
let val = uni.getStorageSync(key);
if (val) {
let temp = val.split('|')
if (!temp[1] || temp[1] <= nowTime) {
uni.removeStorageSync(key)
return '';
} else {
return temp[0];
}
}
}
}
export const GET_ACCESS_TOKEN = () => {
return new Promise((resolve, reject) => {
try{
let access_token = Storage('access_token');
if (!access_token || access_token == '') {
user_profile_baidu(BAIDU_PROFILE).then(res => {
access_token = res.data.access_token;
Storage('access_token', access_token)
resolve(access_token)
})
} else {
resolve(access_token)
}
}catch(e){
reject(e)
}
})
}
三、调用鉴别接口
在应用列表页点击管理,进入详情页,点击展开,获取功能接口:
let BAIDU_REQUEST_API
const BAIDU_LICENSE_API = 'https://aip.baidubce.com/rest/2.0/ocr/v1/business_license'
const BAIDU_IDCARD_API = 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard'
const BAIDU_BUSSINESS_PARAMS = { image: '' }
const BAIDU_IDCARD_PARAMS = { image: '', id_card_side: '' }
const ErrExcept = (type, err) => {
switch(type) {
case 'license':
if (err.data.error_code) {
switch(err.data.error_code) {
case 1,2,4:
uni.$u.toast('识别失败,服务器错误')
break;
case 17,18,19:
uni.$u.toast('识别失败,资源超额')
break;
default:
uni.$u.toast('识别失败,请检查图片')
break;
}
throw new Error('server error')
}
break;
case 'card':
if (err.data.image_status != 'normal') {
uni.$u.toast('识别失败,请重新上传正确的证件照!')
throw new Error('image error')
}
break;
case 'card2':
if (err.data.image_status != 'normal') {
uni.$u.toast('识别失败,请重新上传正确的证件照!')
throw new Error('image error')
}
break;
default:
uni.$u.toast('上传成功');
}
}
export const BAIDU_OCR = (type, apiName, token) => {
return new Promise((resolve, reject) => {
let pic = uni.chooseImage({
sizeType: ['compressed'],
count: 1,
sourceType: type == 0 ? 'camera' : 'album',
success(res) {
uni.getFileSystemManager().readFile({
filePath: res.tempFilePaths[0],
encoding: 'base64',
success(file) {
uni.showLoading({title: '识别中'})
let baidu_data;
if (apiName == 'license') {
BAIDU_REQUEST_API = BAIDU_LICENSE_API
baidu_data = BAIDU_BUSSINESS_PARAMS
} else {
BAIDU_REQUEST_API = BAIDU_IDCARD_API
baidu_data = BAIDU_IDCARD_PARAMS
if (apiName == 'card') {
baidu_data.id_card_side = 'front'
} else {
baidu_data.id_card_side = 'back'
}
}
baidu_data.image = file.data
uni.request({
url: `${BAIDU_REQUEST_API}?access_token=${token}`,
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded'
},
data: baidu_data,
success(baidu_res) {
uni.hideLoading()
ErrExcept(apiName, baidu_res)
resolve(res.tempFilePaths[0])
},
fail(baidu_res_reject) {
uni.hideLoading()
uni.$u.toast('识别失败,请重新上传')
reject(baidu_res_reject)
}
})
},
fail(baidu_reject) {
uni.$u.toast('读取失败,请重新上传')
reject(baidu_reject)
}
})
},
fail: (err) => {
uni.$u.toast('获取照片失败,请重新上传')
reject(err)
}
})
})
}
四、完整应用
使用的组件是 uview2.0 ,需要的就替换成自己的组件。《传送地址》
<template>
<!-- 营业执照 -->
<view class="box_radius" style="margin-top: 20rpx;">
<text>请点击上传您的营业执照</text>
<view @click="bindActions('license')">
<image style="width: 100%;margin-top: 20rpx;" :src="fileinp_yin_path || 'http://nq34.gzfloat.com/public/user/license@2x.png'" mode="widthFix"></image>
</view>
</view>
<!-- 身份证正反面 -->
<view class="box_radius" style="margin: 20rpx 0;">
<text>请点击上传您的身份证正反面</text>
<view>
<view @click="bindActions('card')">
<image style="width: 100%;margin: 20rpx 0;" :src="fileinp_zheng_path || 'http://nq34.gzfloat.com/public/user/card@2x.png'" mode="widthFix"></image>
</view>
<view @click="bindActions('card2')">
<image style="width: 100%;" :src="fileinp_fan_path || 'http://nq34.gzfloat.com/public/user/card2@2x.png'" mode="widthFix"></image>
</view>
</view>
</view>
<!-- Action Sheet组件 -->
<u-action-sheet @select="selectActions" @close="actionShow = false" :actions="actionList" :safeAreaInsetBottom="true" cancelText="取消" :closeOnClickOverlay="true" :closeOnClickAction="true" :title="actionTitle" :show="actionShow"></u-action-sheet>
</template>
<script>
import { GET_ACCESS_TOKEN, BAIDU_OCR } from '@/utils/ocr/index.js'
export default {
data() {
return {
name: '',
actionShow: false,
actionTitle: '上传营业执照',
actionList: [{id: 0, name: '拍照'},{id: 1, name: '相册'}],
name: '',
actionTitle: '',
fileinp_yin_path: '',
fileinp_zheng_path: '',
fileinp_fan_path: ''
}
},
methods: {
bindActions(name) {
this.actionShow = true
if (name == 'license') {
this.name = 'license'
this.actionTitle = '上传营业执照'
} else if (name == 'card') {
this.name = 'card'
this.actionTitle = '上传身份证正面'
} else {
this.name = 'card2'
this.actionTitle = '上传身份证反面'
}
},
async selectActions(index) {
const BAIDU_ACCESS_TOKEN = await GET_ACCESS_TOKEN()
const chooseResult = await BAIDU_OCR(index.id, this.name, BAIDU_ACCESS_TOKEN)
const result = await this.uploadFilePromise(chooseResult)
if (this.name == 'license') {
this.fileinp_yin_path = result
} else if (this.name == 'card') {
this.fileinp_zheng_path = result
} else {
this.fileinp_fan_path = result
}
}
}
}
</script>
五、实现效果
|