HBase常用服务调用封装

本文主要介绍了在生产环境中,针对HBase的数据获取操作,如get、scan和scan range,进行的服务封装,旨在提升数据访问效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言


      生产刚刚接入HBase,应用对其数据的获取的几种方式如get,scan,scan range进行了相关服务封装


二、服务封装


   

package com.hbase.sources;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.RegexStringComparator;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.util.Bytes;
import java.util.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import com.hbase.utils.StringUtilsx;

/**
 * 
 * 类名: HbaseService.java 
 * 描述:Hbase数据获取操作类
 * @author tanjie 
 * 创建时间: 2017年4月12日 上午10:44:37
 * @version V1.0.0
 */
@Service("hbaseSource")
public class HBaseSource {
	
	private final Logger LOGGER = LoggerFactory.getLogger(HBaseSource.class);

	/**
	 * HbaseSource连接池
	 */
	private static Map<String, HBaseSource> hbasepool = new ConcurrentHashMap<String, HBaseSource>();

	/**
	 * HbaseSource实例创建时间池
	 */
	private static Map<String,Date> createTimes = new ConcurrentHashMap<String, Date>();
	
	/**
	 * HbaseSource实例访问时间池
	 */
	private static Map<String,Date> lastTimes = new ConcurrentHashMap<String,Date>();
	
	/**
	 *  hbase连接的配置属性
	 */
	private Configuration conf;

	/** 
	 * hbase的连接
	 */
	private Connection connection;
	
	
	/**
	 * 创建时间多长就清除连接
	 */
	private long intervalsTime = 4 * 1000 * 60 * 60;
	
	/**
	 * 多长时间没调用就清除连接
	 */
//	private long instanceTime = 2*60*1000;
	
	
	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午10:56:28
	 * 描述: 定时销毁创建时间过久或空闲时间过长的连接
	 */
	@Scheduled(cron ="0 0/5 * * * ?")
	public void clearHbaseConnection(){
		LOGGER.info("定时任务开始工作!");
		LOGGER.info("clear hbase connection hbasepool :" + hbasepool.size());
		LOGGER.info("clear hbase connection createTimes :" + createTimes.size());
		LOGGER.info("clear hbase connection lastTimes :" + lastTimes.size());
	    Date currentDate = new Date();
	    if(null != createTimes && createTimes.size() !=0){
	    	for(final Map.Entry<String, Date> ctime : createTimes.entrySet()){
	    		if(null != ctime.getValue() && currentDate.getTime()-ctime.getValue().getTime()>=intervalsTime){
	    			LOGGER.info("clear hbase connection 创建时间太长被清理 :" + ctime.getKey());
	    		    hbasepool.get(ctime).closeConnection();
	    		    hbasepool.remove(ctime);
	    		    createTimes.remove(ctime.getKey());
	    		    lastTimes.remove(ctime.getKey());
	    		}
	    	}
	    }
	    if(null != lastTimes && lastTimes.size()!=0){
			for(final Map.Entry<String,Date> m:lastTimes.entrySet()){
				if(m.getValue()!=null&& currentDate.getTime()-m.getValue().getTime()>=1000*60*20){
					LOGGER.info("clear hbase connection 调用时间间隔太长被清理 :" + m.getKey());
					hbasepool.get(m.getKey()).closeConnection();
					hbasepool.remove(m.getKey());
					createTimes.remove(m.getKey());
					lastTimes.remove(m.getKey());
				}
			}
		}
	}

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午9:11:54
	 * 描述: 创建表
	 * @param hbaseTableName 表名
	 * @param columnFamily 列族民
	 * @throws IOException
	 */
	public void createHbaseTable(String hbaseTableName, String columnFamily)
			throws IOException {
		if(null == connection){
			return;
		}
		Admin admin = connection.getAdmin();
		TableName tableName = TableName.valueOf(hbaseTableName);
		if (admin.tableExists(tableName)) {
			return;
		}
		HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
		HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(
				columnFamily);
		hTableDescriptor.addFamily(hColumnDescriptor);
		admin.createTable(hTableDescriptor);
		boolean avail = admin.isTableAvailable(tableName);
		LOGGER.info("创建的表是否可用:" + avail);
		admin.close();
	}
	
	
	/**
	 * 通过表名和rowkey查询所需数据
	 * @param tableName hbase的表名
	 * @param keyData hbase rowkey的List集合
	 * @return Map<String, Map<String, byte[]>>  
	 * 外层的Map key值是Rowkey的值   内层map key值是列族:列   value是该列对应的值
	 * @throws HasNotHbasePremission 
	 * @throws IOException 
	 * @exception  java.io.Exception
	 */
	public  Map<String, Map<String, byte[]>> searchData(String tableName,
			                                            List<String> keyData) throws IOException{
		Map<String, Map<String,byte[]>> re = new HashMap<String, Map<String,byte[]>>();
		Table table=connection.getTable(TableName.valueOf(tableName));
		List<Get> list = new ArrayList<Get>();
		for(String strKey : keyData){
			Get get = new Get(strKey.getBytes());
			list.add(get);
		}
		Result[] rs=table.get(list);
		if(rs==null){
			return re;
		}
		for(Result r:rs){
			Map<String,byte[]> tmpmap = new HashMap<String,byte[]>();
			if(r!=null&&r.getRow()!=null){
				String rowkey = new String(r.getRow());
				Cell[] cells=r.rawCells();
				for(Cell cell: cells){
					tmpmap.put(new String(CellUtil.cloneFamily(cell)) +":" + 
							   new String(CellUtil.cloneQualifier(cell)),
							              CellUtil.cloneValue(cell));
				}
				re.put(rowkey, tmpmap);
			}
		}
		table.close();
		return re;
	}
	

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午9:56:35
	 * 描述: 根据表名,rowkey模拟查询数据 
	 * 根据rowkey模糊匹配查询数据 Map<String,Map<String,byte[]>>
	 * @param tableName 表名
	 * @param rowkey 表对应的rowkey
	 * @return  Map<String,Map<String,byte[]>> 外层Map key是rowkey,里层Map key是列,value是具体数据
	 */
	public Map<String, Map<String, byte[]>> searchData(String tableName,
			String rowkey) {
		if(null == connection){
			return null;
		}
		HashMap<String, Map<String, byte[]>> result = new HashMap<String, Map<String, byte[]>>();
		Table table = null;
		ResultScanner rs = null;
		try {
			table = connection.getTable(TableName.valueOf(tableName));
			Scan scan = new Scan();
			if (StringUtilsx.isNotEmpty(rowkey)) {
				Filter filter = new RowFilter(CompareOp.EQUAL,
						new RegexStringComparator(rowkey));
				scan.setFilter(filter);
			}
			rs = table.getScanner(scan);
			for (final Result r : rs) {
				String keyData = Bytes.toString(r.getRow());
				Cell[] cells = r.rawCells();
				Map<String, byte[]> tmpMap = new HashMap<String, byte[]>();
				for (final Cell cell : cells) {
					tmpMap.put(new String(CellUtil.cloneFamily(cell)) + ":" +
				               new String(CellUtil.cloneQualifier(cell)),
				                CellUtil.cloneValue(cell));
				}
				result.put(keyData, tmpMap);
			}
		} catch (IOException e) {
			LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";rowkey:" + rowkey
					+ ";异常:"+ e.getMessage(),e);
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";rowkey:" + rowkey
							+ ";异常:"+ e.getMessage(),e);
				}
			}
			if(null != rs){
				rs.close();
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年6月1日 下午4:20:16
	 * 描述: 根据表名,rowkey查询单个具体的信息
	 * @param tableName 表名
	 * @param rowkey rowkey
	 * @return Result Result对象
	 */
	public Result searchDataByGet(String tableName,
			String rowkey) {
		if(null == connection){
			return null;
		}
		Table table = null;
		try {
			table = connection.getTable(TableName.valueOf(tableName));
			Get get = new Get(rowkey.getBytes());
			return table.get(get);
		} catch (IOException e) {
			LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";rowkey:" + rowkey
					+ ";异常:"+ e.getMessage(),e);
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";rowkey:" + rowkey
							+ ";异常:"+ e.getMessage(),e);
				}
			}
		}
		return null;
	}

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午10:09:33
	 * 描述: 查询某张表从startRowKey到entRowkey区间的数据
	 * @param tableName 表名
	 * @param startRowKey 开始rowkey
	 * @param endRowKey 结束rowkey
	 * @return Map<String,Map<String,byte[]>> 外层Map key是rowkey,里层Map key是列,value是具体数据
	 */
	public Map<String, Map<String, byte[]>> searchData(String tableName,
			String startRowKey, String stopRowKey) {
		if(null == connection){
			return null;
		}
		HashMap<String, Map<String, byte[]>> result = new HashMap<String, Map<String, byte[]>>();
		Table table = null;
		ResultScanner rs = null;
		try {
			table = connection.getTable(TableName.valueOf(tableName));
			Scan scan = new Scan();
			if (StringUtilsx.isNotEmpty(startRowKey)) {
				scan.setStartRow(Bytes.toBytes(startRowKey));
			}
			if (StringUtilsx.isNotEmpty(stopRowKey)) {
				scan.setStopRow(Bytes.toBytes(stopRowKey));
			}
			rs = table.getScanner(scan);
			for (final Result r : rs) {
				String keyData = new String(r.getRow());
				Cell[] cells = r.rawCells();
				Map<String, byte[]> tmpMap = new HashMap<String, byte[]>();
				for (final Cell cell : cells) {
					tmpMap.put(new String(CellUtil.cloneFamily(cell)) + ":" +
				               new String(CellUtil.cloneQualifier(cell)),
				                CellUtil.cloneValue(cell));
				}
				result.put(keyData, tmpMap);
			}
		} catch (IOException e) {
			LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";startRowkey:" + startRowKey
					+";stopRowKey:" + stopRowKey + ";异常:"+ e.getMessage(),e);
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";startRowkey:" + startRowKey
							+";stopRowKey:" + stopRowKey + ";异常:"+ e.getMessage(),e);
				}
			}
			if(null != rs){
				rs.close();
			}
		}
		return result;
	}

	/**
	 * 
	 * @author tanjie 
	 * 创建时间: 2017年4月13日 下午8:18:35 
	 * 描述: 查询打标数据表
	 * @param tableName 表名
	 * @param startRowKey 开始rowkey
	 * @param stopRowKey 结束rowkey
	 * @param keyData1 rowkey包含的数据1
	 * @param keyData2  rowkey包含的数据2
	 * @return Map<String,Map<String,byte[]>> 外层Map key是rowkey,里层Map key是列,value是具体数据
	 */
	public Map<String, Map<String, byte[]>> searchData(String tableName,
			String startRowKey, String stopRowKey, String keyData1,
			String keyData2) {
		if(null == connection){
			return null;
		}
		HashMap<String, Map<String, byte[]>> result = new HashMap<String, Map<String, byte[]>>();
		Table table = null;
		ResultScanner rs = null;
		try {
			table = connection.getTable(TableName.valueOf(tableName));
			Scan scan = new Scan();
			if (StringUtilsx.isNotEmpty(startRowKey)) {
				scan.setStartRow(Bytes.toBytes(startRowKey));
			}
			if (StringUtilsx.isNotEmpty(stopRowKey)) {
				scan.setStopRow(Bytes.toBytes(stopRowKey));
			}
			rs = table.getScanner(scan);
			for (final Result r : rs) {
				String rowKeyData = new String(r.getRow());
				if (StringUtilsx.isNotEmpty(rowKeyData)) {
					if ((StringUtilsx.isNotEmpty(keyData1) && rowKeyData
							.contains(keyData1))
							|| (StringUtilsx.isNotEmpty(keyData2) && rowKeyData
									.contains(keyData2))) {
						Cell[] cells = r.rawCells();
						Map<String, byte[]> tmpMap = new HashMap<String, byte[]>();
						for (final Cell cell : cells) {
							tmpMap.put(new String(CellUtil.cloneFamily(cell)) + ":" +
						               new String(CellUtil.cloneQualifier(cell)),
						                CellUtil.cloneValue(cell));
						}
						result.put(rowKeyData, tmpMap);
					}
				}
			}
		} catch (IOException e) {
			LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";startRowkey:" + startRowKey
					+";stopRowKey:" + stopRowKey + ";keyData1:" + keyData1 + ";keyData2:" + keyData2+  ";异常:"+ e.getMessage(),e);
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";startRowkey:" + startRowKey
							+";stopRowKey:" + stopRowKey + ";keyData1:" + keyData1 + ";keyData2:" + keyData2+  ";异常:"+ e.getMessage(),e);
				}
			}
			if(null != rs){
				rs.close();
			}
		}
		return result;
	}

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午10:34:37
	 * 描述: 通过rowkey前缀搜索数据
	 * @param tableName 表名
	 * @param prefixRowKey 前缀
	 * @return Map<String,Map<String,byte[]>> 外层Map key是rowkey,里层Map key是列,value是具体数据
	 */
	public Map<String, Map<String, byte[]>> searchDataByPrefix(
			String tableName, String prefixRowKey) {
		if(null == connection){
			return null;
		}
		HashMap<String, Map<String, byte[]>> result = new HashMap<String, Map<String, byte[]>>();
		Table table = null;
		ResultScanner rs = null;
		try {
			table = connection.getTable(TableName.valueOf(tableName));
			Scan scan = new Scan();
			if (StringUtilsx.isNotEmpty(prefixRowKey)) {
				scan.setRowPrefixFilter(prefixRowKey.getBytes());
			}
			rs = table.getScanner(scan);
			for (final Result r : rs) {
				String keyData = new String(r.getRow());
				Cell[] cells = r.rawCells();
				Map<String, byte[]> tmpMap = new HashMap<String, byte[]>();
				for (final Cell cell : cells) {
					tmpMap.put(new String(CellUtil.cloneFamily(cell)) + ":" +
				               new String(CellUtil.cloneQualifier(cell)),
				                CellUtil.cloneValue(cell));
				}
				result.put(keyData, tmpMap);
			}
		}catch (IOException e) {
			LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";prefixRowKey:" + prefixRowKey + ";异常:"+ e.getMessage(),e);
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";prefixRowKey:" + prefixRowKey + ";异常:"+ e.getMessage(),e);
				}
			}
			if(null != rs){
				rs.close();
			}
		}
		return result;
	}
	
	
	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午10:34:37
	 * 描述: 通过rowkey前缀搜索数据
	 * @param tableName 表名
	 * @param prefixRowKey 前缀
	 * @param keyData1 包含数据
	 * @return Map<String,Map<String,byte[]>> 外层Map key是rowkey,里层Map key是列,value是具体数据
	 */
	public Map<String, Map<String, byte[]>> searchDataByPrefix(
			String tableName, String prefixRowKey,String keyData1) {
		if(null == connection){
			return null;
		}
		HashMap<String, Map<String, byte[]>> result = new HashMap<String, Map<String, byte[]>>();
		Table table = null;
		ResultScanner rs = null;
		try {
			table = connection.getTable(TableName.valueOf(tableName));
			Scan scan = new Scan();
			if (StringUtilsx.isNotEmpty(prefixRowKey)) {
				scan.setRowPrefixFilter(prefixRowKey.getBytes());
			}
			rs = table.getScanner(scan);
			for (final Result r : rs) {
				String rowKeyData = new String(r.getRow());
				if (StringUtilsx.isNotEmpty(rowKeyData)) {
					if ((StringUtilsx.isNotEmpty(keyData1) && rowKeyData
							.contains(keyData1))) {
						Cell[] cells = r.rawCells();
						Map<String, byte[]> tmpMap = new HashMap<String, byte[]>();
						for (final Cell cell : cells) {
							tmpMap.put(new String(CellUtil.cloneFamily(cell)) + ":" +
						               new String(CellUtil.cloneQualifier(cell)),
						                CellUtil.cloneValue(cell));
						}
						result.put(rowKeyData, tmpMap);
					}
				}
			}
		}catch (IOException e) {
			LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";prefixRowKey:" + prefixRowKey + ";异常:"+ e.getMessage(),e);
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					LOGGER.info("HbaseSource-searchData,tableName:" + tableName + ";prefixRowKey:" + prefixRowKey + ";异常:"+ e.getMessage(),e);
				}
			}
			if(null != rs){
				rs.close();
			}
		}
		return result;
	}

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年6月1日 下午4:19:27
	 * 描述: 失效一张表
	 * @param disableTableName
	 */
	public void disableTable(String disableTableName){
		TableName tableName = TableName.valueOf(disableTableName);
		Admin admin = null;
		try {
			admin = connection.getAdmin();
			admin.disableTable(tableName);
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			if(null !=  admin){
				try {
					admin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年6月1日 下午4:19:40
	 * 描述: 删除一张表
	 * @param deleteTableName
	 */
	public void deleteTable(String deleteTableName){
		TableName tableName = TableName.valueOf(deleteTableName);
		Admin admin = null;
		try {
			admin = connection.getAdmin();
			admin.deleteTable(tableName);
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			if(null !=  admin){
				try {
					admin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 
	 * @author tanjie 创建时间: 2017年4月13日 上午10:56:30 描述: 批量插入
	 * @param tableName
	 * @param list
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public void batchPut(String tableName, List<Put> list){
		Table table = null;
		try {
			if(null == connection){
				return;
			}
			table = connection.getTable(TableName.valueOf(tableName));
			if (null == table) {
				return;
			}
			int size = list.size();
			table.batch(list, new Object[size]);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	
	/**
	 * 
	 * @author tanjie 创建时间: 2017年4月13日 上午10:56:30 描述: 单个put操作
	 * @param tableName 表名
	 * @param put  Put
	 * @throws IOException
	 */
	public void singlePut(String tableName, Put put){
		Table table = null;
		try {
			if(null == connection){
				return;
			}
			table = connection.getTable(TableName.valueOf(tableName));
			if (null == table) {
				return;
			}
			table.put(put);
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			if(null != table){
				try {
					table.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 
	 * @author tanjie 
	 * 创建时间: 2017年4月13日 上午10:56:39 
	 * 描述: 添加每一行数据
	 * @param put Put对象
	 * @param family 列族
	 * @param column 列
	 * @param value 值
	 */
	public void addColumn(Put put, String family, String column, String value) {
		put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column),
				System.currentTimeMillis(), Bytes.toBytes(value));
	}

	/**
	 * 
	 * @author tanjie 
	 * 创建时间: 2017年4月10日 下午6:52:13
	 * 描述: 获取某个应用对应的hbaseSource
	 * @throws IOException
	 */
	public static HBaseSource getHbaseSource(String appName) throws IOException {
		HBaseSource hbaseSource = hbasepool.get(appName);
		Date date = new Date();
		if (null == hbaseSource) {
			hbaseSource = new HBaseSource();
			hbasepool.put(appName, hbaseSource);
			createTimes.put(appName,date);
			lastTimes.put(appName, date);
			return hbaseSource;
		}
		lastTimes.put(appName, date);
		return hbaseSource;
	}

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午10:32:13
	 * 描述: 打开连接
	 * @throws IOException
	 */
	public void openConnection(){
		try {
			if (null != connection) {
				return;
			}
			conf = HBaseConfiguration.create();
			connection = ConnectionFactory.createConnection(conf);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @author tanjie
	 * 创建时间:  2017年4月15日 上午11:51:05
	 * 描述: 判断数据源连接是否打开
	 * @return  boolean
	 */
	public boolean isOpen(){
		if(null == connection || connection.isClosed() ){
			return false;
		}
		return true;
	}
	/**
	 * 关闭与数据源的连接
	 */
	public void closeConnection() {
		if (null != connection) {
			try {
				connection.close();
				connection = null;
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				connection = null;
			}
		}
	}

	/**
	 * 获取hbase的连接属性
	 * 
	 * @return Configuration this.conf 连接属性
	 */
	public Configuration getConfiguration() {
		return this.conf;
	}

	/**
	 * 构造hbase的连接配置属性
	 * 
	 * @param config
	 *            连接属性
	 */
	public void setConfiguration(Configuration config) {
		this.conf = config;
	}

}                                                                                                                 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值