wangfukang 21 hours ago
parent
commit
85e3cd4627
  1. 16
      common/ieApi.js
  2. 270
      common/ieRealNameAuth.js

16
common/ieApi.js

@ -3,7 +3,7 @@ import tui from './httpRequest'
function unwrap(res) { function unwrap(res) {
if (!res) return null if (!res) return null
if (res.success === false) { if (res.success === false) {
uni.showToast({ title: res.message || '请求失败', icon: 'none' }) uni.showToast({ title: res.message || '请求失败', icon: 'none', duration: 3200 })
return null return null
} }
return res.result !== undefined ? res.result : res return res.result !== undefined ? res.result : res
@ -16,7 +16,7 @@ function unwrapIeHome(res) {
if (message.indexOf('i/e 身份已被封禁') !== -1 || message.indexOf('ie 身份已被封禁') !== -1) { if (message.indexOf('i/e 身份已被封禁') !== -1 || message.indexOf('ie 身份已被封禁') !== -1) {
return { banned: true, message } return { banned: true, message }
} }
uni.showToast({ title: message, icon: 'none' }) uni.showToast({ title: message, icon: 'none', duration: 3200 })
return null return null
} }
return res.result !== undefined ? res.result : res return res.result !== undefined ? res.result : res
@ -50,6 +50,18 @@ export function saveIeProfile(data) {
return tui.request('/app/ie/profile', 'POST', data, false, false, false).then(unwrap) return tui.request('/app/ie/profile', 'POST', data, false, false, false).then(unwrap)
} }
export function verifyIeRealName(data) {
return tui.request('/app/ie/real-name/verify', 'POST', data, false, false, false).then(unwrap)
}
export function verifyIeStudentCard(data) {
return tui.request('/app/ie/real-name/student-card/verify', 'POST', data, false, false, false).then(unwrap)
}
export function getIeRealNameAuditSetting() {
return tui.request('/app/ie/real-name/audit-setting', 'GET', {}, false, true, true).then(unwrap)
}
export function startIeMatch(data) { export function startIeMatch(data) {
return tui.request('/app/ie/match/start', 'POST', data, false, false, false).then(unwrap) return tui.request('/app/ie/match/start', 'POST', data, false, false, false).then(unwrap)
} }

270
common/ieRealNameAuth.js

@ -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…
Cancel
Save