项目初始化
This commit is contained in:
1
src/app/shared/components/captcha/captcha.component.html
Normal file
1
src/app/shared/components/captcha/captcha.component.html
Normal file
@ -0,0 +1 @@
|
||||
<div id="captcha"></div>
|
||||
6
src/app/shared/components/captcha/captcha.component.less
Normal file
6
src/app/shared/components/captcha/captcha.component.less
Normal file
@ -0,0 +1,6 @@
|
||||
:host {
|
||||
::ng-deep {
|
||||
.captcha-box {
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/app/shared/components/captcha/captcha.component.ts
Normal file
79
src/app/shared/components/captcha/captcha.component.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { keysConf } from '@conf/keys.conf';
|
||||
import { Subject } from 'rxjs';
|
||||
import { EACaptchaService } from '../../services';
|
||||
import { initNECaptchaWithFallback } from './dun';
|
||||
@Component({
|
||||
selector: 'app-captcha',
|
||||
templateUrl: './captcha.component.html',
|
||||
styleUrls: ['./captcha.component.less'],
|
||||
})
|
||||
export class CaptchaComponent implements OnInit {
|
||||
@Input() phone!: string; // 手机号
|
||||
@Input() url!: string; // api地址
|
||||
@Output() done = new EventEmitter<any>();
|
||||
captchaIns: any;
|
||||
|
||||
initSubject = new Subject<any>();
|
||||
|
||||
constructor(public captchaService: EACaptchaService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
init() {
|
||||
const _this = this;
|
||||
if (this.captchaIns) {
|
||||
return this.initSubject;
|
||||
}
|
||||
|
||||
initNECaptchaWithFallback(
|
||||
{
|
||||
element: '#captcha',
|
||||
captchaId: keysConf.yidun_capcha_id,
|
||||
mode: 'popup',
|
||||
width: '320px',
|
||||
onClose: () => {
|
||||
// 弹出关闭结束后将会触发该函数
|
||||
},
|
||||
onVerify: (err: any, data: any) => {
|
||||
// console.log('🚀 ~ init ~ data', data);
|
||||
if (data?.validate) {
|
||||
// 验证通过,获取验证码
|
||||
_this.captchaDone(data?.validate);
|
||||
}
|
||||
},
|
||||
},
|
||||
(instance: any) => {
|
||||
// console.log('🚀 ~ initCaptcha ~ instance', instance);
|
||||
// 初始化成功后得到验证实例instance,可以调用实例的方法
|
||||
_this.captchaIns = instance;
|
||||
this.initSubject.next(_this.captchaIns);
|
||||
},
|
||||
(err: any) => {
|
||||
// 初始化失败后触发该函数,err对象描述当前错误信息
|
||||
},
|
||||
);
|
||||
|
||||
return this.initSubject;
|
||||
}
|
||||
/* 网易盾验证通过 */
|
||||
captchaDone(validate: any) {
|
||||
this.captchaService.getCaptchaByDun(this.phone, validate, this.url || undefined).subscribe((res: any) => {
|
||||
// console.log('🚀 ~ 验证通过发送验证码=>', res);
|
||||
if (res) {
|
||||
this.captchaService.msgSrv.success('验证码发送成功!');
|
||||
this.done.emit(null);
|
||||
} else {
|
||||
this.captchaService.msgSrv.warning(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
popUp() {
|
||||
console.log(222222222222222222222222222222222222222);
|
||||
if (!this.captchaIns) {
|
||||
this.init();
|
||||
}
|
||||
this.captchaIns.refresh();
|
||||
this.captchaIns.popUp();
|
||||
}
|
||||
}
|
||||
84
src/app/shared/components/captcha/dun.helper.ts
Normal file
84
src/app/shared/components/captcha/dun.helper.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||
import { ComponentPortal } from '@angular/cdk/portal';
|
||||
import { ComponentRef, Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { CaptchaComponent } from './captcha.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DunHelper {
|
||||
captchacontainerRef!: ComponentRef<CaptchaComponent>;
|
||||
userInfo;
|
||||
|
||||
constructor(private overlay: Overlay) {
|
||||
this.userInfo = JSON.parse(localStorage.getItem('user') || '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件初始化
|
||||
* @param phone 手机号
|
||||
* @param url 发送验证码请求地址
|
||||
*/
|
||||
init(phone: string, url?: string): Subject<any> {
|
||||
const overlayRef = this.createOverlay();
|
||||
const containerPortal = new ComponentPortal(CaptchaComponent);
|
||||
this.captchacontainerRef = overlayRef.attach<CaptchaComponent>(containerPortal);
|
||||
this.captchacontainerRef.instance.phone = phone;
|
||||
this.captchacontainerRef.instance.url = url || '';
|
||||
return this.captchacontainerRef.instance.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出滑块验证
|
||||
* @param phone 手机号
|
||||
* @param url 发送验证码请求地址
|
||||
*/
|
||||
popUp(phone?: string, url?: string): Observable<any> {
|
||||
if (this.captchacontainerRef) {
|
||||
this.destory();
|
||||
}
|
||||
|
||||
this.init(phone || this.userInfo?.phone, url).subscribe((instance) => {
|
||||
if (instance) {
|
||||
this.captchacontainerRef.instance.popUp();
|
||||
}
|
||||
});
|
||||
|
||||
/* if (!this.captchacontainerRef) {
|
||||
this.init(phone || this.userInfo?.phone, url).subscribe(instance => {
|
||||
if (instance) {
|
||||
this.captchacontainerRef.instance.popUp();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!!phone && !!url) {
|
||||
this.init(phone || this.userInfo?.phone, url).subscribe(instance => {
|
||||
if (instance) {
|
||||
this.captchacontainerRef.instance.popUp();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.captchacontainerRef.instance.popUp();
|
||||
}
|
||||
} */
|
||||
return this.captchacontainerRef.instance.done;
|
||||
}
|
||||
|
||||
/** 组件销毁 */
|
||||
destory() {
|
||||
this.captchacontainerRef.destroy();
|
||||
}
|
||||
|
||||
private createOverlay(): OverlayRef {
|
||||
const overlayConfig = new OverlayConfig({
|
||||
hasBackdrop: false,
|
||||
scrollStrategy: this.overlay.scrollStrategies.block(),
|
||||
positionStrategy: this.overlay.position().global(),
|
||||
backdropClass: 'captcha-back-drop',
|
||||
panelClass: 'captcha-overlay',
|
||||
});
|
||||
|
||||
return this.overlay.create(overlayConfig);
|
||||
}
|
||||
}
|
||||
353
src/app/shared/components/captcha/dun.ts
Normal file
353
src/app/shared/components/captcha/dun.ts
Normal file
@ -0,0 +1,353 @@
|
||||
/* eslint-disable no-undef */
|
||||
let errorCallbackCount: any = 0;
|
||||
|
||||
// 常量
|
||||
const DEFAULT_VALIDATE =
|
||||
'QjGAuvoHrcpuxlbw7cp4WnIbbjzG4rtSlpc7EDovNHQS._ujzPZpeCInSxIT4WunuDDh8dRZYF2GbBGWyHlC6q5uEi9x-TXT9j7J705vSsBXyTar7aqFYyUltKYJ7f4Y2TXm_1Mn6HFkb4M7URQ_rWtpxQ5D6hCgNJYC0HpRE7.2sttqYKLoi7yP1KHzK-PptdHHkVwb77cwS2EJW7Mj_PsOtnPBubTmTZLpnRECJR99dWTVC11xYG0sx8dJNLUxUFxEyzTfX4nSmQz_T5sXATRKHtVAz7nmV0De5unmflfAlUwMGKlCT1khBtewlgN5nHvyxeD8Z1_fPVzi9oznl-sbegj6lKfCWezmLcwft8.4yaVh6SlzXJq-FnSK.euq9OBd5jYc82ge2_hEca1fGU--SkPRzgwkzew4O4qjdS2utdPwFONnhKAIMJRPUmCV4lPHG1OeRDvyNV8sCnuFMw7leasxIhPoycl4pm5bNy70Z1laozEGJgItVNr3'; // 默认validate
|
||||
const FALLBACK_LANG: any = {
|
||||
'zh-CN': '前方拥堵,已自动跳过验证',
|
||||
en: 'captcha error,Verified automatically',
|
||||
};
|
||||
const CACHE_MIN = 1000 * 60; // 缓存时长单位,1分钟
|
||||
const REQUEST_SCRIPT_ERROR = 502;
|
||||
|
||||
const RESOURCE_CACHE: any = {};
|
||||
|
||||
// 工具函数
|
||||
function loadScript(src: any, cb: any) {
|
||||
const head: any = document.head || document.getElementsByTagName('head')[0];
|
||||
const script: any = document.createElement('script');
|
||||
|
||||
cb = cb || function () {};
|
||||
|
||||
script.type = 'text/javascript';
|
||||
script.charset = 'utf8';
|
||||
script.async = true;
|
||||
script.src = src;
|
||||
|
||||
if (!('onload' in script)) {
|
||||
script.onreadystatechange = function () {
|
||||
if (this.readyState !== 'complete' && this.readyState !== 'loaded') {
|
||||
return;
|
||||
}
|
||||
this.onreadystatechange = null;
|
||||
cb(null, script); // there is no way to catch loading errors in IE8
|
||||
};
|
||||
}
|
||||
|
||||
script.onload = function () {
|
||||
this.onerror = this.onload = null;
|
||||
cb(null, script);
|
||||
};
|
||||
script.onerror = function () {
|
||||
// because even IE9 works not like others
|
||||
this.onerror = this.onload = null;
|
||||
cb(new Error('Failed to load ' + this.src), script);
|
||||
};
|
||||
|
||||
head.appendChild(script);
|
||||
}
|
||||
|
||||
function joinUrl(protocol: any, host: any, path: any) {
|
||||
protocol = protocol || '';
|
||||
host = host || '';
|
||||
path = path || '';
|
||||
if (protocol) {
|
||||
protocol = protocol.replace(/:?\/{0,2}$/, '://');
|
||||
}
|
||||
if (host) {
|
||||
const matched = host.match(/^([-0-9a-zA-Z.:]*)(\/.*)?/);
|
||||
host = matched[1];
|
||||
path = (matched[2] || '') + '/' + path;
|
||||
}
|
||||
!host && (protocol = '');
|
||||
|
||||
return protocol + host + path;
|
||||
}
|
||||
|
||||
function setDomText(el: any, value: any) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
const nodeType = el.nodeType;
|
||||
if (nodeType === 1 || nodeType === 11 || nodeType === 9) {
|
||||
if (typeof el.textContent === 'string') {
|
||||
el.textContent = value;
|
||||
} else {
|
||||
el.innerText = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function queryAllByClassName(selector: any, node: any) {
|
||||
node = node || document;
|
||||
if (node.querySelectorAll) {
|
||||
return node.querySelectorAll(selector);
|
||||
}
|
||||
if (!/^\.[^.]+$/.test(selector)) {
|
||||
return [];
|
||||
}
|
||||
if (node.getElementsByClassName) {
|
||||
return node.getElementsByClassName(selector);
|
||||
}
|
||||
|
||||
const children = node.getElementsByTagName('*');
|
||||
let current;
|
||||
const result = [];
|
||||
const className = selector.slice(1);
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
current = children[i];
|
||||
if (~(' ' + current.className + ' ').indexOf(' ' + className + ' ')) {
|
||||
result.push(current);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function assert(condition: any, msg: any) {
|
||||
if (!condition) {
|
||||
throw new Error('[NECaptcha] ' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
function isInteger(val: any) {
|
||||
if (Number.isInteger) {
|
||||
return Number.isInteger(val);
|
||||
}
|
||||
return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
|
||||
}
|
||||
|
||||
function isArray(val: any) {
|
||||
if (Array.isArray) {
|
||||
return Array.isArray(val);
|
||||
}
|
||||
return Object.prototype.toString.call(val) === '[object Array]';
|
||||
}
|
||||
|
||||
function ObjectAssign(a: any, b: any, c: any) {
|
||||
if (Object.assign) {
|
||||
// return Object.assign.apply(null, arguments);
|
||||
return Object.assign.apply(null, arguments as any);
|
||||
}
|
||||
|
||||
const target: any = {};
|
||||
for (let index = 1; index < arguments.length; index++) {
|
||||
const source = arguments[index];
|
||||
if (source != null) {
|
||||
for (const key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function getTimestamp(msec: any) {
|
||||
msec = !msec && msec !== 0 ? msec : 1;
|
||||
return parseInt((new Date().valueOf() / msec).toString(), 10);
|
||||
}
|
||||
|
||||
// 降级方案
|
||||
function normalizeFallbackConfig(customConfig: any) {
|
||||
const siteProtocol = window.location.protocol.replace(':', '');
|
||||
const defaultConf: any = {
|
||||
protocol: siteProtocol === 'http' ? 'http' : 'https',
|
||||
lang: 'zh-CN',
|
||||
errorFallbackCount: 3,
|
||||
};
|
||||
const config: any = ObjectAssign({}, defaultConf, customConfig);
|
||||
|
||||
const errorFallbackCount: any = config.errorFallbackCount;
|
||||
assert(
|
||||
errorFallbackCount === undefined || (isInteger(errorFallbackCount) && errorFallbackCount >= 1),
|
||||
"errorFallbackCount must be an integer, and it's value greater than or equal one",
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function loadResource(config: any, cb: any) {
|
||||
if ((window as any).initNECaptcha) {
|
||||
return cb(null);
|
||||
}
|
||||
function genUrl(server: any) {
|
||||
const path = 'load.min.js';
|
||||
let _urls = [];
|
||||
if (isArray(server)) {
|
||||
for (let i = 0, len = server.length; i < len; i++) {
|
||||
_urls.push(joinUrl(config.protocol, server[i], path));
|
||||
}
|
||||
} else {
|
||||
const url = joinUrl(config.protocol, server, path);
|
||||
_urls = [url, url];
|
||||
}
|
||||
|
||||
return _urls;
|
||||
}
|
||||
const urls = genUrl(config.staticServer || ['cstaticdun.126.net', 'cstaticdun1.126.net', 'cstatic.dun.163yun.com']);
|
||||
|
||||
function step(i: any) {
|
||||
const url = urls[i] + '?v=' + getTimestamp(CACHE_MIN);
|
||||
loadScript(url, function (err: any) {
|
||||
if (err || !(window as any).initNECaptcha) {
|
||||
// loadjs的全局变量
|
||||
i = i + 1;
|
||||
if (i === urls.length) {
|
||||
return cb(new Error('Failed to load script(' + url + ').' + (err ? err.message : 'unreliable script')));
|
||||
}
|
||||
return step(i);
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
step(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* entry: initNECaptchaWithFallback
|
||||
* options:
|
||||
* errorFallbackCount: 触发降级的错误次数,默认第三次错误降级
|
||||
* defaultFallback: 是否开启默认降级
|
||||
* onFallback: 自定义降级方案,参数为默认validate
|
||||
*/
|
||||
export function initNECaptchaWithFallback(options: any, onload: any, onerror: any) {
|
||||
let captchaIns: any = null;
|
||||
|
||||
const config = normalizeFallbackConfig(options);
|
||||
const defaultFallback = config.defaultFallback !== false;
|
||||
const langPkg = FALLBACK_LANG[config.lang === 'zh-CN' ? config.lang : 'en'];
|
||||
const storeKey = window.location.pathname + '_' + config.captchaId + '_NECAPTCHA_ERROR_COUNTS';
|
||||
try {
|
||||
errorCallbackCount = parseInt(localStorage.getItem(storeKey)?.toString() || '0', 10);
|
||||
} catch (error) {}
|
||||
|
||||
const fallbackFn = !defaultFallback
|
||||
? config.onFallback || function () {}
|
||||
: (validate: any) => {
|
||||
function setFallbackTip(instance: any) {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
setFallbackTip(instance._captchaIns);
|
||||
if (!instance.$el) {
|
||||
return;
|
||||
}
|
||||
const tipEles = queryAllByClassName('.yidun-fallback__tip', instance.$el);
|
||||
if (!tipEles.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保在队列的最后
|
||||
setTimeout(() => {
|
||||
for (let i = 0, l = tipEles.length; i < l; i++) {
|
||||
setDomText(tipEles[i], langPkg);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
setFallbackTip(captchaIns);
|
||||
|
||||
config.onVerify && config.onVerify(null, { validate: validate });
|
||||
};
|
||||
const noFallback = !defaultFallback && !config.onFallback;
|
||||
|
||||
const proxyOnError = (error: any) => {
|
||||
errorCallbackCount++;
|
||||
if (errorCallbackCount < config.errorFallbackCount) {
|
||||
try {
|
||||
localStorage.setItem(storeKey, errorCallbackCount);
|
||||
} catch (err) {}
|
||||
|
||||
onerror(error);
|
||||
} else {
|
||||
fallbackFn(DEFAULT_VALIDATE);
|
||||
proxyRefresh();
|
||||
noFallback && onerror(error);
|
||||
}
|
||||
};
|
||||
|
||||
const proxyRefresh = () => {
|
||||
errorCallbackCount = 0;
|
||||
try {
|
||||
localStorage.setItem(storeKey, '0');
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const triggerInitError = (error: any) => {
|
||||
if (initialTimer && initialTimer.isError()) {
|
||||
initialTimer.resetError();
|
||||
return;
|
||||
}
|
||||
initialTimer && initialTimer.resetTimer();
|
||||
noFallback ? onerror(error) : proxyOnError(error);
|
||||
};
|
||||
|
||||
config.onError = (error: any) => {
|
||||
if (initialTimer && initialTimer.isError()) {
|
||||
initialTimer.resetError();
|
||||
}
|
||||
proxyOnError(error);
|
||||
};
|
||||
config.onDidRefresh = () => {
|
||||
if (initialTimer && initialTimer.isError()) {
|
||||
initialTimer.resetError();
|
||||
}
|
||||
proxyRefresh();
|
||||
};
|
||||
|
||||
const initialTimer = options.initTimeoutError ? options.initTimeoutError(proxyOnError) : null; // initialTimer is only for mobile.html
|
||||
|
||||
const loadResolve = () => {
|
||||
(window as any).initNECaptcha(
|
||||
config,
|
||||
(instance: any) => {
|
||||
if (initialTimer && initialTimer.isError()) {
|
||||
return;
|
||||
}
|
||||
initialTimer && initialTimer.resetTimer();
|
||||
captchaIns = instance;
|
||||
onload && onload(instance);
|
||||
},
|
||||
triggerInitError,
|
||||
);
|
||||
};
|
||||
const cacheId = 'load-queue';
|
||||
if (!RESOURCE_CACHE[cacheId]) {
|
||||
RESOURCE_CACHE[cacheId] = {
|
||||
rejects: [],
|
||||
resolves: [],
|
||||
status: 'error',
|
||||
};
|
||||
}
|
||||
if (RESOURCE_CACHE[cacheId].status === 'error') {
|
||||
RESOURCE_CACHE[cacheId].status = 'pending';
|
||||
loadResource(config, (error: any) => {
|
||||
if (error) {
|
||||
const err: any = new Error();
|
||||
err.code = REQUEST_SCRIPT_ERROR;
|
||||
err.message = config.staticServer + '/load.min.js error';
|
||||
|
||||
const rejects = RESOURCE_CACHE[cacheId].rejects;
|
||||
for (let i = 0, iLen = rejects.length; i < iLen; i++) {
|
||||
rejects.pop()(err);
|
||||
}
|
||||
RESOURCE_CACHE[cacheId].status = 'error';
|
||||
} else {
|
||||
RESOURCE_CACHE[cacheId].status = 'done';
|
||||
const resolves = RESOURCE_CACHE[cacheId].resolves;
|
||||
for (let j = 0, jLen = resolves.length; j < jLen; j++) {
|
||||
resolves.pop()();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (RESOURCE_CACHE[cacheId].status === 'done') {
|
||||
loadResolve();
|
||||
}
|
||||
if (RESOURCE_CACHE[cacheId].status === 'pending') {
|
||||
RESOURCE_CACHE[cacheId].rejects.push(function loadReject(err: any) {
|
||||
triggerInitError(err);
|
||||
});
|
||||
RESOURCE_CACHE[cacheId].resolves.push(loadResolve);
|
||||
}
|
||||
}
|
||||
2
src/app/shared/components/captcha/index.ts
Normal file
2
src/app/shared/components/captcha/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './captcha.component';
|
||||
export * from './dun.helper';
|
||||
Reference in New Issue
Block a user