提交 ded9c842 编写于 作者: DCloud_JSON's avatar DCloud_JSON

修复在某些情况下,微信小程序端验证码显示错误的问题

上级 093f2117
## 1.2.1(2022-05-18)
- 修复在某些情况下,微信小程序端验证码显示错误的问题
## 1.2.0(2022-05-16)
- 短信验证码登陆、绑定手机号码新增防刷逻辑。当短信验证码输入错误2次以上,弹出图形验证码进行人机校验。
- uni-id-cf,新增防刷机制。更改loginLog为uniIdLog 记录各类uni-id操作,并新增action字段记录操作的行为名称
......
{
"name": "uni-starter",
"appid": "请点击重新获取",
"description": "云端一体应用快速开发模版",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"locales": {
"en": {
"name": "uni-starter",
"android": {
"strings": {
"CustomKey": "CustomValue"
}
},
"ios": {
"privacyDescription": {
"NSPhotoLibraryUsageDescription": "access to the user’s photo library(read)"
},
"infoPlist": {
"CustomKey": "CustomValue"
}
}
},
"zh": {
"name": "统一应用基本项目"
}
},
"privacy": {
"prompt": "template",
"template": {
"title": "服务协议和隐私政策",
"message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"https://ask.dcloud.net.cn/protocol.html\">《服务协议》</a>和<a href=\"https://ask.dcloud.net.cn/protocol.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意",
"buttonRefuse": "暂不同意"
}
},
"compatible": {
"ignoreVersion": true
},
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": false,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {
"Fingerprint": {
},
"Share": {
},
"OAuth": {
},
"FaceID": {
},
"Geolocation": {
},
"Bluetooth": {
},
"Push": {
},
"Maps": {
}
},
"distribute": {
"android": {
"permissions": [
"name" : "uni-starter-old",
"appid" : "__UNI__EC87F46",
"description" : "云端一体应用快速开发模版",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
"locales" : {
"en" : {
"name" : "uni-starter",
"android" : {
"strings" : {
"CustomKey" : "CustomValue"
}
},
"ios" : {
"privacyDescription" : {
"NSPhotoLibraryUsageDescription" : "access to the user’s photo library(read)"
},
"infoPlist" : {
"CustomKey" : "CustomValue"
}
}
},
"zh" : {
"name" : "统一应用基本项目"
}
},
"privacy" : {
"prompt" : "template",
"template" : {
"title" : "服务协议和隐私政策",
"message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"https://ask.dcloud.net.cn/protocol.html\">《服务协议》</a>和<a href=\"https://ask.dcloud.net.cn/protocol.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept" : "同意",
"buttonRefuse" : "暂不同意"
}
},
"compatible" : {
"ignoreVersion" : true
},
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : false,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {
"Fingerprint" : {},
"Share" : {},
"OAuth" : {},
"FaceID" : {},
"Geolocation" : {},
"Bluetooth" : {},
"Push" : {},
"Maps" : {}
},
"distribute" : {
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
......@@ -86,156 +78,136 @@
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"abiFilters": [
"armeabi-v7a",
"arm64-v8a",
"x86"
]
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
},
"ios": {
"capabilities": {
"entitlements": {
"com.apple.developer.associated-domains": [
"applinks:static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com"
]
"ios" : {
"capabilities" : {
"entitlements" : {
"com.apple.developer.associated-domains" : [ "applinks:static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com" ]
}
}
},
"sdkConfigs": {
"oauth": {
"apple": {
"sdkConfigs" : {
"oauth" : {
"apple" : {},
"weixin" : {
"appid" : "",
"appsecret" : "",
"UniversalLinks" : ""
},
"weixin": {
"appid": "",
"appsecret": "",
"UniversalLinks": ""
"univerify" : {}
},
"univerify": {
"ad" : {},
"share" : {
"weixin" : {
"appid" : "",
"UniversalLinks" : ""
}
},
"ad": {
},
"share": {
"weixin": {
"appid": "",
"UniversalLinks": ""
}
},
"geolocation": {
"baidu": {
"__platform__": [
"ios",
"android"
],
"appkey_ios": "请填写地图的key",
"appkey_android": "请填写地图的key"
"geolocation" : {
"baidu" : {
"__platform__" : [ "ios", "android" ],
"appkey_ios" : "请填写地图的key",
"appkey_android" : "请填写地图的key"
}
},
"push": {
"unipush": {
"version": "2",
"offline": true,
"meizu": {
},
"mi": {
},
"vivo": {
},
"oppo": {
},
"hms": {
"push" : {
"unipush" : {
"version" : "2",
"offline" : true,
"meizu" : {},
"mi" : {},
"vivo" : {},
"oppo" : {},
"hms" : {}
}
}
},
"payment": {
},
"maps": {
}
"payment" : {},
"maps" : {}
},
"icons": {
"android": {
"hdpi": "",
"xhdpi": "",
"xxhdpi": "",
"xxxhdpi": ""
"icons" : {
"android" : {
"hdpi" : "",
"xhdpi" : "",
"xxhdpi" : "",
"xxxhdpi" : ""
},
"ios": {
"appstore": "",
"ipad": {
"app": "",
"app@2x": "",
"notification": "",
"notification@2x": "",
"proapp@2x": "",
"settings": "",
"settings@2x": "",
"spotlight": "",
"spotlight@2x": ""
"ios" : {
"appstore" : "",
"ipad" : {
"app" : "",
"app@2x" : "",
"notification" : "",
"notification@2x" : "",
"proapp@2x" : "",
"settings" : "",
"settings@2x" : "",
"spotlight" : "",
"spotlight@2x" : ""
},
"iphone": {
"app@2x": "",
"app@3x": "",
"notification@2x": "",
"notification@3x": "",
"settings@2x": "",
"settings@3x": "",
"spotlight@2x": "",
"spotlight@3x": ""
"iphone" : {
"app@2x" : "",
"app@3x" : "",
"notification@2x" : "",
"notification@3x" : "",
"settings@2x" : "",
"settings@3x" : "",
"spotlight@2x" : "",
"spotlight@3x" : ""
}
}
},
"splashscreen": {
"iosStyle": "common",
"androidStyle": "common",
"useOriginalMsgbox": true
"splashscreen" : {
"iosStyle" : "common",
"androidStyle" : "common",
"useOriginalMsgbox" : true
}
},
"nvueLaunchMode": ""
},
"quickapp": {
"nvueLaunchMode" : ""
},
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false,
"es6": false
"quickapp" : {},
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false,
"es6" : false
},
"usingComponents": true,
"betterScopedSlots": true,
"permission": {
"scope.userLocation": {
"desc": "演示在onShow生命周期获取地理位置"
"usingComponents" : true,
"betterScopedSlots" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "演示在onShow生命周期获取地理位置"
}
}
},
"mp-alipay": {
"usingComponents": true
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu": {
"usingComponents": true
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao": {
"usingComponents": true
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics": {
"enable": false
"uniStatistics" : {
"enable" : false
},
"h5": {
"template": "",
"sdkConfigs": {
"maps": {
"qqmap": {
"key": ""
"h5" : {
"template" : "",
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : ""
}
}
},
"router": {
"base": ""
"router" : {
"base" : ""
},
"uniStatistics": {
"enable": true
"uniStatistics" : {
"enable" : true
}
},
"_spaceID": "",
"vueVersion": "2"
"_spaceID" : "",
"vueVersion" : "2"
}
{
"id": "uni-starter",
"displayName": "uni-starter",
"version": "1.2.0",
"version": "1.2.1",
"description": "云端一体应用快速开发基本项目模版",
"keywords": [
"login",
......
......@@ -4,12 +4,8 @@
<text class="title">{{$t('pwdLogin.pwdLogin')}}</text>
<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')"/>
<!-- <view class="captcha-box" v-if="captchaBase64">
<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>
<uni-agreements class="agreement" @setAgree="agree = $event"></uni-agreements>
<button class="send-btn" :disabled="!canLogin" :type="canLogin?'primary':'default'"
@click="pwdLogin">{{$t('pwdLogin.login')}}</button>
<!-- 忘记密码 -->
......@@ -107,7 +103,7 @@
}
</script>
<style>
<style lang="scss" scoped>
@import url("../common/login-page.css");
.auth-box {
......@@ -128,16 +124,7 @@
margin-top: 80px;
width: 600rpx;
}
.captcha-box{
flex-direction: row;
align-items: center;
justify-content: flex-end;
}
.captcha-img{
margin: 0 15px 10px 0;
width: 250rpx;
}
.captcha{
width: 350rpx;
.agreement{
margin-top: 10px;
}
</style>
## 0.5.1(2022-05-18)
- 修复在某些情况下微信小程序端验证码显示错误的问题
## 0.5.0(2022-05-17)
- 新增支持在`uni-captcha-co`->`config`配置验证码
## 0.4.1(2022-05-16)
- 新增示例项目
## 0.4.0(2022-05-16)
......
......@@ -73,7 +73,7 @@
uniIdCo.getImageCaptcha({
scene: this.scene
}).then(result => {
console.log(result);
// console.log(result);
this.captchaBase64 = result.captchaBase64
})
.catch(e => {
......@@ -107,7 +107,6 @@
}
.captcha-img-box {
cursor: pointer;
position: relative;
background-color: #FEFAE7;
}
......@@ -126,9 +125,14 @@
.captcha-img-box,
.captcha-img,
.loding {
height: 44px !important;
width: 100px;
}
.captcha-img{
cursor: pointer;
}
.loding {
z-index: 9;
color: #bbb;
......
{
"id": "uni-captcha",
"displayName": "uni-captcha",
"version": "0.4.1",
"description": "简洁、高效、灵活可配置的云端验证码模块",
"version": "0.5.1",
"description": "云端一体图形验证码组件",
"keywords": [
"uniCloud",
"captcha",
......
## 用途说明
主要起到人机校验或其他限制调用的作用,如:
- 防止机器冒充人类做暴力破解
- 防止大规模在线注册滥用服务
- 防止滥用在线批量操
- 防止信息被大量采集聚合
常见的业务场景有:
- 注册环节:防止无效垃圾注册,从源头进行管理
- 登录环节:防止撞库攻击、暴力破解,保障用户数据案例
- 短信防刷:减少短信接口被刷情况,减少企业不必要成本
- 互动环节:防止批量垃圾互动信息破坏用户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)验证码刷新失败 |
\ No newline at end of file
<h2>
文档已移至 <a href="https://uniapp.dcloud.io/uniCloud/uni-captcha.html" target="_blank">uni-captcha文档</a>
</h2>
\ No newline at end of file
module.exports = {
"image-captcha":{
"width": 150, //图片宽度
"height": 44, //图片高度
"background": "#FFFAE8", //验证码背景色,设置空字符`''`不使用背景颜色
// "size": 4, //验证码长度,最多 6 个字符
// "noise": 4, //验证码干扰线条数
// "color": false, //字体是否使用随机颜色,当设置`background`后恒为`true`
// "fontSize": 40, //字体大小
// "ignoreChars": '', //忽略那些字符
// "mathExpr": false, //是否使用数学表达式
// "mathMin": 1, //表达式所使用的最小数字
// "mathMax": 9, //表达式所使用的最大数字
// "mathOperator": '' //表达式所使用的运算符,支持 `+`、`-`。不传随机使用
// "expiresDate":180 //验证码过期时间(s)
}
}
\ No newline at end of file
// 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
//导入验证码公共模块
const uniCaptcha = require('uni-captcha')
//获取数据库对象
const db = uniCloud.database();
//获取数据表opendb-verify-codes对象
const verifyCodes = db.collection('opendb-verify-codes')
module.exports = {
async getImageCaptcha({scene}) {
let {deviceId} = this.getClientInfo();
let res = await verifyCodes.where({scene,deviceId,state:0}).limit(1).get()
console.log("res: " + JSON.stringify(res));
let action = res.data.length?'refresh':'create'
console.log(action);
return await uniCaptcha[action]({
async getImageCaptcha({
scene
}) {
//获取设备id
let {
deviceId,
platform
} = this.getClientInfo();
//根据:设备id、场景值、状态,查找记录是否存在
let res = await verifyCodes.where({
scene,
width:100,
height:44
})
deviceId,
state: 0
}).limit(1).get()
//如果已存在则调用刷新接口,反之调用插件接口
let action = res.data.length ? 'refresh' : 'create'
//执行并返回结果
//导入配置,配置优先级说明:此处配置 > uni-config-center
const config = require('./config')
return await uniCaptcha[action](Object.assign(
config, // 配置优先级说明:此配置 > uni-config-center
{
scene,//来源客户端传递,表示:使用场景值,用于防止不同功能的验证码混用
uniPlatform:platform
},
))
}
}
// 在本文件中可配置云数据库初始化,数据格式见:https://uniapp.dcloud.io/uniCloud/hellodb?id=db-init
// 编写完毕后对本文件点右键,可按配置规则创建表和添加数据
{
"opendb-verify-codes":{}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册