如何高逼格的写java代码

12 篇文章 1 订阅

记录一些了解的高逼格、好用的java代码

欢迎大家补充,共同学习

1. 函数式接口—@FunctionalInterface

好处:高逼格、代码收拢、解藕、统一处理
适用范围:具有共性的接口调用代码
举个栗子:

在我们平时的微服务开发中,调用其他服务的接口,通常要把接口调用部分做异常处理(try catch),或者打印异常日志或者结果日志,并且也有可能做一些统一的调用处理,比如微服务接口熔断等处理,这个时候可以适用函数式接口收拢所有的微服务调用集中处理

这是一个微服务接口

public interface TestServer {
	T test();

	void test2();
}

普通的方式调用上面的微服务方法

public class RpcTestServerImpl{
	//引用微服务接口
	private TestServer testServer;	

	public T test(){
		try{
			return testServer.test();
		}catch (Exception e){
            log.error("RPC error: ", e);
        }
        return null;
	}
	
	public void test2(){
		try{
			testServer.test2();
		}catch (Exception e){
            log.error("RPC error: ", e);
        }
	}
}

使用函数式接口后的写法:先定义统一调用类

//首先定义一个ServerExecutor
public class ServerExecutor {
	//不需要返回值的
	public static void execute(Runnable runnable) {
        try {
            //调用前和调用后可以做一些处理,比如熔断、日志等等
            runnable.run();
            //调用前和调用后可以做一些处理,比如熔断、日志等等
        } catch (Exception e) {
            log.error("RPC error: ", e);
        }
    }

	//需要返回值的
	public static <T> T executeAndReturn(Callable<T> callable) {
        try {
        	//调用前和调用后可以做一些处理,比如熔断、日志等等
            return callable.call();
        } catch (Exception e) {
            log.error("RPC invoke error", e);
        }
        return null;
    }
}

使用统一定义的接口调用之后:

public class RpcTestServerImpl{
	//引用微服务接口
	private TestServer testServer;	

	public T test(){
        return ServerExecutor.executeAndReturn(() -> testServer.test());
	}

	public T test2(){
		ServerExecutor.execute(() -> testServer.test2());
	}
}

扩展知识:

@FunctionalInterface
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html
通过文档可以知道:
1、该注解只能标记在有且仅有一个抽象方法的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

2. List分页工具类

平时开发中可能会遇到一批数据的批量查询,但是接口限制每次只能查10个,多了查询失败,这个时候就很烦,还的手动给这一批数据分页再去掉接口查询,这个时候就可以用到com.google.common.collect.Lists工具的com.google.common.collect.Lists#partition方法,这个方法可以帮我们自动拆分List,根据我们传入的分批大小,把大集合拆成多个小集合
好处:方便实用,不用手写
举个栗子:

List<Long> userIdList = new ArrayList<>();
userIdList.add(1L);
userIdList.add(2L);
userIdList.add(3L);
userIdList.add(4L);
userIdList.add(5L);
userIdList.add(6L);
userIdList.add(7L);
userIdList.add(8L);
List<List<Long>> partition = Lists.partition(userIdList, 3);

上面这个代码,就可以把8个元素的userIdList通过Lists.partition拆分成3个元素一个的小集合,而且顺序不变,以前不知道这个方法,被同事diss一波,然后记录一下

3. 分割符的字符串和集合互转

日常开发经常遇到这种:1,2,3,4,5,6这种逗号分割的,要把他转成List,或者把一个List转成根据逗号分割的字符串,自己写的话就只能split加循环,这个时候又可以用到谷歌工具包
好处:方便实用,不用手写
举个栗子:

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.util.List;

public class CollectionUtil {

    /**
     * 把list的数据根据split拼接起来,自动过滤data中的null
     * 栗子:data:[1,3,4,6]  split:"."  return: "1.3.4.6"
     */
    public static String jointList(List data, String split){
        return Joiner.on(split).skipNulls().join(data);
    }

    /**
     * 把根据split分隔的字符串str变成List
     * 栗子:str:"1.3.5.7.9" split:"."   return:["1","3","5","7","9"]
     */
    public static List<String> splitIntoList(String str, String split){
        return Splitter.on(split).omitEmptyStrings().splitToList(str);
    }
}

4. 使用策略模式消除大篇幅if else

日常开发经常会遇到需要大量的if else判断,然后执行对应的代码,如果业务复杂,可能会十几个或者几十个if else,对于代码的扩展性和可读性有很大影响,而且看起来就很low,所以我们可以用到策略模式来消除大量的if else,并且让代码更具有健壮性
好处:高逼格,代码健壮,扩展性强
举个栗子:

//举例老的if else
if ("xxx".equals(type)) {
    System.out.println("do xxx");
} else if ("yyy".equals(type)) {
    System.out.println("do yyy");
} else if ("zzz".equals(type)) {
    System.out.println("do zzz");
} else if ("xyz".equals(type)) {
    System.out.println("do xyz");
}
// 后面可能会有更多

随着业务发展,可能会有非常多的if else,每次添加逻辑都要改这里的if else,代码篇幅会非常长,而且不利于维护,还违反了开闭原则
这个时候我们可以用策略模式出来处理:

//定义策略执行接口
public interface DataProcessor {

    Result handleData(Data data);

}

//定义每种数据的处理实现类
@Service("XxxProcessor")
public class XxxProcessor implements DataProcessor {
	@Override
	public Result handleData(Data data){
		//do xxx ....
		return null;
	}
}
@Service("YyyProcessor")
public class YyyProcessor implements DataProcessor {
	@Override
	public Result handleData(Data data){
		//do yyy ....
		return null;
	}
}
@Service("ZzzProcessor")
public class ZzzProcessor implements DataProcessor {
	@Override
	public Result handleData(Data data){
		//do zzz ....
		return null;
	}
}
@Service("XyzProcessor")
public class XyzProcessor implements DataProcessor {
	@Override
	public Result handleData(Data data){
		//do Xyz ....
		return null;
	}
}

每一个DataProcessor的实现类,就相当于一个if else中的处理逻辑执行器
然后定义一个枚举,把这些逻辑执行器管理起来:

public enum DataProcessorEnum {
	XXX(1, "xxx业务描述", "XxxProcessor"),
	YYY(2, "yyy业务描述", "YyyProcessor"),
	ZZZ(3, "zzz业务描述", "ZzzProcessor"),
	XYZ(4, "xyz业务描述", "XyzProcessor");
	/**
     * 业务type
     */
    private Integer type;

    /**
     * 业务描述
     */
    private String name;

    /**
     * 对应处理器
     * {@link DataProcessor}
     */
    private String processor;
    
	public static DataProcessorEnum valueOf(Integer type) {
        if (type == null) {
            return null;
        }
        for (DataProcessorEnum dataProcessorEnum : DataProcessorEnum.values()) {
            if (type.equals(dataProcessorEnum.getType())) {
                return dataProcessorEnum;
            }
        }
        return null;
    }
	
	//省略get等方法
}

当实际使用的时候,我们需要拿到对应处理器的实例,这里举例是在spring容器中为例,因为DataProcessor实现上都有@Service注解,如果不在spring容器中,可以自己实现反射获取实例的方法,这里就不再写反射获取了,大多数情况我们都是在spring环境中适用的,所以需要工具从spring 中获取对应实例,提供一个spring实例获取工具,这个应该很常见:


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

准备工作到这里应该算是完成了,实际使用中,可以这样操作:

public void dealData(Data bizData){
	if(bizData == null){
		return;
	}
	DataProcessorEnum dataProcessorEnum = DataProcessorEnum.valueOf(bizData.getType());
	if(dataProcessorEnum == null){
		//没有在枚举中匹配到处理器,说明业务参数不合法或者没有添加对应的业务枚举
		return;
	}
	DataProcessor processor = SpringUtil.getBean(dataProcessorEnum.getProcessor(), DataProcessor.class);
	if(processor == null){
		//没有从spring容器中获取到对应处理器到实例,属于异常情况,检查枚举配置和处理器是否正确注入spring容器
		return;
	}
	//交给对应到处理器去处理
	Result result = processor.handleData(bizData);
	//处理完成
}

到这里,使用策略模式消除if else就基本写完了,这里只是写出了基本到策略模式使用,还有其他更多的用法可以自己学习一下,而且其实我们使用策略模式前和使用后,感觉代码篇幅不减反增,肯定会想,这改造越改越复杂了呀
所以得出结论:if-else或switch case 这种分支判断的方式对于分支逻辑不多的简单业务,还是直观高效的。对于业务复杂,分支逻辑多,采用适当的模式技巧,会让代码更加清晰,容易维护,但同时类或方法数量也是倍增的。我们需要对业务做好充分分析,避免一上来就设计模式,避免过度设计!
另外再说一下,你就天天光写if else 和实习生有啥区别?怎么提升逼格?对吧

5. 当策略模式使用不值得的时候,如果优雅的处理大量if else

上面刚说完使用策略模式消除if else,立马先打脸自己,打脸自己才能成长,这次说一个使用java.util.function.Function jdk1.8 函数接口方式处理大篇幅if else的方式
好处:高逼格、比策略模式看起来简单明了(但是需要了解函数式编程,很简单)

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

public class Test {

    /**
     * 传统的 if else 解决方法
     * 当每个业务逻辑有 3 4 行时,用传统的策略模式不值得,直接的if else又显得不易读
     */
    public String doBizService(String order) {
        if ("type1".equals(order)) {
            return "执行业务逻辑1";
        } else if ("type2".equals(order)) {
            return "执行业务逻辑2";
        }else if ("type3".equals(order)) {
            return "执行业务逻辑3";
        }else if ("type4".equals(order)) {
            return "执行业务逻辑4";
        }else if ("type5".equals(order)) {
            return "执行业务逻辑5";
        }else if ("type6".equals(order)) {
            return "执行业务逻辑6";
        }else if ("type7".equals(order)) {
            return "执行业务逻辑7";
        }else if ("type8".equals(order)) {
            return "执行业务逻辑8";
        }else if ("type9".equals(order)) {
            return "执行业务逻辑9";
        }
        return "不在处理的逻辑中返回业务错误";
    }

	//---------------------------高逼格分割线------------------------------------

    /**
     * 业务逻辑分派Map
     * Function为函数式接口,
     * 下面代码中 Function<String, Object> 的含义是接收一个String类型的变量用来获取你要执行哪个Function,实际使用中可自行定义
     * Function执行完成返回一个Object类型的结果,这个结果就是统一的业务处理返回结果,实际使用中可自行定义
     */
    private static Map<String, Function<String, Object>> checkResultDispatcher = new HashMap<>();

    /**
     * 初始化 业务逻辑分派Map 其中value 存放的是 lambda表达式
     * 也可以依赖于spring的@PostConstruct 初始化checkResultDispatcher 根据各个技术栈调整
     */
    static  {
        checkResultDispatcher.put("type1", order -> testService.handleTyep1(order));
        checkResultDispatcher.put("type2", order -> testService.handleTyep2(order));
        checkResultDispatcher.put("type3", order -> testService.handleTyep3(order));
        checkResultDispatcher.put("type4", order -> testService.handleTyep4(order));
        checkResultDispatcher.put("type5", order -> testService.handleTyep5(order));
        checkResultDispatcher.put("type6", order -> testService.handleTyep6(order));
        checkResultDispatcher.put("type7", order -> testService.handleTyep7(order));
        checkResultDispatcher.put("type8", order -> testService.handleTyep8(order));
        checkResultDispatcher.put("type9", order -> testService.handleTyep9(order));
    }

    public Object handleBizService(String type) {
        //从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式
        Function<String, Object> result = checkResultDispatcher.get(type);
        if (result != null) {
            //执行这段表达式获得String类型的结果
            return result.apply(type);
        }
        return "不在处理的逻辑中返回业务错误";
    }
}

补一下关于java.util.function包下面各个类的简单了解

nametypedescription
ConsumerConsumer< T >接收T对象,不返回值
PredicatePredicate< T >接收T对象并返回boolean
FunctionFunction< T, R >接收T对象,返回R对象
SupplierSupplier< T >提供T对象(例如工厂),不接收值
UnaryOperatorUnaryOperator< T >接收T对象,返回T对象
BiConsumerBiConsumer<T, U>接收T对象和U对象,不返回值
BiPredicateBiPredicate<T, U>接收T对象和U对象,返回boolean
BiFunctionBiFunction<T, U, R>接收T对象和U对象,返回R对象
BinaryOperatorBinaryOperator< T >接收两个T对象,返回T对象

使用Function处理策略需求确实很方便很好用,但是有一个问题,就是在Function中无法抛出非RuntimeException的异常,很多时候我们自定义的异常并不是继承自RuntimeException的,这个时候使用Function就会被异常困扰,这个时候我们可以模仿Function来自定义一个我们自己的FunctionWithException

/**
 * 一个可以抛出Exception的Function
 * 是java.util.function.Function的变体
 * 主要解决java.util.function.Function中不能抛出非RuntimeException的问题
 */
@FunctionalInterface
public interface FunctionWithException<T, R> {
    R apply(T t) throws Exception;
}

然后再做一个FunctionWithException的包装类:

import java.util.function.Function;

/**
 * FunctionWithException的包装器
 */
public class ExceptionWrapper {
    public static <T, R> Function<T, R> wrap(FunctionWithException<T, R> function) {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }

}

这个时候我们就可以不使用java.util.function.Function,而使用我们自己的FunctionWithException,使用例子如下:

private static Map<String, Function<String, Object>> checkResultDispatcher = new HashMap<>();

@PostConstruct
public void init() {
    checkResultDispatcher.put("type1", ExceptionWrapper.wrap(this::handleTyep1));
    checkResultDispatcher.put("type2", ExceptionWrapper.wrap(this::handleTyep2));
}

public Object handleBizService(String type) throws Exception {
    //从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式
    Function<String, Object> result = checkResultDispatcher.get(type);
    if (result == null) {
    	// 这个BizException是自定义的异常  它继承自Exception.class
        throw new BizException("参数异常");
    }
    try {
        return result.apply(type);
    } catch (Exception e) {
    	// 捕获内部的异常,转成我们自定义的异常处理
        throw new BizException(e.getMessage());
    }
}

private Object handleTyep1(String type){
	//...
}

private Object handleTyep2(String type){
	//...
}

这样就可以完美的处理java.util.function.Function的异常抛出问题了.

6. 通用的redis获取值并处理空缓存方法

日常开发会遇到一种缓存无效的情况:普通用获取redis值的时候,发现获取到的是空字符串,这个时候就会去查库,然后mysql库里面查询结果也是空的,这个时候就会有问题,如果一直调用这个key获取数据,缓存查不到,数据库也查不到,如果高频调用就会对数据库造成压力。对于这种情况,可以在库里查询为空的时候,给redis里面存一个空缓存{},当下次查询redis的时候,得到的不再是空字符串"",对于这种逻辑,本来是需要自己写逻辑的,然后每个这种都要写一遍判断逻辑,后来在开发中遇到同事(大佬)写的一个通用方法,很好用,记录下来:

public class CacheConstant {
   /**
    * 各类空缓存对象value
    */
   public static final String EMPTY_CACHE = "{}";

}


public static <T> T get(String key, Long timeout, Class<T> clazz, Callable<T> getFunction, Runnable exceptionRunnable) {
    RedisService redisService = SpringUtil.getBean(RedisService.class);
    if (redisService == null) {
        log.error("Redis查询接口 初始化异常");
        return null;
    }
    if (StringUtils.isBlank(key)) {
        return null;
    }
    String cacheValue = redisService.get(key);
    T object = null;
    if (StringUtils.isNotBlank(cacheValue)) {
        if (CacheConstant.EMPTY_CACHE.equals(cacheValue)) {
            return null;
        }

        try {
            object = JSONObject.parseObject(cacheValue, clazz);
        } catch (Exception e) {
            log.error("RedisUtil get error, key:[" + key + "], cacheValue:[" + cacheValue + "]", e);
        }

        if (object != null) {
            return object;
        } else {
            //说明解析发生了异常,调用解析异常处理,比如可以做一些缓存过期处理之类的操作
            if(exceptionRunnable != null){
                exceptionRunnable.run();
            }
        }
    }

    try {
        if(getFunction != null){
            object = getFunction.call();
        }
    } catch (Exception e) {
        log.error("getFunction.call error", e);
        return null;
    }

    cacheValue = object != null ? JsonUtil.object2json(object) : CacheConstant.EMPTY_CACHE;
    redisService.set(key, cacheValue, timeout);
    return object;
}

7. CompletableFuture基本使用

//创建第一个任务CompletableFuture  使用线程池taskExecutor
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);

//创建第二个任务CompletableFuture  使用线程池taskExecutor
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);

//创建第三个任务CompletableFuture  使用线程池taskExecutor
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> countVO.setTotal(baseDao.selectCount(queryWrapper)), taskExecutor);

//添加三个CompletableFuture到allFuture 
CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);
// allFuture 等待三个任务执行完成并结束,超时时间200毫秒
allFuture.get(200, TimeUnit.MILLISECONDS);

该用法类似于CountDownLatch的使用,创建多个异步任务,在主线程中等待每个任务执行完成
CompletableFuture的方法有很多,各种组合可以实现不同的功能。大致通过方法名称可以总结为:

创建类

  • runAsync 异步执行,无返回值
  • supplyAsync 异步执行,有返回值
  • anyOf 任意一个执行完成,就可以进行下一步动作
  • allOf 全部完成所有任务,才可以进行下一步任务

接续类

  • 以Async结尾的方法,都是异步方法,对应的没有Async则是同步方法,一般都是一个异步方法对应一个同步方法。
  • 以Async后缀结尾的方法,都有两个重载的方法,一个是使用内容的forkjoin线程池,一种是使用自定义线程池
  • 以run开头的方法,其入口参数一定是无参的,并且没有返回值,类似于执行Runnable方法。
  • 以supply开头的方法,入口也是没有参数的,但是有返回值
  • 以Accept开头或者结尾的方法,入口参数是有参数,但是没有返回值
  • 以Apply开头或者结尾的方法,入口有参数,有返回值
  • 带有either后缀的方法,表示谁先完成就消费谁
  • whenCompleteAsync:处理完成或异常,无返回值
  • handleAsync:处理完成或异常,有返回值

状态取值类

  • join 合并结果,等待
  • get 合并等待结果,可以增加超时时间;get和join区别,join只会抛出unchecked异常,get会返回具体的异常

8. @ConditionalOnMissingBean使用

@ConditionalOnMissingBea该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean。日常代码中写公共jar包代码,经常会有接口需要默认实现和自定义实现,jar包中写公共实现,如果需要对接口实现自定义实现,就可以使用@ConditionalOnMissingBean快速实现

/**
 * 默认实现配置
 */
@Configuration
public class DefaultConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "TestDefaultService")//指定Bean的名称
    public TestDefaultService testService() {
        return (param) -> {
        	//...默认公共实现
        };
    }
}
@Qualifier("TestDefaultService")//指定Bean名称
@Resource
private TestDefaultService testDefaultService;

这样使用之后,当手动实现了TestDefaultService 并指定BeanName为“TestDefaultService”,比如在接口实现类上这样写之后

@Service("TestDefaultService")

手动实现的接口就会被注入,而默认实现的就不会被注入,如果不手动实现接口,那么默认实现就会被注入。

9. 使用函数式编程实现代理模式

先上代码:

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 返回值
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class Test1DTO extends TestBaseDTO {

    private String name;

}

import lombok.Data;

/**
 * 返回值基类
 */
@Data
public class TestBaseDTO {

    private Integer value;

}
public interface Test1Service {

    TestBaseDTO handle() throws Exception;

}
public class Test1Util {

    public static <R extends TestBaseDTO> R execute(Test1Service test1Service) throws Exception {
        System.out.println("在执行之前做一些事情");
        R handle = (R) test1Service.handle();
        System.out.println("在执行之后做一些事情");
        return handle;
    }

}
/**
 * 测试类
 */
public class Test1 {

    public static void main(String[] args) throws Exception {

        TestBaseDTO execute = Test1Util.execute(() -> {
            // 这里可以写想要执行的逻辑代码
            // 返回值可以自定义  只要继承TestBaseDTO就行
            Test1DTO testBaseDTO = new Test1DTO();
            testBaseDTO.setValue(1);
            testBaseDTO.setName("test");
            return testBaseDTO;
        });

        System.out.println(execute);

    }

}

如上代码,就是通过函数式编程搞了一个Test1Util 来处理逻辑代码执行时被代理的逻辑。这个方式可改造的方式很多,可以轻易改造成适合自己的业务的写法。

10. 根据数学公式计算结果的工具类

依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>QLExpress</artifactId>
    <version>3.2.0</version>
</dependency>

工具类:

/**
 * 公式计算工具
 *
 * @author jingchuan
 */
public class FormulaCalculateUtil {

    public static final ExpressRunner runner = new ExpressRunner(true, false);
    public static final DefaultContext<String, Object> context = new DefaultContext<>();
    

    /**
     * 根据计算公式 和 公式中每个元素的值 计算出结果
     *
     * @param formula 计算公式
     * @param map     公式中每个元素的值
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T calculate(String formula, Map<String, Object> map) throws Exception {
        if (map != null && !map.isEmpty()) {
            context.putAll(map);
        }
        try {
            return (T) runner.execute(formula, context, null, true, false);
        } finally {
            context.clear();
        }
    }

	public static void main(String[] args) throws Exception {
        String expression = "A+(B-C)*D/E";
        Map<String, Object> map = new HashMap<>();
        map.put("A", 5);
        map.put("B", 4);
        map.put("C", 3);
        map.put("D", 2);
        map.put("E", 2);
        Object calculate = FormulaCalculateUtil.calculate(expression, map);
        System.out.println("计算结果:" + calculate.toString());
    }

}

这个工具类是阿里大佬写的,依赖中有很多其他功能,这里只是使用了其中的公式计算

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值