java代码开发的通用规范


前言

本文的java代码开发的通用规范,适用于新手上路和大部分职场

一、命名风格

  1. 采用英文全称进行命名,保持各层级命名基本统一。
  2. 命名英文单词用全称,尽量不要使用简称。
  3. 模块包命名全部使用小写,命名方式:com.项目名称.项目模块.系统分层(如:com.cdcw.modules.controller)。
  4. 类名使用 UpperCamelCase 风格,遵从大驼峰形式,多个英文单词以大写字母间隔,尽量避免使用缩写,不要使用’_’’-’等特殊符号,以下情形例外:DO / BO / DTO / VO / AO
    正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
    反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
  5. 属性定义位置在类定义开始,按照 public,protected,package,private 顺序定义。
  6. 方法命名采用“动作+属性” 的方法。并且,动作以小写字母开始,属性以大写字母开始。常用的动作有:is、get、set、save、add、del 等。示例:getName()。
  7. 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
    正例: localValue / getHttpMessage() / inputUserId
  8. 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚。
    正例:MAX_STOCK_COUNT 反例:MAX_COUNT
  9. 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
  10. Model 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
    反例:定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是
    isDeleted(),RPC 框架在反向解析的时候,“以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
  11. 不要使用 SqlServer 数据库保留字段命名属性。
  12. 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
    正例:CacheManagerImpl 实现 CacheManager 接口。
  13. 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
    正例:从远程仓库拉取代码的类命名为 PullCodeFromRemoteRepository
    反例:变量 int a;的随意命名方式。
  14. 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是 与接口方法相关,并且是整个应用的基础常量。
    正例:接口方法签名:void f(); 接口基础常量表示:String COMPANY =“alibaba”;
    反例:接口方法定义:public abstract void f();
    说明:JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默 认实现。
  15. 枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词用下划线隔开。
    说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
    正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKOWN_REASON。
  16. 各层命名规约:
    A) Service/DAO 层方法命名规约
    1) 获取单个对象的方法用 get 做前缀。
    2) 获取多个对象的方法用 list 做前缀。
    3) 获取统计值的方法用 count 做前缀。
    4) 插入的方法用 save/insert 做前缀。
    5) 删除的方法用 remove/delete 做前缀。
    6) 修改的方法用 update 做前缀。

二、变量定义

  1. 不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。
    说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
    正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类ConfigConsts 下。

三、代码格式

  1. 废弃的/无用的代码一律直接删除,禁止以注释等方式保留。如需查看历史代码,通过 SVN/Git 的 history 找回(无用的代码会干扰团队成员的阅读/或被误调,越积越多会导致代码维护成本增高)。
    补充:master 按照规范,开发分支允许以注释方式保留。
  2. 接口类中的方法不需添加 public 修饰符。
  3. 需要序列化的 Bean 类统一实现 Serializable 接口并用 IDE 生成
    serialVersionUID。
Eg:
public class MyEntity implements Serializable {
private static final long serialVersionUID = 123456L;
...
}
  1. 常用字符串统一定义在常量类里,如: “utf-8”, “yyyyMMdd”。
  2. 避免数字类型比较的坑: 统一采用 equals 进行比较其值,不用==进行比较,避免踩坑。
  3. 方法单一职责: 单个方法代码行数控制在 100 行以内,超长的需要拆分(拆分成多个方法或类)。
  4. 遵循: Don’t Repeat Yourself,即 DRY 原则。避免进行简单的复制粘贴修改,当出现重复代码时思考是否封装。
  5. 可异步执行的耗时操作采用异步处理:使用 Spring @Async 或 MQ,或夜间 Timer 定时。
  6. 常用数据考虑缓存,存入 Redis,设置缓存过期时间。
  7. 保证写一致性的逻辑,在外层方法上添加事务。
  8. 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:
    1) 左大括号前不换行。
    2) 左大括号后换行。
    3) 右大括号前换行。
    4) 右大括号后还有 else 等代码则不换行 表示终止的右大括号后必须换行。
  9. 左小括号和字符之间不能出现空格;同样,右小括号和字符之间也不能出现空格。
    反例:if (空格 a == b 空格)
  10. if/for/while/switch/do 等保留字与括号之间都必须加空格。
  11. 任何二目、三目运算符的左右两边都需要加一个空格。
    说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
  12. 注释的双斜线与注释内容之间有且仅有一个空格。
    正例:// 注释内容,注意在//和注释内容之间有一个空格。
  13. 方法参数在定义和传入时,多个参数逗号后边必须加空格。
    正例:下例中实参的"a",后边必须要有一个空格。method(“a”, “b”, “c”)。
  14. IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符
    使用 Unix 格式,不要使用 Windows 格式。
  15. 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
    说明:没有必要插入多个空行进行隔开。
  16. Bean 属性拷贝推荐用 Spring BeanCopier 或者 Mapstruct,避免
    Apache BeanUtils 或调用 setter。

四、OOP 规则

  1. 所有的覆写方法,必须加@Override 注解。
  2. 不能使用过时的类或方法。
  3. Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
    正例:“test”.equals(object);
    反例:object.equals(“test”);
  4. 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
    说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑, 推荐使用 equals 方法进行判断。
  5. RPC 方法的返回值和参数必须使用包装数据类型。
  6. 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init方法中。
  7. 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
  8. 循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
    说明:反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
    反例:
	String str = "start";
	for (int i = 0; i < 100; i++) {
	str = str + "hello";
	}
  1. 慎用 Object 的 clone 方法来拷贝对象。
    说明:对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象的拷贝。

五、 集合处理

  1. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
    说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
    正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()]; 
array = list.toArray(array);

反例:
直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。
2. 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请
使用 Iterato 方式,如果并发操作,需要对 Iterator 对象加锁。
正例:

Iterator<String> iterator = list.iterator(); 
while (iterator.hasNext()) {
	String item = iterator.next();
	if (删除元素的条件) {
		iterator.remove();
 	}
}

反例:

List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
	if ("1".equals(item)) {
	list.remove(item);
 	} 
}
  1. 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

六、控制语句

  1. if/else/for/while 语句后必须使用大括号,即使只有一行代码。(需求总是变化的,一行是暂时的)。
  2. 嵌套层次过多的代码块利用反向思维缩减层次。
  3. 禁止在循环中执行耗时的操作,如在循环中执行 SQL 语句/调用外部服务等。
  4. 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止。
  5. 除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
    说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
    正例:
// 伪代码如下
final boolean existed = (file.open(fileName, "w") != null) && (...) ||
(...);
if (existed) {
...
}

反例:

if ((file.open(fileName, "w") != null) && (...) || (...)) {
...
}
  1. 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。
  2. 下列情形,需要进行参数校验:
    1) 调用频次低的方法。
    2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
    3) 需要极高稳定性和可用性的方法。
    4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。

七、注释规约

  1. Java 文件统一添加固定 Header,通过 IDE 统一配置(code
    templates),主要标注该类的作用,对类进行简单描述;记录类的作者和类的创建日期。
Eg:
/**
* <Description>
* @author  Wings_of_L
* @version 1.0
* @date ${YEAR}/${MONTH}/${DAY}
*/
  1. 接口和方法统一添加 Java Doc 标准注释,描述该方法的主要意义,对方法的参数进行说明。
Eg:
/**
*缓存 key-value 并设定过期时间
* @param key 缓存对象的 key
* @param valueList 缓存对象
* @return 缓存是否成功
*/
<T> boolean addList(String key, List<T> valueList);
  1. 属性注释必须说明该属性意义。
Eg:
/**
* 用户姓名
*/
private String name;
  1. Dao 层、Service 层接口中的方法都必须按照方法的注释要求进行注释。
Eg:
/**
* 获取成员列表
* @author Wings_of_L
* @param page 分页
* * @param orgId ID
* @return 成员列表
*/
List<UserImpl> getList(Page page, Long orgId);
/**
* 列表查询
* @author Wings_of_L
* @param page 分页
* @param content 关键字
* @return
*/
List<UserDetail> getList(Page page, String content);
  1. 需暂留的弃用类/方法添加 @Deprecated 废弃标记 和 @see 链接指向新接口
Eg:
/* @see com.wjw.common.cache.redis.JedisSentinelPoolUtil*/ 
@Deprecated
public class JedisUtils {}
  1. 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容 */格式,不得使用// xxx 方式。
  2. 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。说明:对子类的实现要求,或者调用注意事项,请一并说明。
  3. 方法内部单行注释,在被注释语句上方另起一行,使用// 注释。方法内部多行注释 使用/* */注释,注意与代码对齐。
  4. 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
  5. 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
    说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,就失去了导航的意义。
  6. 谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
  7. 对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
  8. 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
  9. 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
    1) 待办事宜(TODO):( 标记人,标记时间,[预计处理时间]) 表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc 还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个Javadoc 标签)。
    2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

八、异常处理

  1. 下层有常异,必须抛向上一层,捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容,controller 禁止向外部抛出系统底层异常及错误码。
  2. 为了使系统能够更好的跟踪运行情况,必须把底层异常放入新异常中。
  3. 如果一个层要抛出多个异常,那么所有自定义异常必须统一继承一个父类异常。这样上层可以通过父类异常捕获。
  4. 异常统一在控制器层处理,控制器层以下的层次在处理异常时,只需要把低层的异常类放到本层约定的异常类中,并抛出,如有需要可以加适当的异常消息。
  5. 调用外部服务等可能异常的代码块,用 try/catch 代码块捕获并在catch 中记录异常跟踪日志及业务逻辑处理。
  6. 禁止吞掉异常信息
    1)禁止 catch 里不做任何记录和处理,吞掉异常及其堆栈信息;
    2)禁止: logger.error(“XXX 操作异常”) 或 logger.error(“XXX 操作异
    常”+e) 或 e.printStackTrace(); 3)正确: logger.error(“XXX 操作异常”, e)
  7. 对于非预期的条件,尽量增加 else 记录跟踪日志。
  8. 禁止通过 System.*.out()打印日志(单元测试例外)。
  9. 日志记录 logger 需使用 Slf4J/log4j 代理声明,禁止绑死具体日志系统的 API,避免后期更换日志组件导致代码的大量改动。如采用了 lombok,可用 @Slf4j 注解替代以上声明。
Eg:
private static final Logger log = 
LoggerFactory.getLogger(OrganizationServiceImpl.class);
  1. 对 trace/debug/info 级别的日志输出,必须使用占位符形式,避免直接 String 拼接异常信息(即使日志级别不匹配也会执行拼接操作空耗资源)。
Eg:
log.debug("当前用户 id: {} ,操作对象: {}=>{} ", userId, objectType,
objectId);

或条件输出形式如:

if(log.isDebugEnabled()){
log.debug("当前用户 id:+id+” ,操作对象:+ objectType +=>+ objectId);
}
  1. 避免 NPE(NullPointException)
    1)equals 比较将非空对象前置如"true".equals(request.getParameter(“isXx”)),即使后者为空也不会导致NPE。
    2)数据库字段可空的映射属性使用包装类型定义:如基本数据类型的 int 映射到数据库的 null 值将产生 NPE,而用吧包装类型Integer 则不会。
    3)可能为空的变量进行必要判空,并在非预期条件下打印必要的跟踪日志,不但避免 NPE,还非常便于跟踪调试。
    4)级联调用 obj.getA().getB().getC() 易产生 NPE,先进行判空或使用 JDK8的 Optional 类包装。
    5)调用 Dubbo 接口拿到返回值时,进行判空。
    6)封装统一的判空类用于常用类型的判空,代码需要判空时统一调用即可。
    如 XX.isEmpty(), XX.isNotEmpty()。
    12.如果一个方法业务逻辑比较复杂,在一个方法中进行处理,该方法将会臃肿、不宜阅读和维护,建议每个方法代码行不要大于 50 行,建议多使用方法重构。建议对方法中需要进行说明的地方进行注释,如参数、方法中多个步骤的业务逻辑处理。(备注,代码行数指的是代码语句行数,不是编辑器行数)。

九、编码检查

  1. 后端服务及其他需要自测的代码,编写对应的单元测试类,统一采用 Junit,禁止直接在原 Java 类中写 main()方法自测。
Eg:
Junit 单元测试类示例:
public class TestApollo {
@Test // 标记为单元测试方法
public void testApolloConfig(){
String appId = Foundation.app().getAppId();
// 预期结果断言
Assert.assertNotNull(appId);
 }
}

十、编码安全

  1. 用户敏感数据禁止列表批量展示/导出,必须进行脱敏,如客户手机号,身份证号。
  2. 禁止直接写死登录账号密码,给系统造成巨大的安全风险;
  3. 禁止代码/配置文件中出现生产环境的明文密码(改为加密存放)。
  4. 禁止使用侵犯他人版权的代码,字体,图片。
  5. 对用户输入数据必须做有效性检查。

十一、其他约定

  1. 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
    说明:不要在方法体内定义:
    Pattern pattern = Pattern.compile(规则);
  2. 注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够 取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后 取整,直接使用 Random 对象的 nextInt 或 者 nextLong 方法。
  3. 获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();
  4. 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。

十二、SqlServer 数据库

建表规约

  1. 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
    说明:SqlServer 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
    正例:feitian_admin,rdc_config,level3_name
    反例:Feitian_Admin,rdcConfig,level_3_name
  2. 禁用保留字,如 desc、range、match、delayed 等,请参考 SqlServer官方保留字。
  3. 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 dx_字段名。
    说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
  4. 小数类型为 decimal,禁止使用 float 和 double。
  5. 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
  6. varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text。
  7. 表必备三个字段:id,create_time, update_time, delete_flag
  8. 对于 Boolean 型的字段,采用 decimal 类型
  9. 表和字段都需要添加注释信息。
  10. 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
    说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
  11. 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

索引规约

  1. 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
    说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
  2. 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
    说明:即使双表 join 也要注意表索引、SQL 性能。
  3. 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
    说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
  4. 创建索引时避免有如下极端误解:
    1)宁滥勿缺。认为一个查询就需要建一个索引。
    2)宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
    3)抵制惟一索引。认为业务的唯一性一律需要在应用层通过“先查后插”方式解决。

SQL 语句

  1. 不要使用 count(列名)或 count(常量)来替代 count(),count()是
    SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
    说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为NULL 值的行。
  2. count(distinct col)计算该列除 NULL 之外的不重复行数,注意
    count(di col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。
  3. 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL。
  4. 不得使用外键与级联,一切外键概念必须在应用层解决。
  5. 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
  6. in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。
  7. 如果有全球化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符的区别。
    说明: SELECT LENGTH(" 轻 松 工 作 ") ; 返 回 为 12 SELECT CHARACTER_LENGTH(“轻松工作”);返回为 4 如果需要存储表情,那么选择 utfmb4 来进行存储,注意它与-8 编码的区别。
  8. 更新数据表记录时,必须同时更新记录对应的 update_time 字段值为当前时间。
  9. @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值