图形验证码生成及短信验证码发送流程及相关代码

SpringBoot实现图形验证码 目录项目创建前端代码实现约定前后端交互接口需求分析接口定义Hutool工具实现服务器端代码引入依赖获取验证码验证码校验调整前端代码随着安全性的要求越来越高,目前许多项目中都使用了验证码,验证码也有各种类型,如 图形验证码、短信验证码、邮件验证码、人脸识别等,这些不同类型的验证码可以根据实际需求和安全性要求进行选择和应用,保护网站和用户免受恶意攻击在本篇文章中,我们来学习图形验证码的实现验证码的实现方式有很多,可以由前端实现,也可以由后端进行实现,也有很多的插件和工具包可以使用,在这里,我们使用 Hutool 阅读详情

前提:首先我们的页面上有个图形验证码 然后有个短信验证码 有个点击发送按钮

每生成一次图形验证码都会放入缓存  后面发送短信验证码的请求 中的请求体必须包含它

其它短信发送相关bean见 :短信发送相关代码(使用了异步+线程池+http连接池 优化)

下面有俩接口 一个生成图形验证码 一个发送短信验证码 

package net.xdclass.controller;

import com.google.code.kaptcha.Producer;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.constant.RedisConstant;
import net.xdclass.controller.request.SendCodeRequest;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.SendCodeEnum;
import net.xdclass.service.NotifyService;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api/v1/account/notify")
@Slf4j
public class NotifyController {


    @Autowired
    private Producer captchaProducer;


    @Autowired
    private NotifyService notifyService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 生成验证码
     *
     * @param request
     * @param response
     */
    @GetMapping("captcha")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {

        String captchaText = captchaProducer.createText();
        log.info("验证码内容:{}", captchaText);

        //存储redis,配置过期时间
        String captchaKey = getCaptchaKey(request);
        redisTemplate.opsForValue().set(captchaKey, captchaText, RedisConstant.CAPTCHA_KEY_TIMEOUT, TimeUnit.MILLISECONDS);

        BufferedImage bufferedImage = captchaProducer.createImage(captchaText);

        try {
            ServletOutputStream outputStream = response.getOutputStream();
            ImageIO.write(bufferedImage, "jpg", outputStream);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            log.error("获取流出错:{}", e.getMessage());
        }

    }

    private String getCaptchaKey(HttpServletRequest request) {

        String ip = CommonUtil.getIpAddr(request);
        String userAgent = request.getHeader("User-Agent");
        String captchaKey = "account-service:captcha:" + CommonUtil.MD5(ip + userAgent);

        return captchaKey;
    }


    /**
     * 测试发送验证码接口-主要是用于对比优化前后区别
     *
     * @return
     */
    @PostMapping("send_code")
    public JsonData sendCode(@RequestBody SendCodeRequest sendCodeRequest, HttpServletRequest request) {

        String captchaKey = getCaptchaKey(request);
        String cacheCaptchaValue = redisTemplate.opsForValue().get(captchaKey).toString();

        String captcha = sendCodeRequest.getCaptcha();
        String to = sendCodeRequest.getTo();

        if (null != cacheCaptchaValue && null != captcha && captcha.equals(cacheCaptchaValue)) {
            redisTemplate.delete(captchaKey);
            JsonData result = notifyService.sendCode(SendCodeEnum.USER_REGISTER,to);
            return result;
        } else {
            return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_ERROR);
        }

    }

}

短信验证码请求体

@Data
public class SendCodeRequest {

    private String captcha;

    private String to;
}

为了防刷 短信验证码 60秒只能发一次  所以

以 前缀+to(to就是发送手机号) 为key 以验证码+时间戳为value

 存入redis 来进行校验 

package net.xdclass.service.impl;

import lombok.extern.slf4j.Slf4j;
import net.xdclass.component.SmsComponent;
import net.xdclass.config.SmsConfig;
import net.xdclass.constant.RedisConstant;
import net.xdclass.constant.RedisKey;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.SendCodeEnum;
import net.xdclass.service.NotifyService;
import net.xdclass.util.CheckUtil;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 小滴课堂,愿景:让技术不再难学
 *
 * @Description
 * @Author 二当家小D
 * @Remark 有问题直接联系我,源码-笔记-技术交流群
 * @Version 1.0
 **/

@Service
@Slf4j
public class NotifyServiceImpl implements NotifyService {


    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private SmsComponent smsComponent;

    @Autowired
    private SmsConfig smsConfig;

    @Override
    public JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {
        try {
            //判断是否重复发送 以防刷 60秒内只能一次
            String cacheKey = String.format(RedisKey.CHECK_CODE_KEY, SendCodeEnum.USER_REGISTER, to);
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);

            if (cacheValue != null) {
                long leftTime = CommonUtil.getCurrentTimestamp() - Long.valueOf(cacheValue.split("_")[1]);
                if (leftTime < 5 * 60 * 1000) {
                    return JsonData.buildResult(BizCodeEnum.CODE_LIMITED);
                }
            }

            //若是超过一分钟则 重新把验证码存入缓存 并且发送
            String sendCode = CommonUtil.getRandomCode(6);
            cacheValue = sendCode + "_" + CommonUtil.getCurrentTimestamp();
            redisTemplate.opsForValue().set(cacheKey, cacheValue, RedisConstant.CAPTCHA_KEY_TIMEOUT, TimeUnit.MILLISECONDS);

            if (CheckUtil.isEmail(to)) {
                //todo
                return JsonData.buildSuccess();
            } else if (CheckUtil.isPhone(to)) {
                smsComponent.send(to, smsConfig.getTemplateId(), sendCode);
                log.info("发送短信验证码成功");
                return JsonData.buildSuccess();
            }

        } catch (Exception e) {
            String errorMsg = "发送短信验证码失败";
            log.info(errorMsg);
            return JsonData.buildError(errorMsg);
        }
        return null;
    }
}

相关枚举类 与常量类

public class RedisKey {

/**
* 验证码缓存key,第一个是类型,第二个是唯一标识比如手机号或者邮箱
*/
public static final String CHECK_CODE_KEY = "code:%s:%s";


}
public enum SendCodeEnum {

/**
* 用户注册
*/
USER_REGISTER;

}
import lombok.Getter;


public enum BizCodeEnum {


/**
* 短链分组
*/
GROUP_REPEAT(23001,"分组名重复"),
GROUP_OPER_FAIL(23503,"分组名操作失败"),
GROUP_NOT_EXIST(23404,"分组不存在"),


/**
*验证码
*/
CODE_TO_ERROR(240001,"接收号码不合规"),
CODE_LIMITED(240002,"验证码发送过快"),
CODE_ERROR(240003,"验证码错误"),
CODE_CAPTCHA_ERROR(240101,"图形验证码错误"),



/**
* 账号
*/
ACCOUNT_REPEAT(250001,"账号已经存在"),
ACCOUNT_UNREGISTER(250002,"账号不存在"),
ACCOUNT_PWD_ERROR(250003,"账号或者密码错误"),
ACCOUNT_UNLOGIN(250004,"账号未登录"),


/**
* 短链
*/
SHORT_LINK_NOT_EXIST(260404,"短链不存在"),


/**
* 订单
*/
ORDER_CONFIRM_PRICE_FAIL(280002,"创建订单-验价失败"),
ORDER_CONFIRM_REPEAT(280008,"订单恶意-重复提交"),
ORDER_CONFIRM_TOKEN_EQUAL_FAIL(280009,"订单令牌缺少"),
ORDER_CONFIRM_NOT_EXIST(280010,"订单不存在"),

/**
* 支付
*/
PAY_ORDER_FAIL(300001,"创建支付订单失败"),
PAY_ORDER_CALLBACK_SIGN_FAIL(300002,"支付订单回调验证签失败"),
PAY_ORDER_CALLBACK_NOT_SUCCESS(300003,"支付宝回调更新订单失败"),
PAY_ORDER_NOT_EXIST(300005,"订单不存在"),
PAY_ORDER_STATE_ERROR(300006,"订单状态不正常"),
PAY_ORDER_PAY_TIMEOUT(300007,"订单支付超时"),


/**
* 流控操作
*/
CONTROL_FLOW(500101,"限流控制"),
CONTROL_DEGRADE(500201,"降级控制"),
CONTROL_AUTH(500301,"认证控制"),


/**
* 流量包操作
*/
TRAFFIC_FREE_NOT_EXIST(600101,"免费流量包不存在,联系客服"),

TRAFFIC_REDUCE_FAIL(600102,"流量不足,扣减失败"),

TRAFFIC_EXCEPTION(600103,"流量包数据异常,用户无流量包"),


/**
* 通用操作码
*/

OPS_REPEAT(110001,"重复操作"),
OPS_NETWORK_ADDRESS_ERROR(110002,"网络地址错误"),


/**
* 文件相关
*/
FILE_UPLOAD_USER_IMG_FAIL(700101,"用户头像文件上传失败");

@Getter
private String message;

@Getter
private int code;

private BizCodeEnum(int code, String message){
this.code = code;
this.message = message;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonData {

/**
* 状态码 0 表示成功
*/

private Integer code;
/**
* 数据
*/
private Object data;
/**
* 描述
*/
private String msg;


/**
* 获取远程调用数据
* 注意事项:
* 支持多单词下划线专驼峰(序列化和反序列化)
*
* @param typeReference
* @param <T>
* @return
*/
public <T> T getData(TypeReference<T> typeReference){
return JSON.parseObject(JSON.toJSONString(data),typeReference);
}

/**
* 成功,不传入数据
* @return
*/
public static JsonData buildSuccess() {
return new JsonData(0, null, null);
}

/**
* 成功,传入数据
* @param data
* @return
*/
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null);
}

/**
* 失败,传入描述信息
* @param msg
* @return
*/
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg);
}



/**
* 自定义状态码和错误信息
* @param code
* @param msg
* @return
*/
public static JsonData buildCodeAndMsg(int code, String msg) {
return new JsonData(code, null, msg);
}

/**
* 传入枚举,返回信息
* @param codeEnum
* @return
*/
public static JsonData buildResult(BizCodeEnum codeEnum){
return JsonData.buildCodeAndMsg(codeEnum.getCode(),codeEnum.getMessage());
}
}

 


 

Java实现图形验证码 本文生成图像验证码使用了Hutool官网 — 🍬A set of tools that keep Java sweet.图形验证码是一种用于验证用户身份的安全措施。它通过生成一张包含随机图像和文字的图片,要求用户进行识别并输入正确信息来证明其为真实用户而非计算机程序。旨在防止恶意行为,例如批量注册、登录、灌水等,以提高网站的安全性。图形验证码通常会包含随机生成的数字、字母或符号,并且还会加入一些干扰元素,如曲线、噪点或干扰线,以增加识别难度。 阅读详情

相关推荐

图形验证码+短信验证码实战

前言:   上一篇分分享了基于阿里云实现的短信验证码文章,考虑到为了防止登录时,非人工操作,频繁获取验证码,趁热打铁,现在添加了图片验证码服务功能。借鉴网上传统的做法,把实现这两个验证的功能做成有个独立的服务,通过Http分别请求获取校验图片验证码和短信验证码。 一、需求描述: 图形验证码为,短信验证码为6位纯数字 同一系统图片验证码缓存中只存在一个,没有有效期,每次刷新更新旧图形验证码 短信...

weixin_43163153的博客 1442

图形验证码+短信验证码【Java应用实例】

一、图形验证码的实现 1.1 简介 常在网上晃悠的人,对下面这张图都不会陌生。特别是在注册新账号、确认交易时,它们都会频繁出现,要求我们输入正确的验证码,那这些看上去跟我们要做的事情完全无关的验证码到底有何作用呢? 其实,验证码的校验即是一个直接的图灵测试,简单的逻辑是:服务提供者向用户方提出一个较为复杂的问题,能正确回答的即是人类,回答不出的即是机器或者程序代码。这个概念基于这样一个重...

短信接口_短信验证码_短信验证码接口 2万+

1.6 实战:Postman请求Get接口-获取用于登录的图形验证码

Postman请求Get接口-获取用于登录的图形验证码

zyg的博客 4404

如何实现celery

一、为什么要使用celery 思考: 消费者取到消息之后,要消费掉(执行任务),需要我们去实现。 任务可能出现高并发的情况,需要补充多任务的方式执行。 耗时任务很多种,每种耗时任务编写的生产者和消费者代码有重复。 取到的消息什么时候执行,以什么样的方式执行。 结论: 实际开发中,我们可以借助成熟的工具Celery来完成。 有了Celery,我们在使用生产者消费者模式时,只需要关注任务本身,极大的简...

艾利克斯的个人博客 396

图形验证码从设计到实现

获取前端传过来的手机号以及图形验证码,根据ip以及网络地址相关西信息获取缓存key,根据key获取验证码,然后比较验证码是否一致,如果一致验证通过,删除缓存,执行发送短信相关业务。获取验证码的接口非常简单,调用验证码配置类获取验证码,将获取到的验证码缓存到redis中并设置过期时间,然后调用配置类传入验证码获取到图形验证码,通过IO流返回。上述我们主要讲述了用户登录获取验证码的逻辑,用户注册,找回密码逻辑都是差不多一样的,只是后续的处理不太一样,在这里就不再过多讲述了。再去看下校验验证码的这个接口,

qq_41934990的博客 2157

Python全栈(十一)Django项目之3.图形验证码实现逻辑和短信验证码初步实现

后端实现图形验证码验证时,需要先使用第三方包生成验证码,同时保存到Redis中,最后响应到前端注册页,需要配置好Redis;前端需要生成uuid,并获取验证码图片和显示,还需要对验证码长度进行验证。短信验证码逻辑为接收参数并校验,在校验成功、图形验证码正确时生成短信验证码并保存、响应;发送短信选择容联云通讯平台,可以进行开发调试,需要安装依赖库;同时为了多人发送短信而需要多次初始化类导致的内存占用问题,封装了短信发送的单例类。按照实现逻辑实现后端视图,并返回相应状态;为了避免硬编码,专门定义常量文件。

CUFEECR的博客 1572

Java实现短信验证码最佳实践

1、背景2、实现3、运行效果:4、源码5、总结1、背景年初,从外地转移阵地到西安,转眼已两个多月。很久不写业务代码了,到了新公司,条件恶劣到前所未有,从需求,设计,架构,实现,实施,测试,...

独行侠梦的博客 2222

RTL8211F千兆PHY原理图与PCB设计:从手册到量产的完整指南

在工业网关、边缘计算和FPGA/CPU以太网扩展设计中,千兆PHY芯片的稳定运行取决于外围电路的精细设计。RTL8211F(I)作为应用广泛的物理层收发器,其原理图设计涉及电源域划分、时钟与复位时序、RGMII/ MDIO接口匹配以及MDI差分网络等多个环节。正确理解数据手册的阅读顺序,掌握100Ω差分阻抗控制、等长约束和EMI布局技巧,是保证链路可靠性和信号完整性的关键。本文从芯片选型与数据手册解析切入,系统梳理从电源去耦到PCB布局布线的工程实践要点,帮助工程师规避Link不稳定、MDIO读取失败、吞吐

weixin_28721917的博客 348

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)

1、图形验证码后端逻辑 准备captcha扩展包 captcha扩展包用于后端生成图形验证码,captcha扩展包可以从网上百度找到相关代码和文件,fonts是支持的字体文件,包含有actionj.ttf、Arial.ttf、Georgia.ttf。 生成验证码文件:apps/verifications/libs/captcha/captcha.py #!/usr/bin/env python # -*- coding: utf-8 -*- # refer to `https://bitbucket.o

weixin_42118531的博客 1452

OpenClaw记忆插件升级实战:从memory-lancedb-pro安装到AgentSkill封装

向量数据库作为现代AI应用的核心基础设施,通过将非结构化数据转换为高维向量并利用相似度检索实现语义理解。其原理基于嵌入模型生成向量表示,结合近似最近邻搜索算法实现高效查询。这项技术为智能体系统提供了长期记忆和上下文感知能力,显著提升了对话连贯性与任务执行效率。在AI智能体开发领域,向量数据库常用于知识库构建、对话历史管理和个性化推荐等场景。本文聚焦OpenClaw框架中memory-lancedb-pro插件的部署实践,针对**插件加载顺序**和**Apache Arrow依赖**两大常见痛点,通过封装成可

weixin_42525482的博客 636

告别手动连接!一键搞定Mumu模拟器与Android Studio的ADB配置

本文详细介绍了如何通过编写自动化脚本,一键完成Mumu模拟器与Android Studio的ADB连接配置。告别繁琐的手动命令输入,该脚本能智能检查环境、判断连接状态并自动处理端口转发,显著提升Android开发者的调试与测试效率。

yog99的博客 495

开源低代码平台-Microi吾码-接口引擎实战:发送第三方短信

【代码】开源低代码平台-Microi吾码-接口引擎实战:发送第三方短信。

开源低代码平台-Microi吾码-框架C#.NET8 + Vue3 1万+

计组实验之四路表决器

请在之前四路表决器电路实现的基础上,继续用连接线 联接CD4511,将投票为1的票数总和显示到一个 LED上,思考如何做?由于我们需要通过显示字形来直接看出有几个1,在这里,我们最多有四个1,即显示字形最多到4就行,所以我们需要将有几个1用2进制数来表示,下面给出了相关电路图。显示输入插口,按真值表(自拟)要求,逐次改变输入变量, 测量相应的输出值,验证逻辑功能, 与表3-1 进行比较, 验证所设计的逻辑电路是否符合要求。用实验结果与真值表3-1和3-2进行比对,可以得出,我们实验结果是正确的。

szyugly的博客 1万+

Java 发送验证码(多平台)和 图形验证码 的实现

在用户进行登录或者注册时,都会有输入验证码的相关操作,这就会涉及到发送验证码给用户的操作,发送方式大致分为两种:短信和邮箱,其中他们又有各种平台的发送方式,本次分别开发了和,其他的类似。图形验证码则是防止恶意刷短信次数设置的,主要是如何将生成的字符串转成图形验证码的过程。

weixin_48685040的博客 1849

Ubuntu20.04安装Ceres库

ceres真难安装。

little_white138的博客 8378

复习单片机:动态数码管(1 数码管介绍+2 74HC245 和 74HC138 芯片介绍+3 硬件设计+4 软件设计+5 实验现象)

复习单片机:动态数码管(1 数码管介绍+2 74HC245 和 74HC138 芯片介绍+3 硬件设计+4 软件设计+5 实验现象)

wo12369874的博客 1699

微信小程序中短信验证码登录全流程及代码

才开始学习小程序,用到短信验证码登录,使用榛子云短信(smsow.zhenzikj.com) 效果: 我是java开发者,后端使用了springMvc 短信验证码实现流程 1、构造手机验证码,生成一个6位的随机数字串; 2、使用接口向短信平台发送手机号和验证码,然后短信平台再把验...

chuanshenqm74397的博客 2163

Vue3+Openlayers教程导航页【目录】

欢迎来到 Vue3+Openlayers 系列教程,这里是我在 优快云 编写的所有 Vue3+Openlayers 系列相关文章的索引页,适合初学者到进阶用户系统阅读。👀 更多教程正在持续更新中,欢迎关注我,一起探索 Openlayers 的强大魅力!

小彭的博客 1258

Redis入门,解决手机短信验证码发送上限问题

来填上一周的坑了~在测试注册时会遇到需要用某一手机号发送验证码来完成注册流程,而每个手机号每天发送验证码的次数是有上限的,这时就会出现测试用例还没跑完而手机号已经不能发送验证码的情况。这时我们就可以通过修改Redis缓存来解决这个问题。Redis是一个开源的、使用 C 语言编写的高性能键值对存储数据库。Redis 于项目中的主要作用在于充当缓存,作用有二。

weixin_43812150的博客 1563

2025年再论 SAP ABAP 系统经典的三层架构的理解

【摘要】本文基于作者19年SAP开发经验,深入解析SAP经典三层架构在S/4HANA时代的演进与实践。数据库层从多数据库支持转向HANA专属,通过SQLScript实现计算下推;应用层以ABAP平台为核心,融合OData和RAP等现代开发范式;表现层从SAP GUI过渡到Fiori Launchpad为主导。文章通过真实案例(如月末关账优化、ATP检查改造)展示三层协作如何提升性能,并探讨了本地/云/混合部署策略。全文贯穿SAP官方技术文档参考,为开发者提供体系化架构认知和实践指导。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 754

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

实现图片验证码与手机短信验证码

实现图片验证码与手机短信验证码: 1、HTML 代码: <!-- 和验证码一样大小的提示图片:"请输入手机号" --> <input type="hidden" value="${@cn.mycs.core.util.ToolUtil.url(@cn.mycs.core.constant.CommonConstant.DOMAINS_STATIC)}/2.0...

wpg_boke的博客 2503

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【Spring Security OAuth2】- spring security - 短信验证码接口发送

作者简介:大家好,我是哥,前中兴通讯、美团架构师,现某互联网公司联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!

smart_an的专栏 1355

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

一个短信验证码功能引发的总结思考

从简单的功能探寻背后的技术。 文章目录故事短信验证码设计总结1、时间限制2、图形验证码限制 + 时间限制3、手机号+指定时间可以发短信次数限制4、IP及Cookie限制5、短信预警机制后记 故事 昨天看到一个地址,新用户免费领取X登读书APP的14天会员,2020年了,要开始读书了。看到这个活动是在笔记本上,于是用笔记本浏览器访问活动页面,输入手机号,收到验证码,填写验证码,领取这个会员。本...

阿飞云漫步 3407
上一篇: springboot整合redis
下一篇: 0基础五分钟学会使用shardingJDBC实现分表 及测试
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我才是真的封不觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值