2 changed files with 284 additions and 2 deletions
@ -0,0 +1,270 @@ |
|||
import tui from './httpRequest' |
|||
import { verifyIeRealName, verifyIeStudentCard, getIeRealNameAuditSetting } from './ieApi.js' |
|||
|
|||
function showLongToast(title, duration = 3200) { |
|||
uni.showToast({ title, icon: 'none', duration }) |
|||
} |
|||
|
|||
function chooseImage(sourceType = ['camera'], camera = 'front') { |
|||
return new Promise((resolve, reject) => { |
|||
const success = res => { |
|||
const file = res.tempFiles && res.tempFiles[0] |
|||
resolve((file && (file.tempFilePath || file.path)) || (res.tempFilePaths && res.tempFilePaths[0]) || '') |
|||
} |
|||
if (uni.chooseMedia) { |
|||
uni.chooseMedia({ |
|||
count: 1, |
|||
mediaType: ['image'], |
|||
sourceType, |
|||
camera, |
|||
success, |
|||
fail: reject |
|||
}) |
|||
return |
|||
} |
|||
uni.chooseImage({ |
|||
count: 1, |
|||
sourceType, |
|||
success, |
|||
fail: reject |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
function extractUploadUrl(fileObj) { |
|||
if (!fileObj) return '' |
|||
if (typeof fileObj === 'string') return fileObj |
|||
return fileObj.url || fileObj.fileUrl || fileObj.path || fileObj.fullPath || '' |
|||
} |
|||
|
|||
const MAX_CERT_PICTURE_BASE64_BYTES = 50 * 1024 |
|||
const STUDENT_CARD_AUTH_DISABLED = true |
|||
|
|||
function confirmCameraCapture(title, content, confirmText = '开始拍摄') { |
|||
return new Promise(resolve => { |
|||
uni.showModal({ |
|||
title, |
|||
content, |
|||
confirmText, |
|||
cancelText: '取消', |
|||
success: res => resolve(!!res.confirm), |
|||
fail: () => resolve(false) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
async function uploadStudentCard(attempt = 0) { |
|||
const confirmed = await confirmCameraCapture( |
|||
attempt === 0 ? '拍摄学生证照片' : '重新拍摄学生证', |
|||
attempt === 0 |
|||
? '请拍摄本人大学学生证照片,尽量保证证件信息清晰、无遮挡。' |
|||
: '上次未识别为大学生学生证,请重新拍摄一张清晰的学生证照片。' |
|||
) |
|||
if (!confirmed) return '' |
|||
const path = await chooseImage(['camera', 'album']) |
|||
if (!path) return '' |
|||
const uploaded = await tui.uploadFile('/upload/file', path) |
|||
return extractUploadUrl(uploaded) |
|||
} |
|||
|
|||
function promptText(title, placeholder) { |
|||
return new Promise(resolve => { |
|||
uni.showModal({ |
|||
title, |
|||
editable: true, |
|||
placeholderText: placeholder, |
|||
confirmText: '确定', |
|||
cancelText: '取消', |
|||
success: res => resolve(res.confirm ? String(res.content || '').trim() : ''), |
|||
fail: () => resolve('') |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
function compressImage(filePath, option) { |
|||
return new Promise(resolve => { |
|||
if (!uni.compressImage) { |
|||
resolve(filePath) |
|||
return |
|||
} |
|||
const params = { |
|||
src: filePath, |
|||
quality: option.quality, |
|||
success: res => resolve(res.tempFilePath || filePath), |
|||
fail: () => resolve(filePath) |
|||
} |
|||
if (option.width) { |
|||
params.compressedWidth = option.width |
|||
params.compressWidth = option.width |
|||
} |
|||
if (option.height) { |
|||
params.compressedHeight = option.height |
|||
params.compressHeight = option.height |
|||
} |
|||
uni.compressImage(params) |
|||
}) |
|||
} |
|||
|
|||
function readFileBase64(filePath) { |
|||
return new Promise((resolve, reject) => { |
|||
const wxApi = typeof wx !== 'undefined' ? wx : null |
|||
if (!wxApi || !wxApi.getFileSystemManager) { |
|||
reject(new Error('当前环境不支持读取图片Base64')) |
|||
return |
|||
} |
|||
wxApi.getFileSystemManager().readFile({ |
|||
filePath, |
|||
encoding: 'base64', |
|||
success: res => resolve(String(res.data || '').replace(/\s+/g, '')), |
|||
fail: reject |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
async function chooseFacePictureBase64() { |
|||
const confirmed = await confirmCameraCapture( |
|||
'拍摄实名核验照片', |
|||
'请拍摄本人清晰正脸照片用于身份证三要素核验,照片只用于本次认证并会压缩到50KB以内。' |
|||
) |
|||
if (!confirmed) return '' |
|||
const path = await chooseImage(['camera'], 'front') |
|||
if (!path) return '' |
|||
const compressOptions = [ |
|||
{ quality: 70, width: 480, height: 480 }, |
|||
{ quality: 55, width: 360, height: 360 }, |
|||
{ quality: 40, width: 300, height: 300 }, |
|||
{ quality: 30, width: 240, height: 240 }, |
|||
{ quality: 22, width: 200, height: 200 }, |
|||
{ quality: 15, width: 160, height: 160 }, |
|||
{ quality: 10, width: 128, height: 128 } |
|||
] |
|||
let lastBase64 = '' |
|||
for (const option of compressOptions) { |
|||
const compressed = await compressImage(path, option) |
|||
const base64 = await readFileBase64(compressed) |
|||
lastBase64 = base64 |
|||
if (base64.length <= MAX_CERT_PICTURE_BASE64_BYTES) { |
|||
return base64 |
|||
} |
|||
} |
|||
if (lastBase64.length > MAX_CERT_PICTURE_BASE64_BYTES) { |
|||
showLongToast('人像照片Base64不能超过50KB,请靠近拍摄清晰正脸并减少背景后重试', 3800) |
|||
return '' |
|||
} |
|||
return lastBase64 |
|||
} |
|||
|
|||
function confirmAuth(actionText, cancelText) { |
|||
return new Promise(resolve => { |
|||
uni.showModal({ |
|||
title: '进阶实名认证', |
|||
content: `为满足未成年人保护与防沉迷要求,${actionText || '继续使用'}前需要完成身份证三要素核验。`, |
|||
confirmText: '去认证', |
|||
cancelText: cancelText || '暂不继续', |
|||
success: res => resolve(!!res.confirm), |
|||
fail: () => resolve(false) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
function confirmStudentCardOnly(actionText, cancelText) { |
|||
return new Promise(resolve => { |
|||
uni.showModal({ |
|||
title: '学生身份认证', |
|||
content: `${actionText || '继续使用'}前需要完成学生证认证。`, |
|||
confirmText: '去认证', |
|||
cancelText: cancelText || '暂不继续', |
|||
success: res => resolve(!!res.confirm), |
|||
fail: () => resolve(false) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
async function ensureStudentCardVerified(profile, options = {}) { |
|||
if (profile && profile.studentCardVerified) return true |
|||
for (let attempt = 0; attempt < 2; attempt += 1) { |
|||
const imageUrl = await uploadStudentCard(attempt) |
|||
if (!imageUrl) { |
|||
showLongToast('未上传学生证照片,请重新选择', 3000) |
|||
return false |
|||
} |
|||
const result = await verifyIeStudentCard({ imageUrl }) |
|||
if (!result) return false |
|||
if (result.studentCardVerified) { |
|||
if (!options.skipSuccessToast) { |
|||
showLongToast('学生证认证通过', 2200) |
|||
} |
|||
return true |
|||
} |
|||
if (result.studentCardNeedRetry && attempt === 0) { |
|||
continue |
|||
} |
|||
showLongToast(result.message || '学生证认证未完成,请重新上传', 3200) |
|||
return false |
|||
} |
|||
return false |
|||
} |
|||
|
|||
async function ensureAliyunRealNameVerified(profile) { |
|||
if (profile && profile.realNameVerified) return true |
|||
const certName = await promptText('填写真实姓名', '请输入身份证上的姓名') |
|||
if (!certName) { |
|||
showLongToast('未填写真实姓名,无法完成实名认证', 3200) |
|||
return false |
|||
} |
|||
const certNo = await promptText('填写身份证号', '请输入身份证号码') |
|||
if (!certNo) { |
|||
showLongToast('未填写身份证号,无法完成实名认证', 3200) |
|||
return false |
|||
} |
|||
const certPicture = await chooseFacePictureBase64() |
|||
if (!certPicture) return false |
|||
uni.showLoading({ title: '实名认证中...', mask: true }) |
|||
let result = null |
|||
try { |
|||
result = await verifyIeRealName({ certName, certNo, certPicture }) |
|||
} finally { |
|||
uni.hideLoading() |
|||
} |
|||
if (!result || !result.realNameVerified) { |
|||
showLongToast((result && result.message) || '实名认证未完成,请稍后重试', 3200) |
|||
return false |
|||
} |
|||
showLongToast('实名认证通过', 2200) |
|||
return true |
|||
} |
|||
|
|||
export async function ensureIeVerifiedBeforeAction(options = {}) { |
|||
const profile = options.profile || {} |
|||
if (STUDENT_CARD_AUTH_DISABLED && profile.realNameVerified) return true |
|||
if (!STUDENT_CARD_AUTH_DISABLED && profile.realNameVerified && profile.studentCardVerified) return true |
|||
const setting = await getIeRealNameAuditSetting().catch(() => null) |
|||
const realNameAuditEnabled = !setting || setting.realNameAuditEnabled !== false |
|||
if (!realNameAuditEnabled) return true |
|||
const confirmed = realNameAuditEnabled |
|||
? await confirmAuth(options.actionText, options.cancelText) |
|||
: await confirmStudentCardOnly(options.actionText, options.cancelText) |
|||
if (!confirmed) return false |
|||
try { |
|||
// 学生证认证流程临时注释保留,当前阶段只执行实名认证。
|
|||
// const studentOk = await ensureStudentCardVerified(profile, { skipSuccessToast: realNameAuditEnabled })
|
|||
// if (!studentOk) return false
|
|||
// if (!realNameAuditEnabled) {
|
|||
// if (typeof options.reload === 'function') {
|
|||
// await options.reload()
|
|||
// }
|
|||
// return true
|
|||
// }
|
|||
const realNameOk = await ensureAliyunRealNameVerified(profile) |
|||
if (!realNameOk) return false |
|||
if (typeof options.reload === 'function') { |
|||
await options.reload() |
|||
} |
|||
return true |
|||
} catch (e) { |
|||
showLongToast('认证未完成,请稍后重试', 3200) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
export { showLongToast } |
|||
Loading…
Reference in new issue