pymysql 和 #phone#与替换

这篇博客详细介绍了Python中的文件路径配置,包括可移植性的目录结构。此外,讲解了如何使用pymysql连接MySQL数据库,执行SQL操作并封装数据库连接。还展示了通过faker生成手机号码,并用于自动化测试注册接口,确保手机号码未被注册。最后,给出了测试用例的处理方法,包括占位符替换和断言验证。

文件路径配置

– 框架结构当中,不同的目录路径配置。可移植

import os

# 1、basedir
basedir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# 拼到配置文件路径
conf_dir = os.path.join(basedir, "Conf")

# 拼接  测试数据路径
testdata_dir = os.path.join(basedir, "testdatas")

# 日志路径
log_dir = os.path.join(basedir, "outputs", "logs")

# 报告路径
report_dir = os.path.join(basedir, "outputs", "reports")

==================

pymysql 的 使用

一、python与各大数据库的连接:
http://testingpai.com/article/1596527686073

二、pymysql 安装
pip install pymysql

三、pymysql 包引入
import pymysql

1、连接数据库
数据库ip地址/域名
数据库名
用户名和密码
端口:mysql 3306

import pymysql

# 1、连接mysql数据库 - 占用数据库资源
db = pymysql.connect(
    host="api.lemonban.com",
    user="future",
    password="123456",
    port=3306,
    database="futureloan",
    charset="utf8",
    cursorclass=pymysql.cursors.DictCursor
)

cur = db.cursor()  	# 2、创建游标

# 3、执行sql语句
sql = "select id from member where mobile_phone='13350171234'"
affected_rows = cur.execute(sql)	# 返回的是affected_rows表示执行后的结果 条数

# 4、获取查询的结果
# data = cur.fetchone()		# 获取第一个结果。返回是一个字典。
# cur.fetchmany(size=2) 	# 获取前2行

# 获取所有的结果。返回的是一个列表。
data = cur.fetchall()
print(data)

# 5、关闭游标、关闭数据库连接
cur.close()			#	关闭游标
db.close()			#	关闭数据库连接
 

================================

封装 pymysql

– 封装数据库连接,数据库操作,关闭。 数据库连接参数配置在Conf目录下

import pymysql
import os

from common.myConf import MyConf
from common.my_path import conf_dir

class MyMysql:

    def __init__(self):
    
        # 实例化配置类对象
        conf = MyConf(os.path.join(conf_dir, "mysql.ini"))
        
        # 连接数据库/生成游标
        self.db = pymysql.connect(
            host=conf.get("mysql", "host"),
            user=conf.get("mysql", "user"),
            password=conf.get("mysql", "passwd"),
            port=conf.getint("mysql", "port"),
            database=conf.get("mysql", "database"),
            charset="utf8",
            cursorclass=pymysql.cursors.DictCursor
        )

        # 2、创建游标
        self.cur = self.db.cursor()

    def get_count(self,sql):
    """ 获取 sql 的条数, 表示执行后的结果:条数 """
        count = self.cur.execute(sql)
        return count

    def get_one_data(self,sql):
    """ 获取第一个结果。返回是一个字典。 """
        self.cur.execute(sql)
        return self.cur.fetchone()

    def get_many_data(self,sql, size=None):
    
        self.cur.execute(sql)
        if size:
         """ 获取多少行,可以指定获取多少行。 """
            return self.cur.fetchmany(size)
        else:
         """ 获取所有的结果 """
            return self.cur.fetchall()

    def close_conn(self):
    """ 关闭游标、关闭数据库连接 """
        self.cur.close()
        self.db.close()

""" 调式代码:执行当前的模块。"""
if __name__ == '__main__':
    conn = My_mysql()
    sql = "select * from member where mobile_phone='13560745669';"
    count = conn.get_count(sql)
    print(count)
    conn.close_conn()

=================================================

获取手机号码处理

– 手机号码生成。faker生成,用mysql去确认是否已注册。

from faker import Faker
from common.mysql_handler import MysqlHandler


def get_new_phone():
    """
    # 得到没有注册过的手机号码。
    # 1、使用faker生成手机号码
    # 2、调用mysql数据库操作,去判断是否在数据中存在。如果不在,表示没有注册
    :return:
    """
    while True:
        phone = Faker("zh_CN").phone_number()
        sql = "select id from member where mobile_phone='{}';".format(phone)
        res = MysqlHandler().get_count(sql)
        if res == 0:
            return phone
            

===========================================================

#phone# 占位符替换

如 {“mobile_phone”: “#phone#”,“pwd”: “wtg444444”, “reg_name”: “白糖”,“tepy”:0}

import os
import json
import pytest
from common.py_log import LoggerHandler
from common.my_assert import MyAssert
from common.My_excel import MyExcel
from common.my_requests import MyRequests
from common.handele_phone import get_new_phone
from common.my_path import testdata_dir

log_info = LoggerHandler()
# 第一步:读取注册接口的测试数据 - 是个列表,列表中的每个成员,都是一个接口用例的数据。
excel_path = os.path.join(testdata_dir,"注册接口用例.xlsx")
me = MyExcel(excel_path, "注册")
cases = me.read_data()

# 第二步:遍历测试数据,每一组数据,发起一个http的接口请求
# 实例化请求对象
mq = MyRequests()
massert = MyAssert()

class TestRegister:

    @pytest.mark.parametrize("case", cases)
    def test_regiser(self,case):
        logger.info("=========== 注册接口测试  ===============")
       new_phone = get_new_phone()		# 设置属性,自动生成手机号码。
       
        if case["req_data"] and case["req_data"].find('#phone#') != -1:		# 如果 case["req_data"] 和 case["req_data"] .里面的(#手机号码#) != -1:  (-1)在这里是没有。
            # 替换掉 req_data 里面的占位符 - 请求数据和断言里面替换掉#phone#,替换成未注册手机号码
            logger.info("新生成的手机号码是:{}".format(new_phone))
            case["req_data"] = case["req_data"].replace('#phone#', new_phone)		# case["req_data"] = case["req_data"].替换掉(#手机号码#,new_phone:新生成的手机号码)
			
        # 替换掉占位符 - 请求数据和断言里面替换掉#phone#,替换成未注册手机号码
        if case["assert_list"] and case["assert_list"].find('#phone#') != -1:		# 如果 case["assert_list"] 和 case["assert_list"].里面的(#手机号码#) != -1:  (-1)在这里是没有。
            case["assert_list"] = case["assert_list"].replace('#phone#', new_phone)		# case["assert_list"] = case["assert_list"].替换成(#手机号码#, new_phone:新生成的手机号码)

        # 把json格式的字符串,转换成一个字典
        req_dict = json.loads(case["req_data"])
        
        resp = mq.send_requests(case["url"], case["method"], req_dict)
        # print(resp.json())
        # 要断言
        if case["assert_list"]:
            massert.assert_response_value(case["assert_list"], resp.json())



import pyodbc import pandas as pd from sqlalchemy import create_engine, text, VARCHAR from urllib.parse import quote_plus import uuid import numpy as np from tqdm import tqdm import time import re import traceback def get_table_columns(engine, table_name): with engine.connect() as conn: result = conn.execute(text(f"SHOW COLUMNS FROM {table_name}")) columns = [row[0] for row in result] return columns def safe_get_column_name(columns, possible_names): lower_columns = [col.lower() for col in columns] for name in possible_names: if name in columns: return name if name.lower() in lower_columns: return columns[lower_columns.index(name.lower())] for col in columns: if any(name in col for name in possible_names): return col return None def compare_records(row, existing_row, fields_to_compare): changes = {} consistent_fields = 0 total_fields = 0 for field in fields_to_compare: total_fields += 1 # 处理可能的NaN值 new_val = row[field] if pd.notna(row[field]) else None old_val = existing_row[field] if field in existing_row and pd.notna(existing_row[field]) else None # 比较值是否不同 if new_val != old_val: changes[field] = new_val else: consistent_fields += 1 return changes, consistent_fields, total_fields def generate_unique_guids(count): guids = set() while len(guids) < count: guids.add(str(uuid.uuid4()).lower()) return list(guids) def main(): print("=" * 80) print("数据同步程序启动") print(f"开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") print("=" * 80) # 1. 安全处理包含特殊字符的密码 password = "@Aa.1234" encoded_password = quote_plus(password) # 对特殊字符进行URL编码 # 2. 创建MySQL连接字符串 mysql_conn_str = ( f"mysql+pymysql://lpsoft:{encoded_password}@192.168.3.1:3306/OrderManage" "?charset=utf8mb4" ) # 3. 创建SQLAlchemy引擎 mysql_engine = create_engine( mysql_conn_str, pool_size=5, max_overflow=10, pool_timeout=30, connect_args={'connect_timeout': 15} ) # 4. 测试MySQL连接 try: with mysql_engine.connect() as test_conn: test_conn.execute(text("SELECT 1")) print("✅ MySQL连接测试成功") except Exception as e: print(f"❌ MySQL连接失败: {str(e)}") return # 5. 获取MySQL表结构 print("正在获取MySQL表结构...") try: table_columns = get_table_columns(mysql_engine, "T_Customer") print(f"表字段: {', '.join(table_columns)}") # 安全获取电话字段名 phone_field = safe_get_column_name(table_columns, ["phone", "Phone", "telephone", "tel"]) if not phone_field: raise ValueError("找不到电话字段") print(f"✅ 确定电话字段: {phone_field}") except Exception as e: print(f"❌ 表结构获取失败: {str(e)}") return # 6. 连接SQL Server (SAP数据) try: sap_conn = pyodbc.connect( 'DRIVER={ODBC Driver 17 for SQL Server};' 'SERVER=192.168.0.229;' 'DATABASE=SINO_SAP;' 'UID=SAPReader;' 'PWD=Sino2025zyq;' ) print("✅ SQL Server连接成功") except Exception as e: print(f"❌ SQL Server连接失败: {str(e)}") return # 7. 从SQL Server获取SAP数据(使用新的查询) sap_query = """ SELECT CAST(111100000048210 + ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS bigint) AS Id, CAST(LOWER(NEWID()) AS varchar(50)) AS GUID, CAST('ipcc.org' AS varchar(50)) AS Domain, CAST( CASE WHEN ZY_TB_CustomerProfile.[客户类型] = 'VIP' THEN '703192237850693' ELSE '703192237846597' END AS varchar(20)) AS CustomerTypeCode, CAST('' AS text) AS Remark, CAST(ZY_TB_CustomerPhone.CardCode AS varchar(50)) AS S6, CAST(ZY_TB_CustomerPhone.Cardname AS varchar(100)) AS CompanyName, CAST(ZY_TB_CustomerPhone.Name AS varchar(50)) AS Name, CAST(LEFT(ZY_TB_CustomerPhone.Telephone, 15) AS varchar(15)) AS Phone, CAST(FORMAT(ZY_TB_CustomerProfile.近一年总毛利, 'N0') AS varchar(50)) AS S4, CAST(FORMAT(ZY_TB_CustomerProfile.预收款金额, 'N0') AS varchar(50)) AS S2, CAST(FORMAT(ZY_TB_CustomerProfile.应收款, 'N0') AS varchar(50)) AS S1, CAST(FORMAT(ZY_TB_CustomerProfile.全部库存金额, 'N0') AS varchar(50)) AS S3, CAST((CAST(ZY_TB_CustomerProfile.客户等级 AS varchar(50)) + ZY_TB_CustomerProfile.等级名称) AS varchar(50)) AS S5 FROM ZY_TB_CustomerPhone LEFT JOIN ZY_TB_CustomerProfile ON ZY_TB_CustomerPhone.CardCode = ZY_TB_CustomerProfile.[客户编号] WHERE ZY_TB_CustomerPhone.CardCode IS NOT NULL AND ZY_TB_CustomerPhone.Cardname IS NOT NULL AND ZY_TB_CustomerPhone.Telephone NOT LIKE '8441%' AND ZY_TB_CustomerPhone.Cardname NOT LIKE '%中源合聚生物%' AND ZY_TB_CustomerPhone.CardCode <> '' AND ZY_TB_CustomerPhone.Cardname <> '' AND ZY_TB_CustomerPhone.Telephone <> '15301373560' """ print("正在从SAP数据库加载数据...") start_time = time.time() try: df_sap = pd.read_sql(sap_query, sap_conn) # 确保电话字段格式正确 df_sap['Phone'] = df_sap['Phone'].astype(str).str.slice(0, 15) sap_duration = time.time() - start_time print(f"✅ 从SAP加载了 {len(df_sap)} 条记录 (耗时: {sap_duration:.2f}秒)") except Exception as e: print(f"❌ SAP数据加载失败: {str(e)}") return # 8. 从MySQL获取现有客户数据(使用动态字段名) print("正在从MySQL加载现有客户数据...") start_time = time.time() try: # 动态生成查询 query = f"SELECT * FROM T_Customer" df_existing = pd.read_sql(query, mysql_engine) # 确保电话字段存在 if phone_field not in df_existing.columns: raise ValueError(f"电话字段 '{phone_field}' 在查询结果中不存在") # 处理电话字段 df_existing['Phone'] = df_existing[phone_field].astype(str).str.slice(0, 15) # 创建以Phone为键的字典,便于快速查找 existing_dict = df_existing.set_index('Phone').to_dict('index') mysql_duration = time.time() - start_time print(f"✅ MySQL现有 {len(df_existing)} 条记录 (耗时: {mysql_duration:.2f}秒)") except Exception as e: print(f"❌ MySQL数据加载失败: {str(e)}") print("请检查表结构字段名") return # 9. 定义需要比较更新的字段(已添加CompanyName) fields_to_update = [ 'S6', 'CustomerTypeCode', 'S5', 'S1', 'S2', 'S3', 'S4', 'CompanyName' # 新增字段 ] # 10. 数据对比分析 print("\n" + "=" * 80) print("开始数据对比分析...") print(f"比较字段: {', '.join(fields_to_update)}") # 初始化统计变量 stats = { 'total_records': len(df_sap), 'existing_records': 0, 'new_records': 0, 'consistent_records': 0, 'inconsistent_records': 0, 'total_fields_compared': 0, 'consistent_fields': 0, 'inconsistent_fields': 0, 'max_consistent_fields': 0, 'min_consistent_fields': len(fields_to_update), # 初始设为最大字段数 'update_candidates': 0 } # 存储需要更新插入的记录 updates_to_apply = [] # 存储需要更新的记录变化字段 inserts_to_apply = [] # 存储需要插入的新记录(仅业务字段) # 进度条显示 pbar = tqdm(total=len(df_sap), desc="分析记录") # 11. 遍历所有SAP记录 for idx, row in df_sap.iterrows(): phone = row['Phone'] # 检查电话是否存在于现有数据中 if phone in existing_dict and phone: # 确保phone不为空 stats['existing_records'] += 1 existing_row = existing_dict[phone] # 比较记录并获取变化 - 只比较需要更新的字段 changes, consistent_fields, total_fields = compare_records(row, existing_row, fields_to_update) stats['total_fields_compared'] += total_fields stats['consistent_fields'] += consistent_fields stats['inconsistent_fields'] += (total_fields - consistent_fields) # 更新最大/最小一致字段数 stats['max_consistent_fields'] = max(stats['max_consistent_fields'], consistent_fields) stats['min_consistent_fields'] = min(stats['min_consistent_fields'], consistent_fields) # 检查是否完全一致 if len(changes) == 0: stats['consistent_records'] += 1 else: stats['inconsistent_records'] += 1 stats['update_candidates'] += 1 changes['Phone'] = phone # 添加标识符 updates_to_apply.append(changes) else: # 新记录 - 只保存业务字段 stats['new_records'] += 1 new_record = { 'CustomerTypeCode': row['CustomerTypeCode'], 'S6': row['S6'], 'CompanyName': row['CompanyName'], 'Name': row['Name'], 'Phone': row['Phone'], 'S4': row['S4'], 'S2': row['S2'], 'S1': row['S1'], 'S3': row['S3'], 'S5': row['S5'] } inserts_to_apply.append(new_record) pbar.update(1) pbar.close() # 12. 打印详细统计信息 print("\n" + "=" * 80) print("数据对比统计结果:") print(f"总记录数: {stats['total_records']}") print(f"已存在记录数: {stats['existing_records']} ({stats['existing_records'] / stats['total_records']:.1%})") print(f"新增记录数: {stats['new_records']} ({stats['new_records'] / stats['total_records']:.1%})") print(f"一致记录数: {stats['consistent_records']} ({stats['consistent_records'] / stats['existing_records']:.1%})") print( f"不一致记录数: {stats['inconsistent_records']} ({stats['inconsistent_records'] / stats['existing_records']:.1%})") print(f"需要更新记录数: {stats['update_candidates']}") print(f"需要插入记录数: {stats['new_records']}") print(f"\n字段级别对比:") print(f"总比较字段数: {stats['total_fields_compared']}") print( f"一致字段数: {stats['consistent_fields']} ({stats['consistent_fields'] / stats['total_fields_compared']:.1%})") print( f"不一致字段数: {stats['inconsistent_fields']} ({stats['inconsistent_fields'] / stats['total_fields_compared']:.1%})") print(f"字段一致性范围: {stats['min_consistent_fields']}-{stats['max_consistent_fields']} (每记录一致字段数)") print("=" * 80 + "\n") # 13. 执行批量更新操作(包含CompanyName字段) if updates_to_apply: print(f"开始批量更新 {len(updates_to_apply)} 条有变化的记录...") start_time = time.time() try: # 创建包含变化的DataFrame df_changes = pd.DataFrame(updates_to_apply) # 打印更新示例 if not df_changes.empty: print(f"示例更新记录: 手机号={df_changes.iloc[0]['Phone']}, 变化字段数={len(df_changes.columns) - 1}") # 创建临时表存储更新数据 temp_table_name = "temp_customer_updates" with mysql_engine.begin() as conn: # 先删除可能存在的旧临时表 conn.execute(text(f"DROP TABLE IF EXISTS {temp_table_name}")) # 定义列类型映射(包含CompanyName) dtype = { 'Phone': VARCHAR(15), # 指定固定长度 'S6': VARCHAR(50), 'CustomerTypeCode': VARCHAR(20), 'S5': VARCHAR(50), 'S1': VARCHAR(50), 'S2': VARCHAR(50), 'S3': VARCHAR(50), 'S4': VARCHAR(50), 'CompanyName': VARCHAR(100) # 新增字段类型 } # 创建临时表(只包含变化的字段)并指定列类型 df_changes.to_sql( temp_table_name, mysql_engine, if_exists='replace', index=False, dtype=dtype ) # 添加前缀索引加速更新 with mysql_engine.begin() as conn: # 创建前缀索引 (Phone(15)) conn.execute(text(f"CREATE INDEX idx_phone ON {temp_table_name} (Phone(15))")) # 动态生成SET子句 - 只更新fields_to_update中的字段 set_clauses = [] for col in df_changes.columns: if col != 'Phone': # 跳过标识符字段 set_clauses.append(f"t.{col} = tmp.{col}") set_clause = ", ".join(set_clauses) # 使用JOIN执行批量更新 update_query = f""" UPDATE T_Customer AS t JOIN {temp_table_name} AS tmp ON LEFT(t.{phone_field}, 15) = tmp.Phone SET {set_clause} """ with mysql_engine.begin() as conn: result = conn.execute(text(update_query)) update_duration = time.time() - start_time print(f"✅ 成功更新 {result.rowcount} 条记录 (耗时: {update_duration:.2f}秒)") except Exception as e: print(f"❌ 更新操作失败: {str(e)}") # 记录错误详情 error_file = f"update_error_{time.strftime('%Y%m%d_%H%M%S')}.log" with open(error_file, 'w') as f: f.write(f"错误时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") f.write(f"错误信息: {str(e)}\n") f.write("部分更新数据:\n") if 'df_changes' in locals(): f.write(df_changes.head().to_string()) print(f"错误详情已保存到: {error_file}") traceback.print_exc() # 14. 执行批量插入操作(包含CompanyName字段) if inserts_to_apply: print(f"开始准备插入 {len(inserts_to_apply)} 条新记录...") start_time = time.time() try: # 准备插入数据 df_insert = pd.DataFrame(inserts_to_apply) print("正在对即将插入的数据进行去重...") df_insert = df_insert.drop_duplicates(subset=[phone_field], keep='first') print(f"✅ 去重完成,剩余 {len(df_insert)} 条新记录") # 获取当前最大ID max_id_query = "SELECT COALESCE(MAX(id), 111100000048210) AS max_id FROM T_Customer" with mysql_engine.connect() as conn: max_id = conn.execute(text(max_id_query)).scalar() # 生成新ID(从最大ID+1开始) new_ids = range(max_id + 1, max_id + 1 + len(df_insert)) # 生成唯一的小写GUID(确保在集合中唯一) new_guids = generate_unique_guids(len(df_insert)) # 添加呼叫中心系统生成的IDGUID df_insert['id'] = list(new_ids) df_insert['GUID'] = new_guids df_insert['Domain'] = 'ipcc.org' df_insert['Remark'] = '' # 选择并排序列以匹配表结构(包含CompanyName) insert_columns = [ 'id', 'GUID', 'Domain', 'CustomerTypeCode', 'Remark', 'S6', 'CompanyName', 'Name', 'Phone', 'S4', 'S2', 'S1', 'S3', 'S5' ] df_insert = df_insert[insert_columns] # 重命名列以匹配实际表结构 column_mapping = {'Phone': phone_field} df_insert = df_insert.rename(columns=column_mapping) # 定义插入列类型(包含CompanyName) insert_dtype = { phone_field: VARCHAR(15), # 指定电话列类型 'S6': VARCHAR(50), 'CustomerTypeCode': VARCHAR(20), 'S5': VARCHAR(50), 'S1': VARCHAR(50), 'S2': VARCHAR(50), 'S3': VARCHAR(50), 'S4': VARCHAR(50), 'CompanyName': VARCHAR(100), # 新增字段类型 'Name': VARCHAR(50) } # 批量插入新记录(分块处理) print(f"正在批量插入 {len(df_insert)} 条新记录...") df_insert.to_sql( 'T_Customer', mysql_engine, if_exists='append', index=False, chunksize=2000, # 分块插入提高性能 method='multi', # 使用多值插入 dtype=insert_dtype ) insert_duration = time.time() - start_time print(f"✅ 成功插入 {len(df_insert)} 条新记录 (耗时: {insert_duration:.2f}秒)") # 验证IDGUID唯一性 id_check_query = "SELECT COUNT(*) AS total, COUNT(DISTINCT id) AS distinct_ids FROM T_Customer" guid_check_query = "SELECT COUNT(*) AS total, COUNT(DISTINCT GUID) AS distinct_guids FROM T_Customer" with mysql_engine.connect() as conn: # 验证ID唯一性 id_check = conn.execute(text(id_check_query)).fetchone() if id_check[0] == id_check[1]: print("✅ ID唯一性验证通过") else: print(f"❌ ID唯一性警告: 总数={id_check[0]}, 唯一数={id_check[1]}") # 验证GUID唯一性 guid_check = conn.execute(text(guid_check_query)).fetchone() if guid_check[0] == guid_check[1]: print("✅ GUID唯一性验证通过") else: print(f"❌ GUID唯一性警告: 总数={guid_check[0]}, 唯一数={guid_check[1]}") except Exception as e: print(f"❌ 插入操作失败: {str(e)}") # 记录错误详情 error_file = f"insert_error_{time.strftime('%Y%m%d_%H%M%S')}.log" with open(error_file, 'w') as f: f.write(f"错误时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") f.write(f"错误信息: {str(e)}\n") f.write("部分插入数据:\n") if 'df_insert' in locals(): f.write(df_insert.head().to_string()) print(f"错误详情已保存到: {error_file}") traceback.print_exc() # 15. 清理临时表 try: with mysql_engine.begin() as conn: conn.execute(text(f"DROP TABLE IF EXISTS {temp_table_name}")) print("✅ 临时表清理完成") except: print("⚠️ 临时表清理失败,请手动检查") # 16. 最终统计 print("\n" + "=" * 80) print("数据同步完成!") print(f"总耗时: {time.time() - start_time:.2f}秒") print(f"处理记录统计:") print(f" 一致记录: {stats['consistent_records']}") print(f" 更新记录: {len(updates_to_apply)}") print(f" 新增记录: {len(inserts_to_apply)}") print(f" 总处理记录: {stats['consistent_records'] + len(updates_to_apply) + len(inserts_to_apply)}") print("=" * 80) if __name__ == "__main__": main() 把每一句代码都加上备注
08-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值