Java 代码规范

Java开发规范,包括命名规范、代码注释、代码格式、控制语句、异常处理、SQL语法、文件组织、版本发布、安全规范、其他规范。本文档参考[《阿里巴马Java开发手册》][1],并根据实际情况进行了裁剪,本文档会在工作实际中进行修订。

一、命名规范

1.1 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
以下是错误示例:

_name / __name / $Object / name_ / name$ / Object$

1.2 代码中的命名严禁使用拼音与英文混合的方式,严禁使用各种无意义的缩写,或无意义的名称。更不允许直接使用中文的方式。
说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式 也要避免采用。国际通用的名称,可视同英文。

alibaba / taobao / youku / hangzhou

以下是错误示例:

DaZhePromotion  // [打折] 
getPingfenByName()  // [评分] 
int 循环次数 = 3

1.3 类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:(领域模型 的相关命名)DO / BO / DTO / VO 等(后缀)。

MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion

以下是错误示例:

macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

1.4 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从 驼峰形式。

localValue / getHttpMessage() / inputUserId

1.5 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

MAX_STOCK_COUNT

以下是不推荐示例:

MAX_COUNT

1.6 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。

1.7 中括号是数组类型的一部分,数组定义如下:String[] args; 反例:请勿使用 String args[]的方式来定义。

1.8 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。

com.geostar.geoios.util     // 应用工具类包名
MessageUtils    // 此规则参考 spring 的框架结构

1.9 杜绝完全不规范的缩写,避免望文不知义。

AbstractClass “缩写”命名成 AbsClass
condition “缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。

1.10 接口和实现类的命名有两套规则:TODO 整理
对于 Service 和 DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。

public class CacheServiceImpl implements CacheService{
}

如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able 的形式)。

public class AbstractTranslator implements Translatable{
}

1.11 枚举类名要带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。

public enum DealStatusEnum {  
    SUCCESS, UNKOWN_REASON 
}  

1.12 数据结构命名后面要加后缀,以标识数据结构类型,数组类型的命名必须为复数。自定义类型的变量可以采用本身的名称,把首字母改为小写。

String[] persons;               // 数组类型
List<String> personList;        // List数据类型
Map<String,String> fieldMap;    // Map数据类型
Set<Sting> registeredSet;       // Set数据类型
UserInfo userInfo;              // 自定义数据类型

1.13 布尔类型变量命名规则。
临时变量:使用is、has、can作为前缀表示临时逻辑变量,或使用动词的过去式表结果状态。不得使用flag、status、state等表示含义模糊逻辑变量,变量的命名应能地表达逻辑判断的内容。

boolean hasChildNode,hasMoney;  //表示是否有XXX
boolean canWrite,canEat,canFly; //表示是否具备某些条件
boolean isLoading,isHight,isBoy;// 表示是否处于某种状态
boolean removed,found,done,uploaded;// 使用动词的过去式表示某项操作是否完成

成员变量:对于POJO类中的布尔类型成员变量,都不要加is,直接使用去掉is前缀后变量名,否则可能引起序列化错误。

public class Bookstore{
    private boolean closed;
    private boolean getClosed{
        return this.closed;
    }
    private boolean setClosed(boolean isClosed){
        this.closed = isClosed;
    }
}

说明:根据JavaBean的规范,布尔类型(boolean)变量通过IDE自动生成的getter方法为isProperty(),而不是getProperty()。如果成员变量名为isClosed,则通过IDE生成的getter方法是isClosed(),这样会导致序列化框架识判变量名为closed,导致无法正确的赋值。
注意:对于包装的布尔类型(Boolean)变量,通过IDE自动生成的getter方法还是getProperty()。如果成员变量为:Boolean isClosed,则生成的getter方法是getIsClosed(),则不会出现序列化的问题。
1.14 不允许出现任何魔法值(即无意义的常量) 直接出现在代码中,
错误示范

String key = "ID#taobo_"+ tradeID;
    cache.put(key,value);

二、代码注释

2.1 类、类属性、类方法的注释必须使用Javadoc规范,使用/**内容*/格式,不得使用 //xxx 方式。
说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注 释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高 阅读效率。

/**
 * 多行文本注释
 * 多行文本换行
 */
public int add(int p1, int p2) {
}

2.2 所有的抽象方法(包括接口中的方法)必须要用Javadoc注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明:对子类的实现要求,或者调用注意事项,请一并说明。
2.3 所有的类都必须添加功能、创建者、创建时间、版本信息。

/**
* 数学函数工具类
* <p>定义了各类数学函数</p>
* @author      张三
* @date        2018/04/29 
* @version     1.0
*/
public class Math {
}

2.4 方法内部单行注释,使用//注释。方法内部多行注释 使用/* */注释,注意与代码对齐。

/**
 * 获取人员的性别
 */
public int getPersonSex(int personId) {
    // 获取人员信息
    Person person = dao.getPerson(personId);
    
    /**
     * 先获取人员对象,再获取人员性别;
     * 获取对象之前,先要判断对象是否为Null
     */
    if (null == person){
        return -1;
    }
    int sex = person.getSex();
    
    return sex;
}

2.5 所有的枚举类型字段必须要有注释,说明每个数据项的用途。

2.6 所有control层做好,swagger 的注解,必须指明这个接口的作用,入参说明,对于有需要特别注意的点使用note注解说明

2.7 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。
说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后, 就失去了导航的意义。

三、代码格式(推荐)

3.1 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则:

  • 左大括号前不换行。
  • 左大括号后换行。
  • 右大括号前换行。
  • 右大括号后还有 else 等代码则不换行;表示终止右大括号后必须换行。

3.2 缩进采用 4 个空格,禁止使用 tab 字符。
说明:如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。可在 eclipse 中勾选 insert spaces for tabs。

public static void main(String[] args) {
    // 缩进 4 个空格
    String say = "hello";

    //	大括号格式风格
    if (flag == 1) { 
        System.out.println("world");
        // 右大括号前换行,右大括号后有 else,不用换行
    } else { 
        System.out.println("ok");
        // 在右大括号后直接结束,则必须换行
    }
}

3.3 单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

  • 第二行相对第一行缩进 4个空格,从第三行开始,不再继续缩进,参考示例。
  • 运算符与下文一起换行。
  • 方法调用的点符号与下文一起换行。
  • 在多个参数超长,逗号后进行换行。
  • 在括号前不要换行。
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,换行缩进 4个空格,并且方法前的点符号一起换行
sb.append("zi").append("xin")...
    .append("huang")...
    .append("huang")...
    .append("huang");
method(args1, args2, args3, ...,
    argsX);

以下是错误示例:

StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,不要在括号前换行
sb.append("zi").append("xin")...append
    ("huang");

//参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1, args2, args3, ...
    , argsX);

3.4 IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式, 不要使用 windows 格式。

四、控制语句

4.1 在一个 switch 块内,每个 case 要么通过 break/return等来终止,要么注释说明程 序将继续执行到哪一个 case 为止;在一个switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。

4.2 在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:
错误示例

if (condition) statements;

4.3 尽量少用 else, if-else 的方式可以改写成:
通过return 返回,结束执行逻辑

if(condition){
    ...
    return obj;
}

// 接着写 else 的业务逻辑代码;

说明:如果非得使用 if()…else if()…else…方式表达逻辑,【强制】请勿超过 3 层

4.4 不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

//伪代码如下
boolean existed = (file.open(fileName, "w") != null) 
    && (...) || (...); 
if (existed) {
    ...
}

以下是错误示例:

if ((file.open(fileName, "w") != null) && (...) || (...)) {
    ...
}

五、异常处理

5.1 不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException /NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。

if(obj != null) {
    ...
}

以下是错误示例:

try { 
    obj.method();
} catch(NullPointerException e){
    ...
}

5.2 异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。

5.3 对大段代码进行 try-catch,这是不负责任的表现。catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理。

5.4 有 try 块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。

5.5 finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。

5.6 不能在 finally 块中使用 return,finally 块中的 return返回后方法结束执行,不会再执行 try 块中的 return 语句。

5.7 注意防止 NPE(空指针异常) 产生的场景:

  • 返回类型为包装数据类型,有可能是 null,返回 int 值时注意判空。 反例:public int f(){ return Integer 对象}; 如果为 null,自动解箱抛 NPE。
  • 数据库的查询结果可能为 null。
  • 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
  • 远程调用返回对象,一律要求进行 NPE 判断。
  • 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
  • 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。

5.8 对于系统中的异常,捕获必须进行处理,如果不处理,只是简单的打印日志,直接交给spring框架的全局异常捕获机制即可。

六、数据库规约

6.1 建表原则,表名字段名统一小写,使用下划线分割
6.2 建表类型选择(推荐)

  • 整数,当明确知道大小低于127的时候,选用tinyint,其他情形选用int
  • 小数,没有特殊情况不要使用小数,使用字符串代替
  • 字符串 使用varchar,禁止统一varchar(255),应当按需取最小值,eg:varchar(32) ,字符多时使用text
  • 图片等非文字类容,使用blob,
  • 日期,使用datatime 存储yyyy-MM-dd HH:mm:ss, 后端接参有统一格式处理,不用再单独处理,前端根据需要进行相关格式化

6.3 不要使用 count(列名)或 count(常量)来替代count(*),count(*)就是 SQL92定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

select count(*) from student;

6.4 count(distinct col)计算该列除NULL之外的不重复数量。
注意:count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。

6.5 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题。

6.6 在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。

6.7 不得使用外键与级联,一切外键概念必须在应用层解决。
概念解释:学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。 如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,则为级联更新。 外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数 据库更新风暴的风险;外键影响数据库的插入速度。

6.8 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
6.9 禁止使用数据库函数,理由同上

6.10 数据库表结构修改,强制写SQL脚本修改,不得直接使用工具手动修改。
6.11 数据

八、API接口规范

8.1 API命名规范
完整的API格式如下:

[http|https]://[ip]:[port]/[module]</subModule>/[action]
  • [http|https] http协议或https协议。
  • [ip]和[port] IP地址和端口,如:192.168.100.104:8080、www.geostaryun.com:8088等
  • [module]</subModule> 模块或业务名称,可以包括子模块,子模块为可选,如:book/category、book/sale等。
  • [action] 执行的动作,如:add、update、delete、list、get等。
    说明:命名必须统一使用驼峰命名法。如upperCamelCase、queryCandidate、queryProcessInstance等。

接口中的action命名规范:

  • 新增接口,如新增一个地址、新增一名用户
    使用add名称或以add为前缀,如:http://192.168.0.1:8088/user/add

  • 修改接口,如修改用户信息
    使用update名称或以update为前缀,如:http://192.168.0.1:8088/user/update

  • 删除接口,如删除用户信息
    使用delete名称或以delete为前缀,如:http://192.168.0.1:8088/user/delete

  • 获取接口,如获取用户信息
    使用get名称或以get为前缀,如:http://192.168.0.1:8088/user/get、http://192.168.0.1:8088/api/v1/user/getAlias

  • 获取列表接口,如获取一组用户信息
    使用list名称或以list为前缀,如:http://192.168.0.1:8088/user/list、http://192.168.0.1:8088/api/v1/user/listUser
    如果需要不分页获取所有数据,加上all标识,如:http://192.168.0.1:8088/user/listAll

  • 关联数据接口,如给用户关联角色、给角色关联权限
    使用bind名称或以bind为前缀,如:http://192.168.0.1:8088/user/bindRole、http://192.168.0.1:8088/api/v1/role/bindPermission

  • 移动关联关系接口,如移除用户的管理员角色
    使用remove名称或以remove为前缀,如:http://192.168.0.1:8088/user/removeRole、http://192.168.0.1:8088/api/v1/role/removePermission

  • 其他接口
    文件上传,使用upload名称或以upload为前缀。如:http://192.168.0.1:8088/doc/upload、http://192.168.0.1:8088/api/v1/doc/uploadImage
    批量操作,使用batch名称或以batch为前缀。如批量上传、批量增加。http://192.168.0.1:8088/api/v1/doc/batchUpload、http://192.168.0.1:8088/user/batchAdd
    接口功能,不在上述范围内的请谨慎命名,与上级协商补充。
    8.2 请求参数规范

  • 请求方式
    简单普通数据获取与查询:GET
    数据修改、删除:POST
    身份验证与敏感数据:POST
    参数超过3个的查询:POST
    禁止使用put, delete 等请求方式,因为部分框架可能不支持,比如feign框架

  • 请求入参
    根据实际需要,参数最小化原则。不需要的参数一律不暴露出来,比如新增的时候不需要ID,就不要暴露这个字段给前端。可以定义多个VO对象
    参数的定义最好由前端定义。依赖倒置原则,前端更接近业务,有前端制定标准。

  • 请求出参
    统一使用com.wiilead.it.common.vo.ResponseVO 结构, 相关返回字段推荐由前端定义。
    示例:

    public ResponseVO saveAssetsType(@RequestBody AssetsAllocationVO assetsAllocationVO) {
        try {
            JSONObject jsonObject = assetsAllocationService.saveAssetsAllocation(assetsAllocationVO);
            if (jsonObject!=null&& !(boolean)(jsonObject.get("flag"))){
                return ResponseVO.success("不能操作审批中或已调拨的订单", null);
            }
            return ResponseVO.success("资产调拨新建成功", null);
        } catch (Exception e) {
//            e.printStackTrace();
        	logger.error("{}",e);
            return ResponseVO.fail("资产调拨新建失败", null);
        }
    }

(7)列表请求特殊规范
pageNo :页数,从1开始。例如:{ “pageNo”: 1 }
pageSize : 每页数量。

九、项目结构

  • 独立微服务

十、JAVA技术选择规范

(1)ORM框架采用,mybatis-plus 拒绝使用任何其他第三方ORM框架,说明:mybatis-plus 是mybatis 的封装,针对单表操作使用简单,保留了所有的mybatis 的功能
尽量使用单表查询,将多表查询拆分。 为了数据库兼容,批量查询,避免循环查询。

(2)API接口,使用统一的ResponseVO对象接收,参考com.wiilead.it.common.vo.ResponseVO.java 该对象有成功和失败的方法封装
使用示例:

     try {
        	Boolean modelExist = modelService.modelExist(bpmnModelVO);
        	if(modelExist) {
        		return ResponseVO.fail("模板标识不能重复",null);
        	}
            modelService.modelSave(bpmnModelVO);
            return ResponseVO.success("模板信息存储成功",null);
        }catch (Exception e){
            return ResponseVO.fail("模板信息存储失败",null);
        }

(3) 内部Feign 调用,建议直接返回象需要对,避免直接返回Response

推荐使用

@FeignClient(value = ServiceNameConstants.WIILEAD_ALM_SERVICE, fallbackFactory = RemoteBugServiceFallbackFactory.class)
public interface RemoteBugService {

	@GetMapping("/bug/fegin/findAllBugVO")
	 List<BugVO> findAllBugVO(@RequestBody BugVO vo, @RequestHeader(SecurityConstants.FROM) String from);
	
} 

避免使用

@FeignClient(value = ServiceNameConstants.WIILEAD_PM_SERVICE, fallbackFactory = RemoteProjectInfoServiceFallbackFactory.class)
public interface RemoteProjectInfoService {
    @GetMapping("/projectInfo/findAllFeign/{strategy}")
    ResponseVO findAll(@PathVariable("strategy") String strategy, @RequestHeader(SecurityConstants.FROM) String from);
}

(4)VO 对象是纯净的VO,它只有属性字段。避免依赖于任何第三方jar,推荐Service层返回model 中的对象,controller层返回VO中的对象

(5)所有的表数据带上is_delete 字段,类型char,长度1. 值:0:代表未删除,1代表删除。不要使用魔法值,使用DeleteFlageEnum枚举!所有删除走逻辑删除。office_id, create_by, create_date, update_by, update_date.

(6)删除,不推荐使用级联删除。可以给出提示,有关联关系,让用户自己处理关联数据

(7)父子自关联关系表,根节点避免空值,推荐使用:0 作为根节点的ParentId

十一、 缓存规范

(1) 项目中所有的缓存使用redis 缓存,使用Redistemplate 操作Redis 数据,避免任何其他方式的缓存

(2) 选取合适的Redis 数据结构,避免大量的Redis 数据操作

(3) 查询数据,对于走了Redis 没有找到的,记得要去数据库中查找,不能只走缓存

第三方库

所有的jar依赖都使用wiilead-cloud 父pom文件中的依赖,尤其是依赖版本

附1:常见名称命名规范

关于缩写名词的说明:
a. 尽可能多用全称,少用缩写;
b. 单个词汇出现的时候,不允许缩写,已固定的缩写(如Id,I是大写)除外;
c. 两个或以上单词都可以缩写的时候,只允许一个单词使用缩写,建议第一个单词使用缩写。

  • 工作流
序号术语命名缩写
1流程process
2流程实例processInstanceprocInst
3活动(任务)定义activity
4活动任务实例activityInstanceactInst,taskInst,task
5待办todo/TODO
6已办done/DONE
  • 系统名称
序号术语命名缩写
1工作台workbench
2文档document
3系统system
4视图view
5流程workflow
6菜单menu
7角色role
8消息message
9通知notice
10编码code
11部门department
  • 项目管理
序号术语命名缩写
1战略strategy
3项目组合projectGroup
4预算budget
5合同contract
6采购purchase
7需求requirement
8资源resource
9产品product
10版本version
11迭代sprint
12故事story
13看板board
14团队team
15计划schedule
16任务task
17里程碑milestone
18问题problem
19风险risk
20变更change
21报告report
22项目project
  • 测试
序号术语命名缩写
1测试test
2测试用例testCase
3缺陷bug
  • 操作
序号术语命名缩写
1新增add
2修改update
3删除delete
4查询query
5审批approve
6通过pass
7驳回reject
8撤销revoke
9上传upload
10下载download
11复制copy
12拆分split
13完成complete
14关闭close
15变更change
16评价appraise
17申请apply
18保存save

TODO:补充所有基本命名

附2:字典编码规范

  1. 后端所有使用字典的地方使用枚举。每一个数据字典对应一个枚举文件 在bpmn 模块的test目录下存在代码生成工具com.wiilead.it.bpmn.DictUtils
    制定对应的字典code,即可自动生成对应的枚举文件
  2. 步骤一,有代码生产工具,可以自动生成,定义字典的时候定义良好的Code 有助于生成可读性良好的代码

备注:之前的字典码,经历过修改,和规范相对于的字典码已经修改,对应表在SVN上查看
SVN地址:https://120.24.183.170/svn/Wiilead/设计文档/wiilead 产品/数据字典整理.xlsx
注意:字典和枚举的使用场景
字典编码使用XXX三位编码,避免使用1,2,3
1XX-9XX 共9大类

  • 流程审批类 6XX
序号状态编码描述
1流程审批(所有的审批类)6XX
2草稿600
3审批中610
4审批完成620
5驳回630
6废弃640
  • 级别类,数字越小越差,越不重要
序号状态编码描述
1优先级3XX(所有的级别类)
2300
3310
4320
5较高330
6非常高340
  • 结果状态类
序号状态编码描述
1通过不通过2XX
2通过200
3不通过210
  • 处理过程类
序号状态编码描述
1处理过程5XX
2待处理500
3处理中510
4处理完成520
5关闭530

附2:代码生成(Eclipse)

在bpmn模块下面,有一个EntityUtils 工具类,用来生成代码。
包括controller VO 等多个文件。

在bpmn模块下面,有一个DictUtils 工具类,可以用来生成字典枚举

附3:代码规范检查插件(Eclipse)

(1)开发环境

  • Eclipse Juno+
  • maven3.+
  • JDK 1.7+

(2)安装插件

  • Help >> Install New Software,在地址栏中输入地址:[https://p3c.alibaba.com/plugin/eclipse/update][3]
    ![安装插件][4]
  • 按安装提示完成安装。

(3)使用插件

  • 在工程目录中,右键点击目录或Java文件,在弹出的菜单中选择Alibaba Coding Guidelines Analyse,或者按快捷键Ctrl+Alt+Shift+J
  • 在代码编辑器窗口,可以查看到所有不规范代码的提示。
    ![代码规范检查插件][5]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨時隨地-在等待

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值