提交 4f412d1d 编写于 作者: DCloud_JSON's avatar DCloud_JSON 提交者: study夏羽

- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。 -...

- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。 - uni-id-cf,新增防刷机制。更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称 - 注册账号新增需要输入图形验证码
上级 20bfd7ac
## 1.2.5(2022-05-29) ## 1.1.35(2022-05-16)
升级预置的`uni_modules`->`uni-captcha`版本为:0.6.0。[详情](https://ext.dcloud.net.cn/plugin?name=uni-captcha)
## 1.2.4(2022-05-20)
- 修改`uni-starter.config.js`->`debug`的默认值为`false`
## 1.2.3(2022-05-20)
- 默认关闭`manifest.json`中的扩展配置
- `uni-starter.config.js` 新增debug,用于配置是否开启调试模式
## 1.2.2(2022-05-19)
- 优化登陆体验,账号密码登陆错误超过2次,再显示图形验证码进行人机校验。
## 1.2.1(2022-05-18)
- 修复在某些情况下,微信小程序端验证码显示错误的问题
## 1.2.0(2022-05-16)
- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。 - 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。
- uni-id-cf,新增防刷机制。更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称 - uni-id-cf,新增防刷机制。更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称
- 注册账号新增需要输入图形验证码 - 注册账号新增需要输入图形验证码
......
...@@ -8,8 +8,8 @@ import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/j ...@@ -8,8 +8,8 @@ import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/j
// #endif // #endif
const db = uniCloud.database() const db = uniCloud.database()
export default async function() { export default async function() {
let loginConfig = uniStarterConfig.router.login; let loginConfig = uniStarterConfig.router.login;
const debug = uniStarterConfig.debug; const debug = uniStarterConfig.debug;
//清除有配置但设备环境不支持的登录项 //清除有配置但设备环境不支持的登录项
// #ifdef APP-PLUS // #ifdef APP-PLUS
await new Promise((callBack) => { await new Promise((callBack) => {
...@@ -120,29 +120,29 @@ export default async function() { ...@@ -120,29 +120,29 @@ export default async function() {
if (args.data && args.key == 'uni_id_token') { if (args.data && args.key == 'uni_id_token') {
let oldToken = uni.getStorageSync('uni_id_token') let oldToken = uni.getStorageSync('uni_id_token')
if(oldToken.length){ if(oldToken.length){
console.log('监听到token更新,就刷新push_clientid的有效期'); console.log('监听到token更新,就刷新push_clientid的有效期');
// #ifdef APP-PLUS // #ifdef APP-PLUS
let push_clientid; let push_clientid;
try { try {
push_clientid = plus.push.getClientInfo().clientid push_clientid = plus.push.getClientInfo().clientid
} catch (e) { } catch (e) {
uni.showModal({ uni.showModal({
content: '获取推送标识失败。如果你的应用不需要推送功能,请注释掉本代码块', content: '获取推送标识失败。如果你的应用不需要推送功能,请注释掉本代码块',
showCancel: false, showCancel: false,
confirmText: "好的" confirmText: "好的"
}); });
console.log(e) console.log(e)
} }
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
"action": "renewDeviceTokenExpiredxpired", "action": "renewDeviceTokenExpiredxpired",
"params": {push_clientid} "params": {push_clientid}
}, },
complete: (e) => { complete: (e) => {
console.log(e); console.log(e);
} }
}) })
// #endif // #endif
} }
} }
...@@ -183,11 +183,11 @@ export default async function() { ...@@ -183,11 +183,11 @@ export default async function() {
}, },
fail() { fail() {
callBack(false) callBack(false)
}, },
complete(){ complete(){
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
uni.hideToast() uni.hideToast()
// #endif // #endif
} }
}); });
}) })
...@@ -198,9 +198,9 @@ export default async function() { ...@@ -198,9 +198,9 @@ export default async function() {
complete(e) { complete(e) {
// console.log(JSON.stringify(e)); // console.log(JSON.stringify(e));
}, },
fail(e) { // 失败回调拦截 fail(e) { // 失败回调拦截
console.error('网络请求错误码:',JSON.stringify(e)); console.error('网络请求错误码:',JSON.stringify(e));
if (debug) { if (Debug) {
uni.showModal({ uni.showModal({
content: JSON.stringify(e), content: JSON.stringify(e),
showCancel: false showCancel: false
......
.uni-flex {
display: flex;
}
.uni-flex-row {
@extend .uni-flex;
flex-direction: row;
box-sizing: border-box;
}
.uni-flex-column {
@extend .uni-flex;
flex-direction: column;
}
.uni-color-gary {
color: #3b4144;
}
/* 标题 */
.uni-title {
display: flex;
margin-bottom: $uni-spacing-col-base;
font-size: $uni-font-size-lg;
font-weight: bold;
color: #3b4144;
}
.uni-title-sub {
display: flex;
font-size: $uni-font-size-base;
font-weight: 500;
color: #3b4144;
}
/* 描述 额外文本 */
.uni-note {
margin-top: 10px;
color: #999;
font-size: $uni-font-size-sm;
}
/* 列表内容 */
.uni-list-box {
@extend .uni-flex-row;
flex: 1;
margin-top: 10px;
}
/* 略缩图 */
.uni-thumb {
flex-shrink: 0;
margin-right: $uni-spacing-row-base;
width: 125px;
height: 75px;
border-radius: $uni-border-radius-lg;
overflow: hidden;
border: 1px #f5f5f5 solid;
image {
width: 100%;
height: 100%;
}
}
.uni-media-box {
@extend .uni-flex-row;
border-radius: $uni-border-radius-lg;
overflow: hidden;
.uni-thumb {
margin: 0;
margin-left: 4px;
flex-shrink: 1;
width: 33%;
border-radius:0;
&:first-child {
margin: 0;
}
}
}
/* 内容 */
.uni-content {
@extend .uni-flex-column;
justify-content: space-between;
}
/* 列表footer */
.uni-footer {
@extend .uni-flex-row;
justify-content: space-between;
margin-top: $uni-spacing-col-lg;
}
.uni-footer-text {
font-size: $uni-font-size-sm;
color: $uni-text-color-grey;
margin-left: 5px;
}
/* 标签 */
.uni-tag {
flex-shrink: 0;
padding: 0 5px;
border: 1px $uni-border-color solid;
margin-right: $uni-spacing-row-sm;
border-radius: $uni-border-radius-base;
background: $uni-bg-color-grey;
color: $uni-text-color;
font-size: $uni-font-size-sm;
}
/* 链接 */
.uni-link {
margin-left: 10px;
color: $uni-text-color;
text-decoration: underline;
}
{ {
"name": "uni-starter", "name": "uni-starter",
"appid": "", "appid": "请点击重新获取",
"description": "云端一体应用快速开发模版", "description": "云端一体应用快速开发模版",
"versionName": "1.0.0", "versionName": "1.0.0",
"versionCode": "100", "versionCode": "100",
"transformPx": false, "transformPx": false,
"app-plus": { "app-plus": {
"locales": { "locales": {
"en": { "en": {
"name": "uni-starter", "name": "uni-starter",
"android": { "android": {
"strings": { "strings": {
"CustomKey": "CustomValue" "CustomKey": "CustomValue"
} }
}, },
"ios": { "ios": {
"privacyDescription": { "privacyDescription": {
"NSPhotoLibraryUsageDescription": "access to the user’s photo library(read)" "NSPhotoLibraryUsageDescription": "access to the user’s photo library(read)"
}, },
"infoPlist": { "infoPlist": {
"CustomKey": "CustomValue" "CustomKey": "CustomValue"
} }
} }
}, },
"zh": { "zh": {
"name": "统一应用基本项目" "name": "统一应用基本项目"
} }
}, },
"privacy": { "privacy": {
"prompt": "template", "prompt": "template",
"template": { "template": {
"title": "服务协议和隐私政策", "title": "服务协议和隐私政策",
"message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"https://ask.dcloud.net.cn/protocol.html\">《服务协议》</a>和<a href=\"https://ask.dcloud.net.cn/protocol.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", "message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"https://ask.dcloud.net.cn/protocol.html\">《服务协议》</a>和<a href=\"https://ask.dcloud.net.cn/protocol.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意", "buttonAccept": "同意",
"buttonRefuse": "暂不同意" "buttonRefuse": "暂不同意"
} }
}, },
"compatible": { "compatible": {
"ignoreVersion": true "ignoreVersion": true
}, },
"usingComponents": true, "usingComponents": true,
"nvueStyleCompiler": "uni-app", "nvueStyleCompiler": "uni-app",
"compilerVersion": 3, "compilerVersion": 3,
"splashscreen": { "splashscreen": {
"alwaysShowBeforeRender": false, "alwaysShowBeforeRender": false,
"waiting": true, "waiting": true,
"autoclose": true, "autoclose": true,
"delay": 0 "delay": 0
}, },
"modules": { "modules": {
"Fingerprint": {}, "Fingerprint": {
"FaceID": {}, },
"Push": {}, "Share": {
"Bluetooth": {}, },
"OAuth": {} "OAuth": {
}, },
"distribute": { "FaceID": {
"android": { },
"permissions": [ "Geolocation": {
"<uses-feature android:name=\"android.hardware.camera\"/>", },
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", "Bluetooth": {
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", },
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", "Push": {
"<uses-permission android:name=\"android.permission.CAMERA\"/>", },
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", "Maps": {
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", }
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", },
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>", "distribute": {
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", "android": {
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>", "permissions": [
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", "<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.USE_FINGERPRINT\"/>", "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" "<uses-permission android:name=\"android.permission.CAMERA\"/>",
], "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"abiFilters": ["armeabi-v7a", "arm64-v8a", "x86"] "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
}, "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"ios": { "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"capabilities": { "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"entitlements": { "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"com.apple.developer.associated-domains": [ "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"" "<uses-permission android:name=\"android.permission.USE_FINGERPRINT\"/>",
] "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
} "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
} "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
}, ],
"sdkConfigs": { "abiFilters": [
"oauth": { "armeabi-v7a",
"weixin": { "arm64-v8a",
"appid": "", "x86"
"appsecret": "", ]
"UniversalLinks": "" },
} "ios": {
}, "capabilities": {
"ad": { "entitlements": {
"gdt": {}, "com.apple.developer.associated-domains": [
"csj": {} "applinks:static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com"
}, ]
"share": {}, }
"geolocation": {}, }
"push": { },
"unipush": {} "sdkConfigs": {
}, "oauth": {
"payment": {} "apple": {
}, },
"icons": { "weixin": {
"android": { "appid": "",
"hdpi": "", "appsecret": "",
"xhdpi": "", "UniversalLinks": ""
"xxhdpi": "", },
"xxxhdpi": "" "univerify": {
}, }
"ios": { },
"appstore": "", "ad": {
"ipad": { },
"app": "", "share": {
"app@2x": "", "weixin": {
"notification": "", "appid": "",
"notification@2x": "", "UniversalLinks": ""
"proapp@2x": "", }
"settings": "", },
"settings@2x": "", "geolocation": {
"spotlight": "", "baidu": {
"spotlight@2x": "" "__platform__": [
}, "ios",
"iphone": { "android"
"app@2x": "", ],
"app@3x": "", "appkey_ios": "请填写地图的key",
"notification@2x": "", "appkey_android": "请填写地图的key"
"notification@3x": "", }
"settings@2x": "", },
"settings@3x": "", "push": {
"spotlight@2x": "", "unipush": {
"spotlight@3x": "" "version": "2",
} "offline": true,
} "meizu": {
}, },
"splashscreen": { "mi": {
"iosStyle": "common", },
"androidStyle": "common", "vivo": {
"useOriginalMsgbox": true },
} "oppo": {
}, },
"nvueLaunchMode": "" "hms": {
}, }
"quickapp": {}, }
"mp-weixin": { },
"appid": "", "payment": {
"setting": { },
"urlCheck": false, "maps": {
"es6": false }
}, },
"usingComponents": true, "icons": {
"betterScopedSlots": true, "android": {
"permission": { "hdpi": "",
"scope.userLocation": { "xhdpi": "",
"desc": "演示在onShow生命周期获取地理位置" "xxhdpi": "",
} "xxxhdpi": ""
} },
}, "ios": {
"mp-alipay": { "appstore": "",
"usingComponents": true "ipad": {
}, "app": "",
"mp-baidu": { "app@2x": "",
"usingComponents": true "notification": "",
}, "notification@2x": "",
"mp-toutiao": { "proapp@2x": "",
"usingComponents": true "settings": "",
}, "settings@2x": "",
"uniStatistics": { "spotlight": "",
"enable": false "spotlight@2x": ""
}, },
"h5": { "iphone": {
"template": "", "app@2x": "",
"sdkConfigs": { "app@3x": "",
"maps": { "notification@2x": "",
"qqmap": { "notification@3x": "",
"key": "" "settings@2x": "",
} "settings@3x": "",
} "spotlight@2x": "",
}, "spotlight@3x": ""
"router": { }
"base": "" }
}, },
"uniStatistics": { "splashscreen": {
"enable": true "iosStyle": "common",
} "androidStyle": "common",
}, "useOriginalMsgbox": true
"_spaceID": "", }
"vueVersion": "2" },
"nvueLaunchMode": ""
},
"quickapp": {
},
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false,
"es6": false
},
"usingComponents": true,
"betterScopedSlots": true,
"permission": {
"scope.userLocation": {
"desc": "演示在onShow生命周期获取地理位置"
}
}
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"h5": {
"template": "",
"sdkConfigs": {
"maps": {
"qqmap": {
"key": ""
}
}
},
"router": {
"base": ""
},
"uniStatistics": {
"enable": true
}
},
"_spaceID": "",
"vueVersion": "2"
} }
{ {
"id": "uni-starter", "id": "uni-starter",
"displayName": "uni-starter", "displayName": "uni-starter",
"version": "1.2.5", "version": "1.1.35",
"description": "云端一体应用快速开发基本项目模版", "description": "云端一体应用快速开发基本项目模版",
"keywords": [ "keywords": [
"login", "login",
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
}, },
"uni_modules": { "uni_modules": {
"dependencies": [ "dependencies": [
"uni-id-cf", "uni-id-cf"
"uni-captcha"
], ],
"encrypt": [], "encrypt": [],
"platforms": { "platforms": {
......
...@@ -22,8 +22,7 @@ ...@@ -22,8 +22,7 @@
<!-- #endif --> <!-- #endif -->
<!-- 列表渲染 --> <!-- 列表渲染 -->
<uni-list-item :to="'/pages/list/detail?id='+item._id+'&title='+item.title" v-for="(item,index) in data" <uni-list-item :to="'/pages/list/detail?id='+item._id+'&title='+item.title" v-for="(item,index) in data" :key="index">
:key="index">
<!-- 通过header插槽定义列表左侧图片 --> <!-- 通过header插槽定义列表左侧图片 -->
<template v-slot:header> <template v-slot:header>
<image class="avatar" :src="item.avatar" mode="aspectFill"></image> <image class="avatar" :src="item.avatar" mode="aspectFill"></image>
...@@ -253,4 +252,4 @@ ...@@ -253,4 +252,4 @@
z-index: 999; z-index: 999;
/* #endif */ /* #endif */
} }
</style> </style>
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
@click="submit">{{$t('common.login')}}</button> @click="submit">{{$t('common.login')}}</button>
</uni-forms> </uni-forms>
<uni-quick-login agree></uni-quick-login> <uni-quick-login agree></uni-quick-login>
<uni-popup-captcha @confirm="submit" ref="popup-captcha" v-model="captcha" scene="loginBySms"> <uni-popup-captcha @confirm="submit" ref="popup-captcha" v-model="captcha" scene="loginBySms"></uni-popup-captcha>
</uni-popup-captcha>
</view> </view>
</template> </template>
<script> <script>
...@@ -25,9 +24,9 @@ ...@@ -25,9 +24,9 @@
mixins: [mixin], mixins: [mixin],
data() { data() {
return { return {
code: '', code:'',
phone: '', phone:'',
captcha: false captcha:false
} }
}, },
computed: { computed: {
...@@ -50,67 +49,37 @@ ...@@ -50,67 +49,37 @@
} }
}, },
methods: { methods: {
async submit() { //完成并提交 submit(){ //完成并提交
return await uniCloud.callFunction({ uniCloud.callFunction({
name: 'uni-id-cf', name:'uni-id-cf',
data: { data:{
action: 'loginBySms', action:'loginBySms',
params: { params:{
"mobile": this.phone, "mobile":this.phone,
"code": this.code, "code":this.code,
"captcha": this.captcha "captcha":this.captcha
},
}
}).then(({result}) => {
uni.showToast({
title: result.msg || result.errMsg,
icon: 'none'
});
if (result.errCode == "CAPTCHA_REQUIRED") {
return this.$refs['popup-captcha'].open()
}
if (result.code === 0) {
this.loginSuccess(result)
}
return result
}).finally((e)=>{
console.log("e: ",e);
this.captcha = false
})
/* uniCloud.callFunction({
name: 'uni-id-cf',
data: {
action: 'loginBySms',
params: {
"mobile": this.phone,
"code": this.code,
"captcha": this.captcha
}, },
}, },
success: ({ success: ({result}) => {
result
}) => {
uni.showToast({ uni.showToast({
title: result.msg || result.errMsg, title: result.msg || result.errMsg,
icon: 'none' icon: 'none'
}); });
if (result.errCode == "CAPTCHA_REQUIRED") { if(result.errCode == "CAPTCHA_REQUIRED"){
return this.$refs['popup-captcha'].open() return this.$refs['popup-captcha'].open()
} }
if (result.code === 0) { if(result.code === 0){
this.loginSuccess(result) this.loginSuccess(result)
} }
}, },
complete: () => { complete: () => {
this.captcha = false this.captcha = false
} }
}) */ })
} }
} }
} }
</script> </script>
<style> <style>
@import url("../common/login-page.css"); @import url("../common/login-page.css");
</style> </style>
...@@ -4,8 +4,12 @@ ...@@ -4,8 +4,12 @@
<text class="title">{{$t('pwdLogin.pwdLogin')}}</text> <text class="title">{{$t('pwdLogin.pwdLogin')}}</text>
<input class="input-box" :inputBorder="false" v-model="username" :placeholder="$t('pwdLogin.placeholder')"/> <input class="input-box" :inputBorder="false" v-model="username" :placeholder="$t('pwdLogin.placeholder')"/>
<input type="password" class="input-box" :inputBorder="false" v-model="password" :placeholder="$t('pwdLogin.passwordPlaceholder')"/> <input type="password" class="input-box" :inputBorder="false" v-model="password" :placeholder="$t('pwdLogin.passwordPlaceholder')"/>
<uni-captcha v-if="needCaptcha" scene="login" v-model="captcha"></uni-captcha> <!-- <view class="captcha-box" v-if="captchaBase64">
<uni-agreements class="agreement" @setAgree="agree = $event"></uni-agreements> <image class="captcha-img" @click="createCaptcha" :src="captchaBase64" mode="widthFix"></image>
<input type="text" class="input-box captcha" :inputBorder="false" v-model="captcha" :placeholder="$t('pwdLogin.verifyCodePlaceholder')"/>
</view> -->
<uni-captcha scene="login" v-model="captcha"></uni-captcha>
<uni-agreements @setAgree="agree = $event"></uni-agreements>
<button class="send-btn" :disabled="!canLogin" :type="canLogin?'primary':'default'" <button class="send-btn" :disabled="!canLogin" :type="canLogin?'primary':'default'"
@click="pwdLogin">{{$t('pwdLogin.login')}}</button> @click="pwdLogin">{{$t('pwdLogin.login')}}</button>
<!-- 忘记密码 --> <!-- 忘记密码 -->
...@@ -25,9 +29,8 @@ ...@@ -25,9 +29,8 @@
return { return {
"password": "", "password": "",
"username": "", "username": "",
"agree": false, "agree": false,
"captcha":'', "captcha":false
"needCaptcha":false
} }
}, },
computed: { computed: {
...@@ -98,40 +101,40 @@ ...@@ -98,40 +101,40 @@
console.log("res:-- ",res); console.log("res:-- ",res);
return res return res
}) })
// 下边是可以登录 // 下边是可以登录
/* uniCloud.callFunction({ /* uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
action:'login', action:'login',
params:{ params:{
"username": this.username, "username": this.username,
"password": this.password, "password": this.password,
"captcha":this.captcha "captcha":this.captcha
}, },
}, },
success: ({result}) => { success: ({result}) => {
console.log(result); console.log(result);
if (result.code === 0) { if (result.code === 0) {
this.loginSuccess(result) this.loginSuccess(result)
} else { } else {
if (result.needCaptcha) { if (result.needCaptcha) {
uni.showToast({ uni.showToast({
title: result.msg||'完成', title: result.msg||'完成',
icon: 'none' icon: 'none'
}); });
this.needCaptcha = true this.needCaptcha = true
// this.createCaptcha() // this.createCaptcha()
}else{ }else{
uni.showModal({ uni.showModal({
title: this.$t('common.error'), title: this.$t('common.error'),
content: result.msg, content: result.msg,
showCancel: false, showCancel: false,
confirmText: this.$t('common.gotIt') confirmText: this.$t('common.gotIt')
}); });
} }
} }
} }
}) */ })
}, },
/* 前往注册 */ /* 前往注册 */
toRegister() { toRegister() {
...@@ -163,8 +166,8 @@ ...@@ -163,8 +166,8 @@
.toRegister { .toRegister {
margin-top: 80px; margin-top: 80px;
width: 600rpx; width: 600rpx;
} }
.agreement{ .agreement{
margin-top: 10px; margin-top: 10px;
} }
</style> </style>
...@@ -6,8 +6,7 @@ ...@@ -6,8 +6,7 @@
v-model="formData.username" trim="both" /> v-model="formData.username" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="nickname"> <uni-forms-item name="nickname">
<uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.nicknamePlaceholder')" <uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.nicknamePlaceholder')" v-model="formData.nickname" trim="both" />
v-model="formData.nickname" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="password" v-model="formData.password" required> <uni-forms-item name="password" v-model="formData.password" required>
<uni-easyinput :inputBorder="false" class="easyinput" <uni-easyinput :inputBorder="false" class="easyinput"
...@@ -15,8 +14,7 @@ ...@@ -15,8 +14,7 @@
trim="both" /> trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="pwd2" v-model="formData.pwd2" required> <uni-forms-item name="pwd2" v-model="formData.pwd2" required>
<uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.passwordAgain')" <uni-easyinput :inputBorder="false" class="easyinput" :placeholder="$t('register.passwordAgain')" type="password" v-model="formData.pwd2" trim="both" />
type="password" v-model="formData.pwd2" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="captcha" required> <uni-forms-item name="captcha" required>
<uni-captcha scene="register" v-model="formData.captcha"></uni-captcha> <uni-captcha scene="register" v-model="formData.captcha"></uni-captcha>
...@@ -36,11 +34,11 @@ ...@@ -36,11 +34,11 @@
data() { data() {
return { return {
formData: { formData: {
"username": "", "username": "111111",
"nickname": "", "nickname": "111111",
"password": "", "password":"111111",
"pwd2": "", "pwd2":"111111",
"captcha": "" "captcha":false
}, },
rules, rules,
agree: false, agree: false,
...@@ -144,12 +142,14 @@ ...@@ -144,12 +142,14 @@
.uni-container { .uni-container {
padding: 15px; padding: 15px;
} }
.send-btn{
.send-btn {
margin-top: 15px; margin-top: 15px;
} }
.uni-container ::v-deep .uni-forms-item__label{
width: 15px !important;
}
.uni-container ::v-deep .uni-forms-item__label { .uni-container ::v-deep .uni-forms-item__label {
width: 15px !important; width: 15px !important;
} }
</style> </style>
...@@ -11,8 +11,7 @@ ...@@ -11,8 +11,7 @@
</uni-easyinput> </uni-easyinput>
<button class="uni-btn send-btn-box" :disabled="!canSubmit" :type="canSubmit?'primary':'default'" <button class="uni-btn send-btn-box" :disabled="!canSubmit" :type="canSubmit?'primary':'default'"
@click="submit">提交</button> @click="submit">提交</button>
<uni-popup-captcha ref="popup-captcha" @confirm="submit" v-model="formData.captcha" scene="bindMobileBySms"> <uni-popup-captcha ref="popup-captcha" @confirm="submit" v-model="formData.captcha" scene="bindMobileBySms"></uni-popup-captcha>
</uni-popup-captcha>
</view> </view>
</template> </template>
<script> <script>
...@@ -50,79 +49,49 @@ ...@@ -50,79 +49,49 @@
*/ */
async submit() { async submit() {
console.log(this.formData); console.log(this.formData);
return await uniCloud.callFunction({ uniCloud.callFunction({
name: 'uni-id-cf', name: 'uni-id-cf',
data: { data: {
action: 'bindMobileBySms', action: 'bindMobileBySms',
params: this.formData params: this.formData
}, },
}).then(({ success: ({
result result
}) => { }) => {
console.log(result);
uni.showToast({
title: result.msg || result.errMsg,
icon: 'none'
});
if (result.errCode == "CAPTCHA_REQUIRED") {
return this.$refs['popup-captcha'].open()
}
if (result.code === 0) {
this.setUserInfo({
"mobile": result.mobile
})
uni.navigateBack()
}
return result
}).catch((reason) => {
console.log(reason, 'reason----');
return reason
})
/* uniCloud.callFunction({
name: 'uni-id-cf',
data: {
action: 'bindMobileBySms',
params: this.formData
},
success: ({result}) => {
console.log(result); console.log(result);
uni.showToast({ uni.showToast({
title: result.msg || result.errMsg, title: result.msg || result.errMsg,
icon: 'none' icon: 'none'
}); });
if (result.errCode == "CAPTCHA_REQUIRED") { if(result.errCode == "CAPTCHA_REQUIRED"){
return this.$refs['popup-captcha'].open() return this.$refs['popup-captcha'].open()
} }
if (result.code === 0) { if (result.code === 0) {
this.setUserInfo({ this.setUserInfo({"mobile":result.mobile})
"mobile": result.mobile
})
uni.navigateBack() uni.navigateBack()
} }
}, },
complete: () => { complete: () => {
this.formData.captcha = false this.formData.captcha = false
} }
}) */ })
/* /*
const uniIdCo = uniCloud.importObject("uni-id-co") const uniIdCo = uniCloud.importObject("uni-id-co")
uniIdCo.bindMobileBySms(this.formData).then(e => { uniIdCo.bindMobileBySms(this.formData).then(e => {
console.log(e); console.log(e);
uni.showToast({ uni.showToast({
title: e.errMsg, title: e.errMsg,
icon: 'none' icon: 'none'
}); });
uni.navigateBack() uni.navigateBack()
}).catch(e => { }).catch(e => {
if( e.errCode == 'CAPTCHA_REQUIRED'){ if( e.errCode == 'CAPTCHA_REQUIRED'){
this.$refs.popup.open() this.$refs.popup.open()
} }
}).finally(e=>{ }).finally(e=>{
this.formData.captcha = false this.formData.captcha = false
}) })
*/ */
}, },
isPhone() { isPhone() {
let reg_phone = /^1\d{10}$/; let reg_phone = /^1\d{10}$/;
...@@ -164,4 +133,4 @@ ...@@ -164,4 +133,4 @@
.send-btn-box { .send-btn-box {
margin-top: 15px; margin-top: 15px;
} }
</style> </style>
## 0.6.0(2022-05-27)
- 新增:支持在`uni-config-center`中根据场景值配置
- 修复:弹窗式验证码,输入内容后点击取消,重新打开验证码的值仍然存在的问题
## 0.5.2(2022-05-19)
- 修复在Vue3的兼容问题
## 0.5.1(2022-05-18)
- 修复在某些情况下微信小程序端验证码显示错误的问题
## 0.5.0(2022-05-17)
- 新增支持在`uni-captcha-co`->`config`配置验证码
## 0.4.1(2022-05-16) ## 0.4.1(2022-05-16)
- 新增示例项目 - 新增示例项目
## 0.4.0(2022-05-16) ## 0.4.0(2022-05-16)
......
...@@ -6,15 +6,26 @@ ...@@ -6,15 +6,26 @@
mode="widthFix"></image> mode="widthFix"></image>
</view> </view>
<input @blur="focusCaptchaInput = false" :focus="focusCaptchaInput" type="text" class="captcha" <input @blur="focusCaptchaInput = false" :focus="focusCaptchaInput" type="text" class="captcha"
:inputBorder="false" maxlength="4" v-model="val" placeholder="请输入验证码"> :inputBorder="false" maxlength="4" v-model="modelValue" placeholder="请输入验证码">
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() {
return {
focusCaptchaInput: false,
modelValue: "",
captchaBase64: "",
loging: false
};
},
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: { props: {
modelValue: String, event: 'update:modelValue',
value: String,
scene: { scene: {
type: String, type: String,
default () { default () {
...@@ -28,37 +39,17 @@ ...@@ -28,37 +39,17 @@
} }
} }
}, },
computed: {
val: {
get() {
return this.value || this.modelValue
},
set(value) {
console.log("value: ", value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
}
},
data() {
return {
focusCaptchaInput: false,
captchaBase64: "",
loging: false
};
},
watch: { watch: {
modelValue(value) {
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value)
},
scene: { scene: {
handler(scene) { handler(scene) {
if (scene) { if (scene) {
this.getImageCaptcha(this.focus) this.getImageCaptcha(false)
} else { } else {
uni.showToast({ uni.showToast({
title: 'scene不能为空', title: 'scene不能为空',
...@@ -66,14 +57,14 @@ ...@@ -66,14 +57,14 @@
}); });
} }
}, },
immediate: true immediate:true
} }
}, },
methods: { methods: {
getImageCaptcha(focus = true) { getImageCaptcha(focus = true) {
this.loging = true this.loging = true
this.modelValue = ''
if (focus) { if (focus) {
this.val = ''
this.focusCaptchaInput = true this.focusCaptchaInput = true
} }
const uniIdCo = uniCloud.importObject("uni-captcha-co", { const uniIdCo = uniCloud.importObject("uni-captcha-co", {
...@@ -82,7 +73,7 @@ ...@@ -82,7 +73,7 @@
uniIdCo.getImageCaptcha({ uniIdCo.getImageCaptcha({
scene: this.scene scene: this.scene
}).then(result => { }).then(result => {
// console.log(result); console.log(result);
this.captchaBase64 = result.captchaBase64 this.captchaBase64 = result.captchaBase64
}) })
.catch(e => { .catch(e => {
...@@ -116,6 +107,7 @@ ...@@ -116,6 +107,7 @@
} }
.captcha-img-box { .captcha-img-box {
cursor: pointer;
position: relative; position: relative;
background-color: #FEFAE7; background-color: #FEFAE7;
} }
...@@ -134,14 +126,9 @@ ...@@ -134,14 +126,9 @@
.captcha-img-box, .captcha-img-box,
.captcha-img, .captcha-img,
.loding { .loding {
height: 44px !important;
width: 100px; width: 100px;
} }
.captcha-img {
cursor: pointer;
}
.loding { .loding {
z-index: 9; z-index: 9;
color: #bbb; color: #bbb;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<view class="popup-captcha"> <view class="popup-captcha">
<view class="content"> <view class="content">
<text class="title">{{title}}</text> <text class="title">{{title}}</text>
<uni-captcha :focus="focus" :scene="scene" v-model="val"></uni-captcha> <uni-captcha :scene="scene" v-model="modelValue"></uni-captcha>
</view> </view>
<view class="button-box"> <view class="button-box">
<view @click="close" class="btn">取消</view> <view @click="close" class="btn">取消</view>
...@@ -17,12 +17,15 @@ ...@@ -17,12 +17,15 @@
export default { export default {
data() { data() {
return { return {
focus: false modelValue: "",
} };
},
model: {
prop: 'modelValue',
event: 'update:modelValue'
}, },
props: { props: {
modelValue:String, event: 'update:modelValue',
value:String,
scene: { scene: {
type: String, type: String,
default () { default () {
...@@ -36,37 +39,23 @@ ...@@ -36,37 +39,23 @@
} }
}, },
}, },
computed:{ watch: {
val:{ modelValue(value) {
get(){ // TODO 兼容 vue2
return this.value||this.modelValue this.$emit('input', value);
}, // TODO 兼容 vue3
set(value){ this.$emit('update:modelValue', value)
// console.log(value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
} }
}, },
methods: { methods: {
open() { open() {
this.focus = true
this.val = ""
this.$refs.popup.open() this.$refs.popup.open()
}, },
close() { close() {
this.focus = false
this.$refs.popup.close() this.$refs.popup.close()
}, },
confirm() { confirm() {
if(!this.val||this.val.length < 4){ if(this.modelValue.length < 4){
return uni.showToast({ return uni.showToast({
title: '请填写验证码', title: '请填写验证码',
icon: 'none' icon: 'none'
......
{ {
"id": "uni-captcha", "id": "uni-captcha",
"displayName": "uni-captcha", "displayName": "uni-captcha",
"version": "0.6.0", "version": "0.4.1",
"description": "云端一体图形验证码组件", "description": "简洁、高效、灵活可配置的云端验证码模块",
"keywords": [ "keywords": [
"uniCloud", "uniCloud",
"captcha", "captcha",
......
<h2> ## 用途说明
文档已移至 <a href="https://uniapp.dcloud.io/uniCloud/uni-captcha.html" target="_blank">uni-captcha文档</a> 主要起到人机校验或其他限制调用的作用,如:
</h2> - 防止机器冒充人类做暴力破解
\ No newline at end of file - 防止大规模在线注册滥用服务
- 防止滥用在线批量操
- 防止信息被大量采集聚合
常见的业务场景有:
- 注册环节:防止无效垃圾注册,从源头进行管理
- 登录环节:防止撞库攻击、暴力破解,保障用户数据案例
- 短信防刷:减少短信接口被刷情况,减少企业不必要成本
- 互动环节:防止批量垃圾互动信息破坏用户UGC内容生态
## 组成部分
- openDB数据表:[opendb-verify-codes](https://gitee.com/dcloud/opendb/blob/master/collection/opendb-verify-codes/collection.json),用于存储验证码数据。
- 集成:获取验证码、校验验证码、刷新验证码的uniCloud公共模块`uni-captcha`
- 云对象`uni-captcha-co`集成获取验证码的api,`getImageCaptcha`
- 集成创建、刷新、显示验证码的云端一体验证码组件
## 目录结构@catalogue
<pre>
├─uni_modules 存放[uni_module](/uni_modules)规范的插件。
│ └─uni-captcha
│ ├─uniCloud
│ │ ├─cloudfunctions 云函数目录
│ │ │ ├─common 公共模块目录
│ │ │ │ └─uni-captcha uni-captcha公共模块
│ │ │ └─uni-captcha-co 集成调用uni-captcha方法的云对象
│ │ └─database
│ │ ├─opendb-verify-codes.schema.json 验证码数据表
│ │ └─db_init.json 初始化数据库文件
│ └─components 组件目录
│ ├─uni-captcha
│ │ └─uni-captcha.vue 普通验证码组件
│ └─uni-popup-captcha
│ └─uni-popup-captcha.vue 弹出式验证码组件
</pre>
## 原理时序
客户端携带场景值(用于防止不同功能的验证码混用,如:`login``pay`)调用云对象`uni-captcha-co``getImageCaptcha`方法,向服务端发起获取验证码请求。
方法内部通过查询数据表`opendb-verify-codes`,判断:同一设备id、相同场景值、待验证的记录是否已存在;
- 不存在则:调用公共模块`uni-captcha``create`方法创建验证码,此时数据表会创建一条验证状态(字段名:`state`)为待验证(字段值`0`)的记录。
- 已存在则:调用公共模块`uni-captcha``refresh`方法刷新这个验证码。此时更新现存记录的验证状态(字段名:`state`)的值为已作废(字段值`2`),然后数据表也会创建一条验证状态(字段名:`state`)为待验证(字段值`0`)的记录。
并向客户端返回:格式为base64的图形验证码资源数据(响应体参数名:captchaBase64)。
客户端得到数据后,显示图形验证码,用户识别后(携带参数:场景值scene、验证码captcha)提交表单,服务端调用公共模块`uni-captcha``verify`方法验证是否正确。
补充:如果是clientDB操作,不想在扩展校验函数中依赖公共模块,还可以直接查库校验验证码。
以上即完整的流程,你如果直接使用云端一体组件,仅需配置组件的场景值属性`scene`,即可实现上述功能。
## 组件介绍
### 普通验证码组件
**组件名:uni-captcha**
云端一体组件,内置调用`uni-captcha-co`云对象创建/刷新验证码,支持双向数据绑定。
仅需传入属性`scene`:场景值即可。
组件遵从[easycom组件规范](https://uniapp.dcloud.io/component/#easycom%E7%BB%84%E4%BB%B6%E8%A7%84%E8%8C%83),使用示例:
```js
<template>
<uni-captcha scene="场景值" v-model="验证码的值"></uni-captcha>
</template>
```
#### Props:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
| ------------ | ------- | ---- | ------- | ------------------------------------------------------------ |
| scene | String | 是 | - | 使用场景值,用于防止不同功能的验证码混用,如:`login``pay` |
| value/v-model| String | - | - | 验证码的值 |
### 弹出式验证码组件
**组件名:uni-popup-captcha**
验证码实现人机校验等作用的同时,降低了用户体验。为了提升用户体验:绝大部分情况下,验证码应当是非常态的,当接口被高频调用的情况下才需要。
另外验证码会使得我们的前端界面设计变得复杂。所以弹出式验证码组件,应需而生。
#### 使用示例:
```js
<template>
<uni-popup-captcha ref="popup-captcha" @confirm="verifyCaptcha" :scene="formData.scene" v-model="formData.captcha"></uni-popup-captcha>
<button @click="openPopupCaptcha" >显示弹出式验证码</button>
</template>
<script>
export default {
data() {
return {
formData:{
captcha:"",
scene:"test"
}
}
},
methods: {
verifyCaptcha(){
const uniCaptchaCo = uniCloud.importObject("uni-captcha-co")
uniCaptchaCo.verifyCaptcha(this.formData).then(e=>{
uni.showToast({
title: e.errMsg,
icon: 'none'
});
})
},
openPopupCaptcha(){
this.$refs['popup-captcha'].open()
}
}
}
</script>
```
#### Props:
| 字段 | 类型 | 必填 | 说明 |
| ------------ | ------- | ---- | ------------------------------------------------------------ |
| scene | String | 是 | 使用场景值,用于防止不同功能的验证码混用,如:`login``pay` |
| value/v-model| String | - | 验证码的值 |
#### Events
| 字段 | 类型 | 说明 |
| ------------ | ------- |---------------- |
| confirm | Function | 点击确定按钮事件 |
## 公共模块
### 获取验证码@create
用法:`uniCaptcha.create(Object params);`
**参数说明**
| 字段 | 类型 | 必填 | 默认值 | 说明 |
| ------------ | ------- | ---- | ------- | ------------------------------------------------------------ |
| scene | String | 是 | - | 使用场景值,用于防止不同功能的验证码混用,如:`login``pay` |
| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 |
| width | Number | - | 150 | 图片宽度 |
| height | Number | - | 40 | 图片高度 |
| background | String | - | #FFFAE8 | 验证码背景色,设置空字符`''`不使用背景颜色 |
| size | Number | - | 4 | 验证码长度,最多 6 个字符 |
| noise | Number | - | 4 | 验证码干扰线条数 |
| color | Boolean | - | false | 字体是否使用随机颜色,当设置`background`后恒为`true` |
| fontSize | Number | - | 40 | 字体大小 |
| ignoreChars | String | - | '' | 忽略那些字符 |
| mathExpr | Boolean | - | false | 是否使用数学表达式 |
| mathMin | Number | - | 1 | 表达式所使用的最小数字 |
| mathMax | Number | - | 9 | 表达式所使用的最大数字 |
| mathOperator | String | - | '' | 表达式所使用的运算符,支持 `+``-`。不传随机使用 |
| expiresDate | Number | - | 180 | 验证码过期时间(s) |
**响应参数**
| 字段 | 类型 | 说明 |
| ------------- | ------ | ------------------- |
| errCode | Number | 错误码,0 表示成功 |
| errMsg | String | 详细信息 |
| captchaBase64 | String | 验证码:base64 格式 |
`注意:`
- 重新生成后,上条验证码不作废
- 刷新验证码,上条验证码作废
- 如果想替换字体,请保证字体格式为 `.ttf` 且包含 `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-` 字符
### 校验验证码@verify
用法:`uniCaptcha.verify(Object params);`
**参数说明**
| 字段 | 类型 | 必填 | 默认值 | 说明 |
| -------- | ------ | ---- | ------ | ----------------------------------------------- |
| scene | String | 是 | - | 类型,用于防止不同功能的验证码混用 |
| captcha | String | 是 | - | 验证码 |
| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 |
**响应参数**
| 字段 | 类型 | 说明 |
| ------- | ------ | ------------------ |
| errCode | Number | 错误码,0 表示成功 |
| errMsg | String | 详细信息 |
`注意:`
- 若提示验证码失效,请重新获取
### 刷新验证码@refresh
用法:`uniCaptcha.refresh(Object params);`
**参数说明**
| 字段 | 类型 | 必填 | 默认值 | 说明 |
| -------- | ------ | ---- | ------ | ----------------------------------------------- |
| scene | String | 是 | - | 类型,用于防止不同功能的验证码混用 |
| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 |
**响应参数**
| 字段 | 类型 | 说明 |
| ------------- | ------ | ------------------- |
| errCode | Number | 错误码,0 表示成功 |
| errMsg | String | 详细信息 |
| captchaBase64 | String | 验证码:base64 格式 |
`注意:`
- 支持传入 create 方法的所有参数,如果不传,则自动按照 deviceId 匹配上次生成时的配置生成新的验证码
## 错误码
_详细信息请查看 message 中查看_
| 模块 | 模块码 | 错误代码 | 错误信息 |
| :----: | :----: | :------: | :---------------------: |
| 验证码 | 100 | 01 | (10001)验证码生成失败 |
| | | 02 | (10002)验证码校验失败 |
| | | 03 | (10003)验证码刷新失败 |
// 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj // 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
//导入验证码公共模块
const uniCaptcha = require('uni-captcha') const uniCaptcha = require('uni-captcha')
//获取数据库对象
const db = uniCloud.database(); const db = uniCloud.database();
//获取数据表opendb-verify-codes对象
const verifyCodes = db.collection('opendb-verify-codes') const verifyCodes = db.collection('opendb-verify-codes')
module.exports = { module.exports = {
async getImageCaptcha({ async getImageCaptcha({scene}) {
scene let {deviceId} = this.getClientInfo();
}) { let res = await verifyCodes.where({scene,deviceId,state:0}).limit(1).get()
//获取设备id console.log("res: " + JSON.stringify(res));
let { let action = res.data.length?'refresh':'create'
deviceId, console.log(action);
platform
} = this.getClientInfo();
//根据:设备id、场景值、状态,查找记录是否存在
let res = await verifyCodes.where({
scene,
deviceId,
state: 0
}).limit(1).get()
//如果已存在则调用刷新接口,反之调用插件接口
let action = res.data.length ? 'refresh' : 'create'
//执行并返回结果
//导入配置,配置优先级说明:此处配置 > uni-config-center
return await uniCaptcha[action]({ return await uniCaptcha[action]({
scene, //来源客户端传递,表示:使用场景值,用于防止不同功能的验证码混用 scene,
uniPlatform: platform width:100,
height:44
}) })
} }
} }
// 在本文件中可配置云数据库初始化,数据格式见:https://uniapp.dcloud.io/uniCloud/hellodb?id=db-init
// 编写完毕后对本文件点右键,可按配置规则创建表和添加数据
{
"opendb-verify-codes":{}
}
{
"goEasy": {
"commonKey": "",
"clientKey": "",
"subscribeKey": "",
"restKey": "",
"secretKey": "",
"restHost":""
}
}
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
"passwordErrorRetryTime": 3600, "passwordErrorRetryTime": 3600,
"autoSetInviteCode": false, "autoSetInviteCode": false,
"forceInviteCode": false, "forceInviteCode": false,
"preferedAppPlatform": "app-plus", "preferedAppPlatform": "app",
"app-plus": { "app": {
"tokenExpiresIn": 2592000, "tokenExpiresIn": 2592000,
"oauth": { "oauth": {
"weixin": { "weixin": {
......
{
"getui": {
"appId": "dCgnx0otCL8rjloSyynDu7",
"appkey": "YGUYb5yPMN67rNyElDZv7A",
"appSecret": "b0zAa92FrG6HT9Hsqk2CR8",
"mastersecret": "nNXpI9MHkS8hZjdmQUD9H3",
"packageName": "io.dcloud.hellouniapp"
}
}
## 1.0.15(2022-05-11)
- loginBySms、bindMobileBySms新增防刷机制(当短信验证码输入错误2次以上,弹出图形验证码进行人机校验)
- 更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称
- register新增校验图形验证码逻辑
## 1.0.14(2022-01-26) ## 1.0.14(2022-01-26)
- 修复 uni-admin 的 'registerUser' 接口,注册用户含有多余字段 uid - 修复 uni-admin 的 'registerUser' 接口,注册用户含有多余字段 uid
## 1.0.13(2022-01-26) ## 1.0.13(2022-01-26)
......
...@@ -31,7 +31,7 @@ exports.main = async (event, context) => { ...@@ -31,7 +31,7 @@ exports.main = async (event, context) => {
} = event; } = event;
const deviceInfo = event.deviceInfo || {}; const deviceInfo = event.deviceInfo || {};
let params = event.params || {}, let params = event.params || {},
tokenExpired; tokenExpired,needCaptcha;
/* /*
2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid 2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid
校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken 校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken
...@@ -90,7 +90,7 @@ exports.main = async (event, context) => { ...@@ -90,7 +90,7 @@ exports.main = async (event, context) => {
await addDeviceInfo(res) await addDeviceInfo(res)
} }
//4.记录成功登录的日志方法 //4.记录成功登录的日志方法
const loginLog = async (res = {}) => { const uniIdLog = async (res = {}) => {
const now = Date.now() const now = Date.now()
const uniIdLogCollection = db.collection('uni-id-log') const uniIdLogCollection = db.collection('uni-id-log')
let logData = { let logData = {
...@@ -98,7 +98,8 @@ exports.main = async (event, context) => { ...@@ -98,7 +98,8 @@ exports.main = async (event, context) => {
ip: context.CLIENTIP, ip: context.CLIENTIP,
type: res.type, type: res.type,
ua: context.CLIENTUA, ua: context.CLIENTUA,
create_date: now create_date: now,
action
}; };
if (res.code === 0) { if (res.code === 0) {
...@@ -109,7 +110,8 @@ exports.main = async (event, context) => { ...@@ -109,7 +110,8 @@ exports.main = async (event, context) => {
} }
if (res.type == 'register') { if (res.type == 'register') {
await registerSuccess(res) await registerSuccess(res)
} else { }
if (res.type == 'login') {
if (Object.keys(deviceInfo).length) { if (Object.keys(deviceInfo).length) {
console.log(context.DEVICEID); console.log(context.DEVICEID);
//避免重复新增设备信息,先判断是否已存在 //避免重复新增设备信息,先判断是否已存在
...@@ -158,17 +160,17 @@ exports.main = async (event, context) => { ...@@ -158,17 +160,17 @@ exports.main = async (event, context) => {
}) })
} }
//5.防止恶意破解登录,连续登录失败一定次数后,需要用户提供验证码 //5.防止恶意破解操作,连续操作失败一定次数后,需要用户提供验证码
const isNeedCaptcha = async () => { const isNeedCaptcha = async () => {
//当用户最近“2小时内(recordDate)”登录失败达到2次(recordSize)时。要求用户提交验证码 //当用户最近“2小时内(recordDate)”操作失败达到2次(recordSize)时。要求用户提交验证码
const now = Date.now(), const now = Date.now(),
recordDate = 120 * 60 * 1000, recordDate = 120 * 60 * 1000,
recordSize = 2; recordSize = 2;
const uniIdLogCollection = db.collection('uni-id-log') const uniIdLogCollection = db.collection('uni-id-log')
let recentRecord = await uniIdLogCollection.where({ let recentRecord = await uniIdLogCollection.where({
deviceId: params.deviceId || context.DEVICEID, ip: context.CLIENTIP,
create_date: dbCmd.gt(now - recordDate), create_date: dbCmd.gt(now - recordDate),
type: 'login' action
}) })
.orderBy('create_date', 'desc') .orderBy('create_date', 'desc')
.limit(recordSize) .limit(recordSize)
...@@ -247,21 +249,50 @@ exports.main = async (event, context) => { ...@@ -247,21 +249,50 @@ exports.main = async (event, context) => {
} }
break; break;
case 'bindMobileBySms': case 'bindMobileBySms':
// console.log({ if(!(/^1\d{10}$/.test(params.mobile))){
// uid: params.uid, return {
// mobile: params.mobile, code: 401,
// code: params.code msg: '手机号格式错误'
// }); }
}
if(!params.code){
return {
code: 401,
msg: '短信验证码不能为空'
}
}
needCaptcha = await isNeedCaptcha()
console.log(needCaptcha)
if(needCaptcha){
let {captcha} = params
if(!captcha){
return {
errCode: 'CAPTCHA_REQUIRED',
errMsg: '操作失败达到2次,请提交验证码'
}
}
res = await uniCaptcha.verify({
captcha,
scene: action
})
console.log(8956,res);
if(res.code != 0){
console.log(res,action);
return res
}
}
res = await uniID.bindMobile({ res = await uniID.bindMobile({
uid: params.uid, uid: params.uid,
mobile: params.mobile, mobile: params.mobile,
code: params.code code: params.code
}) })
// console.log(res); uniIdLog(res)
console.log(res);
break; break;
case 'register': case 'register':
var { var {
username, password, nickname username, password, nickname,captcha
} = params } = params
if (/^1\d{10}$/.test(username)) { if (/^1\d{10}$/.test(username)) {
return { return {
...@@ -274,6 +305,19 @@ exports.main = async (event, context) => { ...@@ -274,6 +305,19 @@ exports.main = async (event, context) => {
code: 401, code: 401,
msg: '用户名不能是邮箱' msg: '用户名不能是邮箱'
} }
}
if(!captcha){
return {
code: 401,
msg: '图形验证码不能为空'
}
}
res = await uniCaptcha.verify({
captcha,
scene: action
})
if(res.code != 0){
return res
} }
res = await uniID.register({ res = await uniID.register({
username, username,
...@@ -285,16 +329,14 @@ exports.main = async (event, context) => { ...@@ -285,16 +329,14 @@ exports.main = async (event, context) => {
await registerSuccess(res) await registerSuccess(res)
} }
break; break;
case 'getNeedCaptcha': { case 'getNeedCaptcha': {
const needCaptcha = await isNeedCaptcha() const needCaptcha = await isNeedCaptcha()
res.needCaptcha = needCaptcha res.needCaptcha = needCaptcha
break; break;
} }
case 'login': case 'login':
let passed = false; let passed = false;
let needCaptcha = await isNeedCaptcha(); needCaptcha = await isNeedCaptcha();
console.log('needCaptcha', needCaptcha); console.log('needCaptcha', needCaptcha);
if (needCaptcha) { if (needCaptcha) {
res = await uniCaptcha.verify({ res = await uniCaptcha.verify({
...@@ -310,7 +352,7 @@ exports.main = async (event, context) => { ...@@ -310,7 +352,7 @@ exports.main = async (event, context) => {
queryField: ['username', 'email', 'mobile'] queryField: ['username', 'email', 'mobile']
}); });
res.type = 'login' res.type = 'login'
await loginLog(res); await uniIdLog(res);
needCaptcha = await isNeedCaptcha(); needCaptcha = await isNeedCaptcha();
} }
...@@ -380,16 +422,16 @@ exports.main = async (event, context) => { ...@@ -380,16 +422,16 @@ exports.main = async (event, context) => {
delete loginRes.accessToken delete loginRes.accessToken
delete loginRes.refreshToken delete loginRes.refreshToken
} }
await loginLog(loginRes) await uniIdLog(loginRes)
return loginRes return loginRes
break; break;
case 'loginByUniverify': case 'loginByUniverify':
res = await uniID.loginByUniverify(params) res = await uniID.loginByUniverify(params)
await loginLog(res) await uniIdLog(res)
break; break;
case 'loginByApple': case 'loginByApple':
res = await uniID.loginByApple(params) res = await uniID.loginByApple(params)
await loginLog(res) await uniIdLog(res)
break; break;
case 'checkToken': case 'checkToken':
res = await uniID.checkToken(uniIdToken); res = await uniID.checkToken(uniIdToken);
...@@ -404,11 +446,17 @@ exports.main = async (event, context) => { ...@@ -404,11 +446,17 @@ exports.main = async (event, context) => {
break; break;
case 'sendSmsCode': case 'sendSmsCode':
/* -开始- 测试期间,为节约资源。统一虚拟短信验证码为: 123456;开启以下代码块即可 */ /* -开始- 测试期间,为节约资源。统一虚拟短信验证码为: 123456;开启以下代码块即可 */
return uniID.setVerifyCode({ res = uniID.setVerifyCode({
mobile: params.mobile, mobile: params.mobile,
code: '123456', code: '123456',
type: params.type type: params.type
}) })
return {
...res,
code: 40000,
msg:
"已启动测试模式,直接使用:123456作为短信验证码即可。正式项目,请配置/common/uni-config-center/uni-id/config.json(service->sm中的密钥信息)并在uni-id-cf完成配置 "
}
/* -结束- */ /* -结束- */
// 简单限制一下客户端调用频率 // 简单限制一下客户端调用频率
...@@ -438,7 +486,25 @@ exports.main = async (event, context) => { ...@@ -438,7 +486,25 @@ exports.main = async (event, context) => {
templateId templateId
}) })
break; break;
case 'loginBySms': case 'loginBySms':
needCaptcha = await isNeedCaptcha()
if(needCaptcha){
let {captcha} = params
if(!captcha){
return {
errCode: 'CAPTCHA_REQUIRED',
errMsg: '操作失败达到2次,请提交验证码'
}
}
res = await uniCaptcha.verify({
captcha,
scene: action
})
if(res.code != 0){
console.log(res,action);
return res
}
}
if (!params.code) { if (!params.code) {
return { return {
code: 500, code: 500,
...@@ -452,7 +518,7 @@ exports.main = async (event, context) => { ...@@ -452,7 +518,7 @@ exports.main = async (event, context) => {
} }
} }
res = await uniID.loginBySms(params) res = await uniID.loginBySms(params)
await loginLog(res) await uniIdLog(res)
break; break;
case 'resetPwdBySmsCode': case 'resetPwdBySmsCode':
if (!params.code) { if (!params.code) {
......
{ {
"name": "uni-id-cf", "name": "uni-id-cf-123",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
......
## 3.3.23(2022-06-13)
- 修复上版本引出的部分依赖未找到的Bug
## 3.3.22(2022-06-13)
- 新增 preferedWebPlatform 配置用于解决HBuilderX 3.4.9版本起web端platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-web-platform)
## 3.3.21(2022-05-24)
- 修复createInstance传入clientInfo无效的Bug
## 3.3.20(2022-05-19)
- 调整以下错误码(账号已注册[uni-id-account-exists]、账号不存在[uni-id-account-not-exists]、账号已绑定[uni-id-account-bound])
## 3.3.19(2022-05-19)
- 修复 addUser 部分情况下会创建出重复账号的Bug
## 3.3.18(2022-05-12)
- 调整绑定、解绑邮箱手机号接口,只要传递code参数就进行验证码校验即使传递的值为undefined
## 3.3.17(2022-05-09) ## 3.3.17(2022-05-09)
- register_env内增加os_name字段用于区分注册时的客户端系统类型 - register_env内增加os_name字段用于区分注册时的客户端系统类型
## 3.3.16(2022-05-09) ## 3.3.16(2022-05-09)
...@@ -47,55 +35,55 @@ ...@@ -47,55 +35,55 @@
- 移除部分接口的废弃提示 - 移除部分接口的废弃提示
## 3.3.6(2021-09-08) ## 3.3.6(2021-09-08)
- 修复 邀请码可能重复的Bug - 修复 邀请码可能重复的Bug
## 3.3.5(2021-08-10) ## 3.3.5(2021-08-10)
- 修复版本号错误 - 修复版本号错误
## 3.3.4(2021-08-10) ## 3.3.4(2021-08-10)
- 微信、QQ、支付宝登录新增type参数用于指定当前是登录还是注册 - 微信、QQ、支付宝登录新增type参数用于指定当前是登录还是注册
## 3.3.3(2021-08-04) ## 3.3.3(2021-08-04)
- 修复使用数组形式的配置文件报错的Bug - 修复使用数组形式的配置文件报错的Bug
## 3.3.2(2021-08-03) ## 3.3.2(2021-08-03)
- 修复上3.3.0版本引出的createInstance接口传入配置不生效的Bug 感谢[hmh](https://gitee.com/hmh) - 修复上3.3.0版本引出的createInstance接口传入配置不生效的Bug 感谢[hmh](https://gitee.com/hmh)
## 3.3.1(2021-07-30) ## 3.3.1(2021-07-30)
- 修复 将设置用户允许登录的应用列表时传入空数组报错的Bug - 修复 将设置用户允许登录的应用列表时传入空数组报错的Bug
## 3.3.0(2021-07-30) ## 3.3.0(2021-07-30)
- 新增 不同端应用配置隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config) - 新增 不同端应用配置隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config)
- 新增 不同端用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user) - 新增 不同端用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user)
+ 此版本升级需要开发者处理一下用户数据,请参考 [补齐用户dcloud_appid字段](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=makeup-dcloud-appid) + 此版本升级需要开发者处理一下用户数据,请参考 [补齐用户dcloud_appid字段](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=makeup-dcloud-appid)
- 新增 QQ登录、注册相关功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=qq) - 新增 QQ登录、注册相关功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=qq)
- 调整 不再支持绑定手机、邮箱时不填验证码直接绑定 - 调整 不再支持绑定手机、邮箱时不填验证码直接绑定
## 3.2.1(2021-07-09) ## 3.2.1(2021-07-09)
- 撤销3.2.0版本所做的调整 - 撤销3.2.0版本所做的调整
## 3.2.0(2021-07-09) ## 3.2.0(2021-07-09)
- 【重要】支持不同端(管理端、用户端等)用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user) - 【重要】支持不同端(管理端、用户端等)用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user)
- 支持不同端(管理端、用户端等)配置文件隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config) - 支持不同端(管理端、用户端等)配置文件隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config)
## 3.1.3(2021-07-08) ## 3.1.3(2021-07-08)
- 移除插件内误传的node_modules - 移除插件内误传的node_modules
## 3.1.2(2021-07-08) ## 3.1.2(2021-07-08)
- 修复 微信小程序绑定微信账号时报错的Bug - 修复 微信小程序绑定微信账号时报错的Bug
## 3.1.1(2021-07-01) ## 3.1.1(2021-07-01)
- 使用新的错误码规范,兼容旧版 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=errcode) - 使用新的错误码规范,兼容旧版 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=errcode)
- 修复微信登录、绑定时未返回用户accessToken的Bug - 修复微信登录、绑定时未返回用户accessToken的Bug
## 3.1.0(2021-04-19) ## 3.1.0(2021-04-19)
- 增加对用户名、邮箱、密码字段的两端去空格 - 增加对用户名、邮箱、密码字段的两端去空格
- 默认忽略用户名、邮箱的大小写 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=case-sensitive) - 默认忽略用户名、邮箱的大小写 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=case-sensitive)
- 修复 customToken导出async方法报错的Bug - 修复 customToken导出async方法报错的Bug
## 3.0.12(2021-04-13) ## 3.0.12(2021-04-13)
- 调整bindTokenToDevice默认值为false - 调整bindTokenToDevice默认值为false
## 3.0.11(2021-04-12) ## 3.0.11(2021-04-12)
- 修复3.0.7版本引出的多个用户访问时可能出现30201报错的Bug - 修复3.0.7版本引出的多个用户访问时可能出现30201报错的Bug
## 3.0.10(2021-04-08) ## 3.0.10(2021-04-08)
- 优化错误提示 - 优化错误提示
## 3.0.9(2021-04-08) ## 3.0.9(2021-04-08)
- bindMobile接口支持通过一键登录的方式绑定 - bindMobile接口支持通过一键登录的方式绑定
- 优化错误提示 - 优化错误提示
## 3.0.8(2021-03-19) ## 3.0.8(2021-03-19)
- 修复 3.0.7版本某些情况下生成token报错的Bug - 修复 3.0.7版本某些情况下生成token报错的Bug
## 3.0.7(2021-03-19) ## 3.0.7(2021-03-19)
- 新增 支持uni-config-center,更新uni-id无须再担心配置被覆盖 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=uni-config-center) - 新增 支持uni-config-center,更新uni-id无须再担心配置被覆盖 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=uni-config-center)
- 新增 自定义token内容,可以缓存角色权限之外的更多信息到客户端 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=custom-token) - 新增 自定义token内容,可以缓存角色权限之外的更多信息到客户端 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=custom-token)
- 新增 支持传入context获取uni-id实例,防止单实例多并发时全局context混乱 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=create-instance) - 新增 支持传入context获取uni-id实例,防止单实例多并发时全局context混乱 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=create-instance)
## 3.0.6(2021-03-05) ## 3.0.6(2021-03-05)
- 新增[uniID.wxBizDataCrypt](https://uniapp.dcloud.io/uniCloud/uni-id?id=%e5%be%ae%e4%bf%a1%e6%95%b0%e6%8d%ae%e8%a7%a3%e5%af%86)方法 - 新增[uniID.wxBizDataCrypt](https://uniapp.dcloud.io/uniCloud/uni-id?id=%e5%be%ae%e4%bf%a1%e6%95%b0%e6%8d%ae%e8%a7%a3%e5%af%86)方法
- 优化loginByApple方法,提高接口响应速度 - 优化loginByApple方法,提高接口响应速度
## 3.0.5(2021-02-03) ## 3.0.5(2021-02-03)
- 调整为uni_modules目录规范 - 调整为uni_modules目录规范
{ {
"id": "uni-id", "id": "uni-id",
"displayName": "uni-id", "displayName": "uni-id",
"version": "3.3.23", "version": "3.3.17",
"description": "简单、统一、可扩展的用户中心", "description": "简单、统一、可扩展的用户中心",
"keywords": [ "keywords": [
"uniid", "uniid",
......
{ {
"name": "uni-id", "name": "uni-id",
"version": "3.3.23", "version": "3.3.17",
"description": "uni-id for uniCloud", "description": "uni-id for uniCloud",
"main": "index.js", "main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id", "homepage": "https://uniapp.dcloud.io/uniCloud/uni-id",
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
}, },
"/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json": { "/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json": {
"tokenExpiresIn": 7200, "tokenExpiresIn": 7200,
"app-plus": { "app": {
"oauth": { "oauth": {
"weixin": { "weixin": {
"appid": "填写来源微信开放平台https://open.weixin.qq.com/创建的应用的appid", "appid": "填写来源微信开放平台https://open.weixin.qq.com/创建的应用的appid",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册