Spring整合Redis单例实现02

spring把专门的数据操作独立封装在spring-data系列中,spring-data-redis自然是针对Redis的独立封装了。

当前版本1.0.1,主要是将jedis、jredis、rjc以及srp等Redis Client进行了封装,同时支持事务。当然,当前版本不支持Sharding,据说Redis3.0会推出,期待吧。想必大家刚开始接触Spring的时候,都学习过Spring JDBC,如果是这样的话,接下来的代码,你们会觉得似曾相识。

一、熟悉的Spring JDBC模式(回调模式)

1.1 pom中添加spring、jedis依赖

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<springframework.version>4.3.13.RELEASE</springframework.version>
		<redis.version>2.9.0</redis.version>
		<spring.redis.version>2.0.2.RELEASE</spring.redis.version>
		<commons-lang.version>2.6</commons-lang.version>
		<commons-beanutils.version>1.9.0</commons-beanutils.version>
		<commons-collections.version>3.2.1</commons-collections.version>
		<commons-io.version>2.4</commons-io.version>
		<commons-logging.version>1.1.1</commons-logging.version>
		<commons-codec.version>1.10</commons-codec.version>
		<slf4j.version>1.7.7</slf4j.version>
		<log4j.version>1.2.16</log4j.version>
		<logback.version>1.1.2</logback.version>

	</properties>

	<repositories>
		<!-- 有了仓库的组的概念, 我们只需要做一次引用就可以了 -->
		<!-- Nexus中预设了2个仓库组,public repositories和public snapshot repositories -->
		<repository>
			<id>nexus</id>
			<name>Team Nexus Repository</name>
			<url>http://mvnrepository.com</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>

		</repository>

	</repositories>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>${commons-lang.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>${commons-collections.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>${commons-beanutils.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>${commons-logging.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
			<version>${commons-codec.version}</version>
		</dependency>

		<!--logger begin -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${slf4j.version}</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
			<exclusions>
				<exclusion>
					<groupId>javax.mail</groupId>
					<artifactId>mail</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.jms</groupId>
					<artifactId>jms</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jdmk</groupId>
					<artifactId>jmxtools</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jmx</groupId>
					<artifactId>jmxri</artifactId>
				</exclusion>
				<exclusion>
					<groupId>oro</groupId>
					<artifactId>oro</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- logger end -->

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>${redis.version}</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.8.9.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${springframework.version}</version>
		</dependency>
	</dependencies>


1.2 新建application.properties资源文件

#\u6700\u5927\u5206\u914D\u7684\u5BF9\u8C61\u6570  
redis.pool.maxActive=1024  
#\u6700\u5927\u80FD\u591F\u4FDD\u6301idel\u72B6\u6001\u7684\u5BF9\u8C61\u6570  
redis.pool.maxIdle=200  
#\u5F53\u6C60\u5185\u6CA1\u6709\u8FD4\u56DE\u5BF9\u8C61\u65F6\uFF0C\u6700\u5927\u7B49\u5F85\u65F6\u95F4  
redis.pool.maxWait=1000  
#\u5F53\u8C03\u7528borrow Object\u65B9\u6CD5\u65F6\uFF0C\u662F\u5426\u8FDB\u884C\u6709\u6548\u6027\u68C0\u67E5  
redis.pool.testOnBorrow=true 
redis.pool.testWhileIdle=true
redis.pool.timeBetweenEvictionRunsMillis=60000
  
#IP  
redis.ip=127.0.0.1 
#Port  
redis.port=6379  


1.3 新建spring配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
			http://www.springframework.org/schema/context
			http://www.springframework.org/schema/context/spring-context-4.1.xsd"
	default-autowire="byName">
	<!-- 以下为jedis单节点配置 -->
	<!-- 加载配置文件 -->
	<context:property-placeholder location="classpath:application.properties" />

	<!-- jedis非切片化配置 -->
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<property name="maxTotal" value="${redis.pool.maxActive}" />
		<property name="maxIdle" value="${redis.pool.maxIdle}" />
		<property name="maxWaitMillis" value="${redis.pool.maxWait}" />
		<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
		<property name="testWhileIdle" value="${redis.pool.testWhileIdle}" />
		<property name="timeBetweenEvictionRunsMillis" value="${redis.pool.timeBetweenEvictionRunsMillis}" />
	</bean>

	<!-- jedis客户端连接工厂实现 -->
	<bean id="jedisConnectionFactory"
		class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
		<property name="hostName" value="${redis.ip}" />
		<property name="port" value="${redis.port}" />
		<property name="poolConfig" ref="jedisPoolConfig" />
	</bean>

	<!-- jedis 模版类 -->
	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory"></property>
		<!-- 如果不配置Serializer,那么存储的时候智能使用String,
		             如果用User类型存储,那么会提示错误User can't cast to String!!! -->
		<property name="keySerializer">
			<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="valueSerializer">
			<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
		</property>
	</bean>

</beans>


1.4实体类

public class User  implements Serializable {  
  
    private static final long serialVersionUID = -1267719235225203410L;  
  
    private String uid;  
  
    private String address;

    private String mobile;  
    
    private String postCode;  
    
    
	public String getUid() {
		return uid;
	}

	public void setUid(String uid) {
		this.uid = uid;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getMobile() {
		return mobile;
	}

	public void setMobile(String mobile) {
		this.mobile = mobile;
	}

	public String getPostCode() {
		return postCode;
	}

	public void setPostCode(String postCode) {
		this.postCode = postCode;
	}

	@Override
	public String toString() {
		return "User [uid=" + uid + ", address=" + address + ", mobile="
				+ mobile + ", postCode=" + postCode + "]";
	}
}


1.5 功能测试类

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;

/**
 * 
 * 案例参考:http://snowolf.iteye.com/blog/1667104
 * 
 * 基本操作,存储String类型数据
 * 
 * 目前redis并未实现集群技术,redis3.0之后版本可能出现集群技术,拭目以待
 * 
 * spring-data-redis已经对jedis、jredis进行了封装,
 * (1)完美兼容redis sentinel部署模式、
 *    --sentinel采用投票方式进行故障切换,而投票方式下原则是少数服从多数,所以sentinel一般至少是3台
 * (2)兼容redis-cluster部署模式
 * (3)单节点redis部署模式,
 * 提供统一的API(RedisTemplate)来调用不同的部署模式,完全不用担心redis部署模式变化导致redis客户端代码做调整
 * 
 * @Description:
 * @version 1.0
 * @Create: 2017年12月27日 下午12:35:13
 * @Modification History
 * @Date Author Version Description
 */
public class SpringJedisCallBackTest {
	private static ApplicationContext context = null;
	private static RedisTemplate<Serializable, Serializable> redisTemplate = null;

	static {
		context = new ClassPathXmlApplicationContext(
				"classpath*:applicationContext.xml");
		redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
	}

	public static void main(String[] args) {
		User user = new User();
		user.setUid("1111112255");
		user.setAddress("上海市");
		user.setMobile("");
		user.setPostCode("");

		/*
		 * save(user);
		 * 
		 * user= read(user.getUid()); System.out.println("save==="+user);
		 * 
		 * user.setAddress("hhhhhhhhhhhh");
		 * 
		 * save(user);
		 * 
		 * delete(user.getUid());
		 * 
		 * user= read(user.getUid()); System.out.println("update==="+user);
		 */

		/*
		 * hashSave(user);
		 * 
		 * user=hashRead(user.getUid()); System.out.println(user);
		 */

	/*	listSave(user);

		
		listRead(user);*/
		
		
		/*setSave(user);
		
		setRead(user);*/
		
		zsetSave(user);
		
		zsetRead(user);
		
		
	}

	/**
	 * 新增/修改 key不存在则新增,key存在则修改对应value值 基本String操作:set
	 * 
	 * @param user
	 */
	public static void save(final User user) {
		redisTemplate.execute(new RedisCallback<User>() {

			public User doInRedis(RedisConnection connection)
					throws DataAccessException {
				connection.set(
						redisTemplate.getStringSerializer().serialize(
								"user.uid." + user.getUid()),
						redisTemplate.getStringSerializer().serialize(
								user.getAddress()));
				return null;
			}
		});

	}

	/**
	 * 基本String操作:get
	 * 
	 * @param uid
	 * @return
	 */
	public static User read(final String uid) {
		return redisTemplate.execute(new RedisCallback<User>() {
			public User doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] key = redisTemplate.getStringSerializer().serialize(
						"user.uid." + uid);
				if (connection.exists(key)) {
					byte[] value = connection.get(key);
					String address = redisTemplate.getStringSerializer()
							.deserialize(value);
					User user = new User();
					user.setAddress(address);
					user.setUid(uid);
					return user;
				}
				return null;
			}
		});
	}

	/**
	 * 基本String操作:del
	 * 
	 * @param uid
	 */
	public static void delete(final String uid) {
		redisTemplate.execute(new RedisCallback<Object>() {

			public Object doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] key = redisTemplate.getStringSerializer().serialize(
						"user.uid." + uid);
				long count = connection.del(key);
				System.out.println("count====" + count);
				return null;
			}
		});
	}

	/**
	 * 保存hash表数据
	 * 
	 * @param user
	 */
	public static void hashSave(final User user) {
		redisTemplate.execute(new RedisCallback<User>() {

			public User doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] key = redisTemplate.getStringSerializer().serialize(
						"user.info.uid." + user.getUid());

				// 绑定hash key
				BoundHashOperations<Serializable, byte[], byte[]> operations = redisTemplate
						.boundHashOps(key);
				operations.put(
						redisTemplate.getStringSerializer()
								.serialize("address"),
						redisTemplate.getStringSerializer().serialize(
								user.getAddress()));
				operations
						.put(redisTemplate.getStringSerializer().serialize(
								"mobile"), redisTemplate.getStringSerializer()
								.serialize(user.getMobile()));
				operations.put(
						redisTemplate.getStringSerializer().serialize(
								"postCode"),
						redisTemplate.getStringSerializer().serialize(
								user.getPostCode()));

				connection.hMSet(key, operations.entries());
				return null;
			}
		});

	}

	/**
	 * hash表数据读取
	 * 
	 * @param uid
	 * @return
	 */
	public static User hashRead(final String uid) {
		return redisTemplate.execute(new RedisCallback<User>() {

			public User doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] key = redisTemplate.getStringSerializer().serialize(
						"user.info.uid." + uid);
				if (connection.exists(key)) {
					List<byte[]> value = connection.hMGet(
							key,
							redisTemplate.getStringSerializer().serialize(
									"address"),
							redisTemplate.getStringSerializer().serialize(
									"mobile"), redisTemplate
									.getStringSerializer()
									.serialize("postCode"));
					User user = new User();
					user.setAddress(redisTemplate.getStringSerializer()
							.deserialize(value.get(0)));
					user.setMobile(redisTemplate.getStringSerializer()
							.deserialize(value.get(1)));
					user.setPostCode(redisTemplate.getStringSerializer()
							.deserialize(value.get(2)));
					user.setUid(uid);
					return user;
				}
				return null;
			}
		});
	}

	/**
	 * 保存list
	 * @param user
	 */
	public static void listSave(final User user) {

		byte[] key = redisTemplate.getStringSerializer().serialize(
				"user.list." + user.getUid());
		// 绑定hash key
		BoundListOperations<Serializable, Serializable> operations = redisTemplate
				.boundListOps(key);
		operations.rightPush("11111");
		operations.rightPush("22222");
		operations.rightPush("33333");
		operations.rightPush("44444");

	}

	/**
	 * 读取list
	 * @param user
	 */
	public static void listRead(final User user) {

		byte[] key = redisTemplate.getStringSerializer().serialize(
				"user.list." + user.getUid());
		// 绑定hash key
		BoundListOperations<Serializable, Serializable> operations = redisTemplate
				.boundListOps(key);
		List<Serializable> list=operations.range(0, -1);
		for (Iterator iterator = list.iterator(); iterator.hasNext();) {
			Serializable serializable = (Serializable) iterator.next();
			System.out.println(serializable);
		}
	}
	
	/**
	 * 保存set
	 * @param user
	 */
	public static void setSave(final User user) {

		byte[] key = redisTemplate.getStringSerializer().serialize(
				"user.set." + user.getUid());
		// 绑定hash key
		BoundSetOperations<Serializable, Serializable> operations = redisTemplate.boundSetOps(key);
		operations.add("set11111");
		operations.add("set33333");
		operations.add("set22222");
	}
	
	
	/**
	 * 读取list
	 * @param user
	 */
	public static void setRead(final User user) {
		byte[] key = redisTemplate.getStringSerializer().serialize(
				"user.set." + user.getUid());
		// 绑定hash key
		BoundSetOperations<Serializable, Serializable> operations = redisTemplate.boundSetOps(key);
		Set<Serializable> set=operations.members();
		for (Iterator iterator = set.iterator(); iterator.hasNext();) {
			Serializable serializable = (Serializable) iterator.next();
			System.out.println(serializable);
		}
	}
	
	public static void zsetSave(final User user) {

		byte[] key = redisTemplate.getStringSerializer().serialize(
				"user.zset." + user.getUid());
		// 绑定hash key
		BoundZSetOperations<Serializable, Serializable> operations = redisTemplate.boundZSetOps(key);
		operations.add("zset1111", 7.0);
		operations.add("zset2222",2.0);
		operations.add("zset3333",3.0);
	}
	
	
	/**
	 * 读取list
	 * @param user
	 */
	public static void zsetRead(final User user) {
		byte[] key = redisTemplate.getStringSerializer().serialize(
				"user.zset." + user.getUid());
		// 绑定hash key
		BoundZSetOperations<Serializable, Serializable> operations = redisTemplate.boundZSetOps(key);
		Set<TypedTuple<Serializable>> set=operations.rangeWithScores(0,-1);
		for (Iterator iterator = set.iterator(); iterator.hasNext();) {
			TypedTuple serializable = (TypedTuple) iterator.next();
			System.out.println(serializable.getValue()+","+serializable.getScore());
		}
	}
}

     上面的由于list、set在RedisConnection中没找到集合添加的方式(只看到可变参数的方式,这种实现麻烦,就不上了),有兴趣的童鞋可以自己研究下。


二、Operations方式

       上面回调的方式操作对于项目上来说实在是不易操作,费时费力(伙计们看到上面代码有没懒癌发作),接下来我们利用Spring替我们封装好的api简化下操作:

       备注:本操作在一的基础上进行,配置文件不需要改动,只要改下测试类 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;

/**
 * 
 * RedisTemplate高级操作 ValueOperations<K, V> valueOps; ListOperations<K, V>
 * listOps; SetOperations<K, V> setOps; ZSetOperations<K, V> zSetOps;
 * 案例参考1:https://www.cnblogs.com/edwinchen/p/3816938.html
 * 案例参考2:http://www.cnblogs.com/luochengqiuse/p/4641256.html
 * 
 * 目前redis并未实现集群技术,redis3.0之后版本可能出现集群技术,拭目以待
 * 
 * spring-data-redis已经对jedis、jredis进行了封装,
 * (1)完美兼容redis sentinel部署模式、
 *    --sentinel采用投票方式进行故障切换,而投票方式下原则是少数服从多数,所以sentinel一般至少是3台
 * (2)兼容redis-cluster部署模式
 * (3)单节点redis部署模式
 * 提供统一的API(RedisTemplate)来调用不同的部署模式,完全不用担心redis部署模式变化导致redis客户端代码做调整
 * 
 * @Description:
 * @Create: 2017年12月27日 下午3:38:20
 * @Modification History
 * @Date Author Version Description
 */
public class SpringJedisRedisOperationsTest {

	private static ApplicationContext context = null;
	private static RedisTemplate<String, User> redisTemplate = null;

	static {
		context = new ClassPathXmlApplicationContext(
				"classpath*:applicationContext.xml");
		redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
	}

	public static void main(String[] args) {
		User user = new User();
		user.setUid("1111112333");
		user.setAddress("上海市虹漕路A座4楼");
		user.setMobile("mobile");
		user.setPostCode("postcode");

		/*
		 * save(user);
		 * 
		 * user=read(user); System.out.println("user==="+user);
		 */

		/*
		 * listSave(user);
		 * 
		 * listRead(user);
		 */

		/*
		 * hashSave(user); hashRead(user);
		 */

		/*setSave(user);

		setRead(user);*/
		
		zsetSave(user);
		
		zsetRead(user);
		
	}

	public static void save(User user) {
		redisTemplate.opsForValue().set(user.getUid(), user);
	}

	public static User read(User user) {
		return redisTemplate.opsForValue().get(user.getUid());
	}

	public static void listSave(User user) {
		List<User> users = new ArrayList<User>();
		users.add(user);
		redisTemplate.opsForList().leftPushAll(
				"user.list.ops." + user.getUid(), users);
	}

	public static void listRead(User user) {
		List<User> users = redisTemplate.opsForList().range(
				"user.list.ops." + user.getUid(), 0, -1);
		for (Iterator iterator = users.iterator(); iterator.hasNext();) {
			User user1 = (User) iterator.next();
			System.out.println(user1);
		}
	}

	public static void hashSave(User user) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("address", user.getAddress());
		map.put("mobile", user.getMobile());
		map.put("postCode", user.getPostCode());
		redisTemplate.opsForHash()
				.putAll("user.hash.ops." + user.getUid(), map);
	}

	public static void hashRead(User user) {
		Map<Object, Object> map = redisTemplate.opsForHash().entries(
				"user.hash.ops." + user.getUid());
		Iterator<Entry<Object, Object>> it = map.entrySet().iterator();
		while (it.hasNext()) {
			Entry<Object, Object> type = it.next();
			System.out.println("key:" + type.getKey() + ",value:"
					+ type.getValue());
		}
	}

	public static void setSave(User user) {

		/*
		 * 竟然没有批量新增方法???? 
		 */
		redisTemplate.opsForSet().add("user.set.ops." + user.getUid(), user);
	}

	public static void setRead(User user) {
		Set<User> sets = redisTemplate.opsForSet().members(
				"user.set.ops." + user.getUid());
		for (Iterator iterator = sets.iterator(); iterator.hasNext();) {
			User user1 = (User) iterator.next();
			System.out.println("user1:" + user1);
		}
	}

	public static void zsetSave(User user) {
        Set<TypedTuple<User>> zsets=new HashSet<TypedTuple<User>>();
        TypedTuple<User> tuple0=new DefaultTypedTuple(user, 1d);
        zsets.add(tuple0);
		redisTemplate.opsForZSet().add("user.zset.ops." + user.getUid(), zsets);
	}
	
	public static void zsetRead(User user) {
        Set<TypedTuple<User>> zsets=new HashSet<TypedTuple<User>>();
        TypedTuple<User> tuple0=new DefaultTypedTuple(user, 1d);
        zsets.add(tuple0);
		Set<User> users=redisTemplate.opsForZSet().range("user.zset.ops." + user.getUid(), 0, -1);
		for (Iterator iterator = users.iterator(); iterator.hasNext();) {
			User user2 = (User) iterator.next();
			System.out.println("user2:"+user2);
		}
	}

}
      上面的代码在操作单例server时,是不是简单许多,不过奇怪的是,spring竟然没有提供set的集合新增方式,之后可变参数方式,心累。到此为止,单例方式我觉得使用上述Operations应该足够在项目中使用了,下一篇准备研究下redis集群技术了,虽然redis自身没有提供集群解决方案,但是咱IT圈牛人还是很多的,redis集群解决方案还是存在的。

      项目代码地址:https://github.com/TaavettiTao/Redis.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值