用户密码存储与校验方案

一、密码存储流程

用户密码使用 随机加盐 方式存储,存储流程如下:

  1. 生成随机盐s
  2. 随机盐s与密码明文p拼接得到待哈希串m
  3. hash(m)得到密文e
  4. 哈希函数代号c+特定分隔符a+随机盐s+特定分隔符a+密文e=最终入库的字符串i

二、校验密码流程

  1. 网站使用HTTPS,前端将用户在界面输入的用户名user和密码明文pwd传输到后台;
  2. 登录接口接收到登录请求后,根据用户名user提取上述存储流程中的密码字符串i
  3. 后台对字符串i使用分隔符a切割得到哈希函数版本c、随机盐s和密码密文e
  4. 后台使用随机盐s和传入的密码明文pwd拼接,根据哈希函数版本c使用对应的算法计算e'
  5. 判断ee'是否相等,如果相等则登录校验成功,发放登录token,否则返回登录失败提示;

三、细节补充说明

  • 随机盐使用JDK自带的SecureRandom生成;
  • 哈希算法使用JDK自带的PBKDF2;
  • 分隔符为双冒号::
  • 哈希函数代号为0

四、密码工具类代码

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BinaryOperator;

/**
 * 密码工具类
 **/
public class PasswordUtils {
	private static final Logger log = LoggerFactory.getLogger(PasswordUtils.class);
	private static final String SEPARATOR = "::";
	private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
	private static final Map<String, BinaryOperator<String>> supportedHashFunctions = new ConcurrentHashMap<>();

	private static final String PBKDF2 = "0";
	private static final BinaryOperator<String> pbkdf2Function = (password, salt) -> {
		int iteration = 65536;
		int strength = 128;
		String algorithm = "PBKDF2WithHmacSHA1";
		KeySpec spec = new PBEKeySpec(password.toCharArray(), hexStringToBytes(salt), iteration, strength);
		try {
			SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
			return bytesToHexString(factory.generateSecret(spec).getEncoded());
		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
			log.error(e.getMessage(), e);
			throw new IllegalStateException(e);
		}
	};

	static {
		// add more hash functions if necessary
		supportedHashFunctions.put(PBKDF2, pbkdf2Function);
	}

	private PasswordUtils() {
	}

	/**
	 * 根据密码明文使用随机加盐生成密码密文
	 *
	 * @param plainPassword 用户密码明文
	 * @param hashOption 哈希函数选项,可不填
	 * @return 随机加盐后的密码密文
	 */
	public static String hashPassword(String plainPassword, String... hashOption) {
		SecureRandom random = new SecureRandom();
		byte[] salt = new byte[16];
		random.nextBytes(salt);
		String option;
		if (hashOption != null && hashOption.length > 0 && StringUtils.isNotBlank(hashOption[0])) {
			// user specified hash function
			option = hashOption[0];
		} else {
			// pick a random hash function
			option = String.valueOf(ThreadLocalRandom.current().nextInt(supportedHashFunctions.size()));
		}
		if (!supportedHashFunctions.containsKey(option)) {
			throw new IllegalArgumentException("there is no such hash option: " + option);
		}
		return option + SEPARATOR + bytesToHexString(salt) + SEPARATOR + supportedHashFunctions.get(option)
				.apply(plainPassword, bytesToHexString(salt));
	}

	/**
	 * 校验用户输入的明文密码是否正确
	 *
	 * @param plainPassword 用户输入的明文密码
	 * @param dbPassword 来自数据库的密码密文
	 * @return 密码校验是否通过
	 */
	public static boolean validatePassword(String plainPassword, String dbPassword) {
		String[] optionSaltAndPass = StringUtils.split(dbPassword, SEPARATOR);
		if (optionSaltAndPass == null || optionSaltAndPass.length != 3) {
			throw new IllegalStateException("split db password array should be of length 3");
		}
		String option = optionSaltAndPass[0];
		if (!supportedHashFunctions.containsKey(option)) {
			throw new IllegalStateException("hash function not found by option: " + option);
		}
		String salt = optionSaltAndPass[1];
		String encryptedPassword = optionSaltAndPass[2];
		return StringUtils.equals(supportedHashFunctions.get(option).apply(plainPassword, salt), encryptedPassword);

	}

	private static String bytesToHexString(byte[] bytes) {
		char[] hexChars = new char[bytes.length * 2];
		for (int j = 0; j < bytes.length; j++) {
			int v = bytes[j] & 0xFF;
			hexChars[j * 2] = hexArray[v >>> 4];
			hexChars[j * 2 + 1] = hexArray[v & 0x0F];
		}
		return new String(hexChars);
	}

	private static byte[] hexStringToBytes(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
		}
		return data;
	}
}

五、测试代码

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNull;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;

public class PasswordUtilsTest {
	@Test
	public void testHashAndValidatePassword() {
		String password = "this_1$_seCret";
		System.out.println("plain: " + password); // NOSONAR
		String encrypted = PasswordUtils.hashPassword(password);
		System.out.println("encrypted: " + encrypted); // NOSONAR
		assertThat(encrypted, IsNull.notNullValue());
		assertThat(ArrayUtils.getLength(StringUtils.split(encrypted, "::")), IsEqual.equalTo(3));
		assertTrue(PasswordUtils.validatePassword(password, encrypted));
		password = "fake_password";
		assertFalse(PasswordUtils.validatePassword(password, encrypted));
		encrypted = PasswordUtils.hashPassword(password, "0");
		assertTrue(PasswordUtils.validatePassword(password, encrypted));
		assertThrows(IllegalArgumentException.class, () -> PasswordUtils.hashPassword("shouldThrow", "999"));
		assertThrows(IllegalStateException.class, () -> PasswordUtils.validatePassword("shouldThrow", "0::asdfasdf"));
		assertThrows(IllegalStateException.class,
				() -> PasswordUtils.validatePassword("shouldThrow", "999::asdfasdf::sdfasdfasdf"));
	}
}

六、Maven依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8</version>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.4.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.4.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>

七、参考资料

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 某某汽车高性能计算管理平台系统需求 5 1.1 业务需求分析: 5 2 某某汽车 HPC/CAE云计算中心建设目标、策略及步骤 7 3.1 建设目标与策略 7 3.2 建设步骤 8 3 面向高性能计算中心的资源管理、作业调度系统方案 10 3.1 基于应用的场景分析 10 3.1.1 终端用户通过Compute Manager,提交Fluent批处理计算作业 10 3.1.2 终端用户通过Display Manager,提交需要图形节点支持的图形交互程序 13 3.1.3 终端用户通过Compute Manager,在线查看CAE计算结果中的动画 14 3.1.4 终端用户通过Portal启动其他第三方的虚拟桌面,如Ctrix 15 3.2 某某汽车技术中心 HPC云计算平台管理场景 17 3.2.1 HPC云计算平台管理维护 17 3.2.2 HPC云计算平台软、硬件利用情况监控、统计分析 18 4 澳汰尔PBS Works产品介绍 20 4.1 系统逻辑图 20 4.2 系统物理架构图 22 4.4 PBS Professional产品介绍 25 4.4.1 整合计算资源、方便用户使用 25 4.4.2 可靠性、可用性、可维护性(RAS) 26 4.4.3 贯彻企业服务公约管理模式 29 4.4.4 优化计算资源的使用 29 4.4.5 计算资源管理功能 30 4.4.6 作业调度功能 32 4.4.7 Hooks功能 34 4.4.8 网格计算 35 4.4.9 安全认证 35 4.5 PAS(PBS 应用服务) 37 4.6 Compute Manager 40 4.6.1 三员管理 41 4.7 Display Manager 42 4.7.1 Display Manager系统架构 44 4.7.2 Display Manager使用体验 45 4.8 PBS Works定制功能 48 4.8.1 菜单布局:通常将布局分为三个模块:计算管理器、集群状态、管理员工具。如果有其他的模块,我们可以方便地集成在这个框架内(awpf)。菜单模块支持用户访问控制。 48 4.8.2 集群状态监控:统计所有计算节点的运行状态、节点类型、应用程序、物理内存、实际使用内存、内存使用率、节点利用率等信息。磁盘信息和实际CPU利用率,通过数字的颜色来反应使用程度:0%<X<30%(绿色),30%<X<50%(蓝色),50%<X<80%(橙色),80%<X>在线设置或修改节点上绑定的applications 50 4.8.5 管理员工具>>用户统计:用户名称,作业总数,运行作业个数,排队作业个数,申请cpu核数,使用cpu核数,排队cpu核数等信息。申请cpu总资源比,通过数字的颜色来反应使用程度:0%<X<30%(绿色),30%<X<50%(蓝色),50%<X<80%(橙色),80%<X>作业管理:统计作业号、作业名称、用户、软件、节点数、核数、状态、开始时间、优先级等信息。当作业排队状态时,允许修改作业的优先级。另外管理员也可以删除任意作业。 51 4.8.7 管理员工具>>监控作业排队原因 51 4.8.8 管理员工具>>一周作业统计: 统计当天到过去一周内所累积的运行和排队作业个数。 51 4.8.9 管理员工具>>求解器使用情况统计:统计每个求解器提交的作业总数,在运行的作业,请求的cpu,排队cpu,使用cpu等信息。 52 4.8.10 管理员工具>>磁盘统计:通过WEB页面随时了解本地磁盘的使用情况。使用百分率,通过数字的颜色来反应使用程度:0%<X<30%(绿色),30%<X<50%(蓝色),50%<X<80%(橙色),80%<X>项目管理项:管理员可以以项目为单位,设定项目编号、项目名称、项目的开始和结束时间,项目组人员和项目的优先级。当有紧急的项目,管理员可以把项目的优先级提高,并可以把相应的用户加到项目组中,以此提高项目组成员的作业优先级。用户在Web Portal页面提交作业可以选择项目名称,并且只能选择自己所属项目的项目名称。 53 4.8.12 管理员工具>>作业委托管理: 统计当前用户自己所提交的作业总数,包含:作业号,作业名称,具体用户,使用的软件,使用节点数,作业状态等信息,用户可以把自己的一部分作业或所有作业委托给其他用户. 53 4.8.13 管理员工具>> CPU资源份额调整: 统计所有队列下每个用户的软份额限制,硬份额限制(软限制>整体HPC计算资源实时使用统计: 统计当前所有高性能机器集群的计算资源数目,运行状况,存储等使用情况。 54 4.8.15 管理员工具>> WEB版FTP大文件上传: 招标书中明确要求提供基于网页版的FTP上传工具,方便上传大文件。 55 4.8.16 portal用户密码校验: 为了安全保密,当用户在登陆portal的时候,连续5此输入密码错误,系统会冻结用户。直到一段时间后,方可再次登陆。 55 4.9 报表工具PBS Analytics介绍 56 4.9.1 Analytics架构 56 4.9.2 Analytics功能说明 57 4.9.3 Analytics实际案例分析 57 4.10 SAO解决方案 59 4.10.1 SAO架构 60 5 PBS支持仿真软件 61 5.1 CAE领域中支持软件分类及特点 61 5.2 CAE领域中支持软件及介绍 61 5.3 CFD领域中支持软件及介绍 63 5.4 其他领域及自研软件 64 6 和其他作业调度系统的比较优势 65 6.1 Top100超级计算中心占有率对比 65 6.2 Display Manager让用户的仿真整个过程从工作站到了云端 65 6.3 针对大规模计算中心之功能特性 67 6.4 领域知识对比(并有能力帮助HPC中心建立围绕计算的业务能力) 68 7 成功案例 68 7.1 汽车行业用户列表 68 7.2 成功案例-泛亚汽车(通用设计中心) 69 7.3 PBS在中国实施的大规模计算案例 71 7.4 PBS在全球实施的大规模计算案例 72 8 平台架构与软硬件配置建议 72 8.1 硬件平台选型常见误区 72 8.2 cpu架构的选择 73 8.3 计算网络的选择 76 8.4 存储系统的选择 79 8.5 GPU的选择 80 8.6 节点类型的选择 87 8.7 操作系统的选择 87 8.8 并行文件系统的选择 88 8.9 PBS Professional的数据流控制 89 8.10 推荐配置 90 9 硬件常见测试 92 9.1 性能峰值能力测试点 92 9.2 测试压力估算 92 9.2.1 估算结果 92 9.2.1.1. 性能峰值能力测试估算 92 9.3 测试完成准则 93 9.4 测试风险 93 9.5 测试策略 93 9.6 测试用例详细 93 9.6.1 术语 93 9.6.2 性能测试用例描述 94 9.6.2.1. 机群性能测试描述 94 10 Altair介绍 96
基于单片机的电子密码锁的设计 通信工程 2014届学生:***** 学号:******** 指导教师: *** 摘要:单片机已经在家电领域中得到了广泛的应用,而且在安全密保方面,具有防盗报 警功能的电子密码锁逐渐取代了传统的机械密码锁,克服了机械密码密码过少的安全 性问题。本密码锁的主控芯片采用价格实惠而且容易购买的 STC89C52芯片。密码输入采用矩阵键盘及独立键盘,密码显示采用共阴极的八段显示数 码管。系统能完成密码输入、正确开锁、超次报警这些基本的密码锁的功能。系统的软 件实现采用功能强大且易于开发的KeilC51环境,且支持ISP下载。因此没使用编程器, 用C语言实现系统的软件部分。由于51单片机也有一些不足之处如断电后内部RAM储存的 数据会完全丢失,为了克服这一缺点,系统的外围加了掉电存储电路并且由AT24C02芯片 来实现。此设计具有安全性高、价格低廉便于实现、易于改进等优点。 关键词 电子密码锁 STC89C52 矩阵键盘 AT24C02 目 录 1 绪 论 1 1.1 单片机及其特点 1 1.2单片机的发展及应用 1 1.2.1 单片机的发展趋势 1 1.2.2单片机的应用 2 1.3 MCS-51系列单片机 3 2系统硬件设计 5 2.1 设计思路 5 2.2系统方案的选择 5 2.2.1 电子密码锁功能简述 6 2.2.2 系统的总体设计 6 2.3单元电路的设计 7 2.3.1主控芯片介绍 7 2.3.2 键盘输入电路 9 2.3.3 密码存储电路 10 2.3.4开锁电路 11 2.3.5报警电路 12 2.3.6 显示电路 12 2.3.7串口通信电路 14 2.3.8 晶振电路 15 3 系统软件设计 17 3.1 软件工具介绍 17 3.1.1. Keil C51概述 17 3.1.2. Keil C51单片机软件开发系统的整体结构 17 3.1.3下载工具STC-ISP 18 3.2程序设计 18 4 硬件电路调试 20 4.1单元功能电路调试 20 4.2联机调试 20 5 改进方法 23 5.1 电路方面的改进 23 5.2 软件方面的改进 23 致 谢 25 附录一 程序 26 附录二 数码管显示电路 32 附录三 总设计图 33 参考文献 36 1 绪 论 2系统硬件设计 2.1 设计思路 硬件设计是整个系统的基础,要考虑的方面很多,除了实现此设计的基本功能以外, 主要还要考虑如下几个因素: 系统稳定度; 器件的通用性或易选购性; 软件编程的易实现性; 系统其它功能及性能指标;因此硬件设计至关重要。现从各功能 模块的实现逐个进行分析探讨。 2.2系统方案的选择 方案一:用以74LS112双JK触发器构成的数字逻辑电路作为密码锁的核心控制,共设 了9个用户输入键,其中只有4个是有效的密码按键,其它的都是干扰按键,若按下干扰 键,键盘输入电路自动清零,原先输入的密码无效,需要重新输入;如果用户输入密码 的时间超过10秒(一般情况下,用户不会超过10秒,若用户觉得不便,还可以修改)电 路将报警20秒,若电路连续报警三次,电路将锁定键盘2分钟,防止他人的非法操作。采 用数字电路设计的方案好处就是设计简单但控制的准确性和灵活性差,故不采用。 方案二:选用单片机STC89C52作为本设计的核心元件,利用单片机灵活的编程设计和 丰富的I/O端口,及其控制的准确性,实现基本的密码锁功能。在单片机的外围电路外接 输入键盘用于密码的输入和一些功能的控制,外接AT24C02芯片用于密码存储,外接八 段数码管用于显示作用。当用户需要开锁时,先按键盘开锁键之后按键盘的数字键0-9 输入密码密码输完后按下确认键,如果密码输入正确则开锁,不正确显示密码错误重 新输入密码,当三次密码错误则发出报警;当用户需要修改密码时,先按下键盘设置键 后可以设置新密码。新密码输入无误后按确认键使新密码将得到存储密码修改成功。 综上分析:方案一虽然设计简单但它的实用性不是很强。而方案二设计复杂,但是 安全性好,功耗低,成本低,而且容易操作保密性强。 2.2.1 电子密码锁功能简述 在本设计中用户通过键盘输入密码,确认后系统调用密码比较判断函数,如果密码在 规定的时间内输入正确,单片机输出开门信号开锁。如果在规定的时间内没完成密码的 输入,系统则自动清除所输入的密码,并提示重新输入。用户发现错误时,可以清除重 输。密码输入后,通过验证和多位校验后了,如果密码错误则启动报警系统,当密码错 误次数达到3次时,系统将锁定一定的时间,时间到后系统则启动键盘输入。初始密码由 系统设定,用户可根据自己的要求设定密码,修改密码时首先需要与旧密码匹配,如果 输入旧密码错误则系统报警并自动退出密码修改系统。旧密码正确则可以输入新密码, 新密码
摘要: 为了提高个人资料、部门文件档案的保密性和安全性,采用高速、低功耗且具备ISP、I AP、内部E^2PROM功能的STC89C52单片机,设计了保密性更高的电子密码锁,并详细介绍 STC89C52单片机内部E。PROM有关的特殊功能寄存器、读写子程序。该电子密码锁的上锁 、开锁、修改密码都在片内进行,不涉及外围芯片,减小了PCB面积,降低故障率和成本 ,提高了可靠性、保密性,值得推广。 1 课程设计题目:密码锁 利用单片机STC89C52设计一个密码锁,能够使用数码管显示器来显示密码输入的相关 信息,通过10位数字按键(0~9)设置4位数字(0~9)密码,2位功能按键A(输入校验密 码并验证密码)和B (设置新密码),利用继电器模拟电子门锁作出是否开门以及报警等反应。 具体设计内容: 上电时内定初始密码为"0000",红色发光二极管点亮,绿色发光二极管熄灭,数码管 显示器显示"初始状态","初始状态"由设计者自行设计,但不可省略。 功能按键A:实现设置新密码功能,存储密码并显示,一旦设定新密码,则初始密 码失效。 功能按键B:实现输入校验密码验证密码功能,显示校验密码并进行密码比较。 密码输入正确则继电器启动,并使红色发光二极管熄灭,绿色发光二极管点亮,数 码管显示器提示"密码正确","密码正确"状态的显示内容由设计者自行设计,但 不可省略,持续2~5S后继电器关闭,绿色发光二极管熄灭,红色发光二极管点亮 ; 密码输入错误则持续红色发光二极管点亮,绿色发光二极管熄灭状态,蜂鸣器报警 ,数码管显示器提示"密码错误","密码错误"状态的显示内容由设计者自行设计 ,但不可省略,持续2~5S后蜂鸣器停止报警; 校验密码连续输入错误3次,则持续红色发光二极管点亮,绿色发光二极管熄灭状态 ,蜂鸣器报警,数码管显示器提示"密码连续错误3次","密码连续错误3次"状态 的显示内容由设计者自行设计,但不可省略,持续2~5S后蜂鸣器停止报警,新密 码失效,恢复初始密码使用。 2 设计内容说明: 设计必须实现"主要设计内容"的所有功能,但对于"具体设计内容"可做适当调整,密 码输入错误3次环节可自行设计。 此外,为了加强密码锁的严密性,可采取下述方案对"设置新密码功能"环节进行加强 ,依据设计思路可作适当调整,此部分内容为附加内容。 功能按键A:实现输入校验密码验证密码功能,显示校验密码并进行密码比较。 密码输入正确: 则继电器启动,并使红色发光二极管熄灭,绿色发光二极管点亮,数码管显示 器提示"密码正确","密码正确"状态的显示内容由设计者自行设计,但不可省 略,若不设置新密码,则持续5~8S后继电器关闭,绿色发光二极管熄灭,红色 发光二极管点亮; 若功能按键B按下:实现设置新密码功能,存储密码并显示,一旦设定新密码 ,则初始密码失效。但此功能必须以旧密码输入正确为前提。 密码输入错误: 校验密码连续输入错误小于3次,则持续红色发光二极管点亮,绿色发光二极管 熄灭状态,蜂鸣器报警,数码管显示器提示"密码错误","密码错误"状态的显 示内容由设计者自行设计,但不可省略,持续2~5S后蜂鸣器停止报警; 校验密码连续输入错误3次,则持续红色发光二极管点亮,绿色发光二极管熄灭 状态,蜂鸣器报警,数码管显示器提示"密码连续错误3次","密码连续错误3 次"状态的显示内容由设计者自行设计,但不可省略,持续2~5S后蜂鸣器停止 报警,新密码失效,恢复初始密码使用。 3 系统方案设计及确定 3.1 系统方案的提出 本次课程设计的密码锁电路主要由四个模块组成:键盘输入模块、数据处理模块、显 示控制模块,继电器驱动模块和蜂鸣器报警模块。 方案一:以单片机为电子密码锁系统核心,使用4*4矩阵键盘作为数据输入方式,驱 动4位数码管显示器提示程序运行过程和开锁的步骤,利用继电器及蜂鸣器模拟电子门锁 作出是否开门以及报警等反应。图1为单片机控制密码锁的系统原理框图。 图1 单片机控制密码锁的系统原理框图 方案二:以74LS112双JK触发器构成的数字逻辑电路控制方案,如图2。 3.2 方案比较及确定 由于利用单片机灵活的编程设计和强大的I/O端口,及其控制的准确性,不但能实现 基本的密码锁功能,还可以增添掉电存储、声光提示等功能,故选用方案一。 CPU的选择 STC89C52系列单片机,高速、低功耗、新增在系统/在应用可编程(ISP,IAP)功能,使 不具有E2PROM的单片机具有了E2PROM的功能,可以在线对现场历史数据的存储功能,适用 于一些需经常改变数据的应用产品(如计费器、门禁系统等)及需远距离改变设备参数的 产品(遥控设备等)。采用STC89C52单片机设计的电子密码锁,利用内部E2PROM资源,不需 要外接程序存储器就能完成修改密码等多种功能,并

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值