关于如何在项目中实现以注解的方式添加数据库字段

本文介绍如何使用自定义注解MyColumn配合MyBatis框架,实现实体类字段与数据库表字段的自动同步。通过在实体类中添加注解属性,系统能自动检测并创建数据库中缺失的字段。

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

一、首先新建一个包annotation,在这个包下面写一个类MyColumn

package hry.utils.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 拓展下Column
 * 
 */
@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface MyColumn {

    String name() default "";

    boolean unique() default false;

    boolean nullable() default true;

    boolean insertable() default true;

    boolean updatable() default true;

    String columnDefinition() default "";

    String table() default "";

    int length() default 255;

    int precision() default 0;

    int scale() default 0;

    String comment() default "";
    
    String type() default "varchar";
}

这个类就相当于数据库的实体

二、再建一个mybatis的包 里面新建4个类分别是AnnotationUtil、ClassUtil、DBHelper、MybatisGeneration

AnnotationUtil类

package hry.utils.mybatis;

import org.apache.log4j.Logger;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;


public class AnnotationUtil {
    public static Properties jdbc;
    private static Logger logger = Logger.getLogger(AnnotationUtil.class);
    public static void validAnnotation(List<Class<?>> clsList) {
        InputStream insjdbc = AnnotationUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
        jdbc = new Properties();
        try {
            jdbc.load(insjdbc);
        } catch (IOException e) {
            e.printStackTrace();
        }


        //开始加字段
        Long start = System.currentTimeMillis();
        if (clsList != null && clsList.size() > 0) {

            for (Class<?> forName : clsList) {
                if (forName != null) {
                    Field[] fs = forName.getDeclaredFields();


                    //先拿到@Table
                    Table annotationTable = forName.getAnnotation(Table.class);
                    String columns = "";
                    if (annotationTable != null) {
                        String name = "";
                        try {
                            name = annotationTable.name();
                            columns = DBHelper.getColumns(name);
                        } catch (Exception e) {
                            e.printStackTrace();
                            logger.error(name);
                            continue;
                        }

                    } else {//如果不是实体类直接跳过
                        continue;
                    }

                    if (!"".equals(columns) && null != columns) {
                        for (Field field : fs) {
                            if (!field.isAccessible()) {
                                field.setAccessible(true);
                            }
                            StringBuffer sb = new StringBuffer("ALTER TABLE " + annotationTable.name() + " ADD ");

                            //取字段上@Column的name
                            Column annotationColumn = field.getAnnotation(Column.class);
                            if (annotationColumn != null) {
                            	try {
                                    if (!"".equals(annotationColumn.name()) && !columns.contains(annotationColumn.name()) && annotationColumn.columnDefinition() != null && (!"".equals(annotationColumn.columnDefinition()))) {
                                        //如果不包含,说明这个字段实体有,数据库没有
                                    	logger.error(annotationColumn.name());
                                        //先创建这个字段
                                        sb.append(annotationColumn.name() + " ");
                                        //判断类型
                                        sb.append(annotationColumn.columnDefinition());


                                        //是否为空
                                        /*if (annotationColumn.nullable()) {
                                            sb.append("DEFAULT NULL ");
                                        } else {
                                            sb.append("DEFAULT NOT NULL ");
                                        }*/
                                        //是否唯一
                                        /*if (annotationColumn.unique()) {
                                            sb.append("UNIQUE ");
                                        }
*/
                                        logger.error("sql  ==  " + sb.toString());
                                        DBHelper.execute(sb.toString());
                                        //logger.error(sb);

                                        //字段上是否含有@Id 和 @GeneratedValue,一般这两个字段是一起用的
                                        Id annotationId = field.getAnnotation(Id.class);
                                        GeneratedValue annotationGeneratedValue = field.getAnnotation(GeneratedValue.class);
                                        if (annotationId != null && annotationGeneratedValue != null) {
                                            String sql = "alter table " + annotationTable.name() + " change " + annotationColumn.name() + " " + annotationColumn.name() + " " + annotationColumn.columnDefinition() + " AUTO_INCREMENT PRIMARY KEY";
                                            DBHelper.execute(sql.toString());
                                            logger.error(sql);
                                        }
                                    }
								} catch (Exception e) {
									//e.printStackTrace();
									logger.error(annotationTable.name());
								}
                      
                            }
                        }
                    }
                }
            }
        }
        logger.error("加字段结束,耗时" + (System.currentTimeMillis() - start) / 1000 + "秒");
    }
}

ClassUtil类

package hry.utils.mybatis;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

public class ClassUtil {

    /**
     * 通过包名获取包内所有类 
     *
     * @param pkg
     * @return
     */
    public static List<Class<?>> getAllClassByPackageName(String pkg) {
        //String packageName = pkg.getName();
        // 获取当前包下以及子包下所以的类  
        List<Class<?>> returnClassList = getClasses(pkg);
        return returnClassList;
    }

    /**
     * 通过接口名取得某个接口下所有实现这个接口的类 
     */
    public static List<Class<?>> getAllClassByInterface(Class<?> c) {
        List<Class<?>> returnClassList = null;

        if (c.isInterface()) {
            // 获取当前的包名  
            String packageName = c.getPackage().getName();
            // 获取当前包下以及子包下所以的类  
            List<Class<?>> allClass = getClasses(packageName);
            if (allClass != null) {
                returnClassList = new ArrayList<Class<?>>();
                for (Class<?> cls : allClass) {
                    // 判断是否是同一个接口  
                    if (c.isAssignableFrom(cls)) {
                        // 本身不加入进去  
                        if (!c.equals(cls)) {
                            returnClassList.add(cls);
                        }
                    }
                }
            }
        }

        return returnClassList;
    }

    /**
     * 取得某一类所在包的所有类名 不含迭代 
     */
    public static String[] getPackageAllClassName(String classLocation, String packageName) {
        // 将packageName分解  
        String[] packagePathSplit = packageName.split("[.]");
        String realClassLocation = classLocation;
        int packageLength = packagePathSplit.length;
        for (int i = 0; i < packageLength; i++) {
            realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
        }
        File packeageDir = new File(realClassLocation);
        if (packeageDir.isDirectory()) {
            String[] allClassName = packeageDir.list();
            return allClassName;
        }
        return null;
    }

    /**
     * 从包package中获取所有的Class 
     *
     * @param
     * @return
     */
    private static List<Class<?>> getClasses(String packageName) {

        // 第一个class类的集合  
        List<Class<?>> classes = new ArrayList<Class<?>>();
        // 是否循环迭代  
        boolean recursive = true;
        // 获取包的名字 并进行替换  
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things  
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去  
            while (dirs.hasMoreElements()) {
                // 获取下一个元素  
                URL url = dirs.nextElement();
                // 得到协议的名称  
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上  
                if ("file".equals(protocol)) {
                    // 获取包的物理路径  
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件  
                    // 定义一个JarFile  
                    JarFile jar;
                    try {
                        // 获取jar  
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        // 从此jar包 得到一个枚举类  
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代  
                        while (entries.hasMoreElements()) {
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件  
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的  
                            if (name.charAt(0) == '/') {
                                // 获取后面的字符串  
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同  
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包  
                                if (idx != -1) {
                                    // 获取包名 把"/"替换成"."  
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包  
                                if ((idx != -1) || recursive) {
                                    // 如果是一个.class文件 而且不是目录
                                    String pattern = "hry/[a-zA-Z]*/[a-zA-Z]*/model/[a-zA-Z]*\\.class";
                                    if (name.endsWith(".class") && !entry.isDirectory()) {

                                        if(!name.contains("model")){
                                            continue;
                                        }
                                        if(!Pattern.matches(pattern, name)){
                                            continue;
                                        }

                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            // 添加到classes  
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

    /**
     * 以文件的形式来获取包下的所有Class 
     *
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) {
        // 获取此包的目录 建立一个File  
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回  
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录  
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)  
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件  
        for (File file : dirfiles) {
            // 如果是目录 则继续扫描  
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String name = file.getPath();
                if(!name.contains("model")){
                    continue;
                }
                String pattern = "hry/[a-zA-Z]*/[a-zA-Z]*/model/[a-zA-Z]*\\.class";
                String clName = name.replace("\\","/").split("classes")[1].substring(1);
                if(!Pattern.matches(pattern,clName)){
                    continue;
                }
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    //e.printStackTrace();
                    //System.out.println("类"+className+"不存在");
                }
            }
        }
    }

    public static void main(String[] args) {
        List<Class<?>> clsList = ClassUtil.getAllClassByPackageName("hry");

        AnnotationUtil.validAnnotation(clsList);
    }
}  

DBHelper类

package hry.utils.mybatis;

import org.apache.log4j.Logger;

import java.sql.*;

public class DBHelper {
    public static final String name = "com.mysql.jdbc.Driver";
    private static Logger logger = Logger.getLogger(DBHelper.class);
    public Connection conn = null;
    public PreparedStatement pst = null;

    public DBHelper(String sql) {
        try {
            Class.forName(name);//指定连接类型
            conn = DriverManager.getConnection(AnnotationUtil.jdbc.getProperty("jdbc.url"), AnnotationUtil.jdbc.getProperty("jdbc.username"), AnnotationUtil.jdbc.getProperty("jdbc.password"));//获取连接
            pst = conn.prepareStatement(sql);//准备执行语句
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void close() {
        try {
            this.conn.close();
            this.pst.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void execute(String sql){
        try {
            DBHelper db = new DBHelper(sql);
            db.pst.execute();
            db.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static String getColumns(String tableName){
        DBHelper db = new DBHelper("show full columns from "+tableName+" ");
        StringBuffer sb = new StringBuffer();
        try {
            ResultSet ret = db.pst.executeQuery();
            while (ret.next()) {
                sb.append(ret.getString(1) + ",");
            }
            ret.close();
            db.close();//关闭连接
            return sb.deleteCharAt(sb.length()-1).toString();
        } catch (SQLException e) {
            //e.printStackTrace();
            logger.error("表"+tableName+"不存在");
        }
        return null;
    }
}  

MybatisGeneration类

package hry.utils.mybatis;

import hry.utils.annotation.MyColumn;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Properties;


/**
 * 
 * MybatisGeneration.java
 */
public class MybatisGeneration {
	private static Logger logger = Logger.getLogger(MybatisGeneration.class);
	public static Properties jdbc;

	public static void generation(){
		InputStream insjdbc = MybatisGeneration.class.getClassLoader().getResourceAsStream("jdbc.properties");
        jdbc = new Properties();
        try {
            jdbc.load(insjdbc);
        } catch (IOException e) {
            e.printStackTrace();
        }
		
		InputStream ins = MybatisGeneration.class.getClassLoader().getResourceAsStream("code.properties");
		Properties pro = new Properties();
		try {
			pro.load(ins);
		} catch (IOException e) {
			e.printStackTrace();
		}
		String modelUrl = pro.getProperty("modelUrl");
		
		if(!StringUtils.isEmpty(modelUrl)){
			logger.error("modelUrl  ==  " + modelUrl);
			
			String[] str = modelUrl.split(",");
			if(str.length>0){
				try {
					for(int i=0;i<str.length;i++){
						Class<?> forName = Class.forName(str[i]);
						if(forName!=null){
							Field[] fs = forName.getDeclaredFields();
							
							//先拿到@Table
							Table annotationTable = forName.getAnnotation(Table.class);
							String columns = "";
							if(annotationTable!=null){
								columns = DBHelper.getColumns(annotationTable.name());
							}
							
							if(!"".equals(columns)){
								for(Field field : fs){
									if(!field.isAccessible()){  
										field.setAccessible(true);  
					                }
									StringBuffer sb = new StringBuffer("ALTER TABLE "+ annotationTable.name() +" ADD ");
									
									//取字段上@MyColumn的name
									MyColumn annotationColumn = field.getAnnotation(MyColumn.class);
									if(annotationColumn!=null){
										if(!"".equals(annotationColumn.name()) && !columns.contains(annotationColumn.name())){
											//如果不包含,说明这个字段实体有,数据库没有
											
											//先创建这个字段
											sb.append(annotationColumn.name() +" ");
											//判断类型
											if("text".equals(annotationColumn.type())){
												sb.append(annotationColumn.type());
											}else if("datetime".equals(annotationColumn.type())){
												sb.append("datetime ");
											}else if("decimal".equals(annotationColumn.type())){
												sb.append("DECIMAL(" + annotationColumn.precision() + "," + annotationColumn.scale() + ") ");
											}else{
												sb.append("" + annotationColumn.type() + "("+ annotationColumn.length() +") ");
											}
											//是否为空
											if(annotationColumn.nullable()){
												sb.append("DEFAULT NULL ");
											}else{
												sb.append("DEFAULT NOT NULL ");
											}
											//是否唯一
											if(annotationColumn.unique()){
												sb.append("UNIQUE ");
											}
											//注释
											if(!"".equals(annotationColumn.comment())){
												sb.append("COMMENT '"+ annotationColumn.comment() +"'");
											}
											logger.error("sql  ==  " + sb.toString());
											DBHelper.execute(sb.toString());
											
											//字段上是否含有@Id 和 @GeneratedValue,一般这两个字段是一起用的
											Id annotationId = field.getAnnotation(Id.class);
											GeneratedValue annotationGeneratedValue = field.getAnnotation(GeneratedValue.class);
											if(annotationId!=null && annotationGeneratedValue!=null){
												String sql = "alter table "+ annotationTable.name() +" change "+ annotationColumn.name() +" "+ annotationColumn.name() +" "+ annotationColumn.type() +" AUTO_INCREMENT PRIMARY KEY";
												DBHelper.execute(sql.toString());
											}
										}
									}
								}
							}
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		MybatisGeneration.generation();
	}
}

三、在定时器中添加代码如下

        //扫描实体类,配置注解可生成字段
        List<Class<?>> clsList = ClassUtil.getAllClassByPackageName("hry");  
        AnnotationUtil.validAnnotation(clsList);

在这里插入图片描述

这个“”里面填你的总包名 例如本地的main中新建有hry包,这个就会扫描你这个包里面的所有实体类

四、在实体类中添加注解属性

在这里插入图片描述

然后只要在实体类中配有属性columnDefinition 就能根据类去生成字段了
@Column(name= “address”, columnDefinition= “varchar(255) DEFAULT NULL COMMENT ‘地址’”)

<think>我们正在讨论使用Java注解实现数据库字段与实体属性的映射。根据引用内容,我们可以使用JPA(Java Persistence API)或Hibernate提供的注解来完成这一任务。常见的注解包括`@Entity`, `@Table`, `@Id`, `@Column`等。 步骤: 1. 创建实体,并使用`@Entity`注解标记该,表示这是一个持久化实体。 2. 使用`@Table`注解指定对应的数据库表名(如果表名与名不一致)。 3. 在的属性上使用`@Column`注解来映射数据库字段,如果字段名与属性名不一致,可以通过`name`属性指定数据库字段名。 4. 使用`@Id`注解标记主键字段,并可以选择主键生成策略(如`@GeneratedValue`)。 另外,如果数据库字段是下划线命名,而实体属性是驼峰命名,JPA默认会自动进行转换(如`user_name`映射到`userName`)。如果不需要转换或者命名不一致,我们可以通过`@Column`注解显式指定。 根据引用[3],我们也可以自己编写一个工具方法将数据库字段名转为驼峰命名的属性名,但在使用JPA时,通常不需要手动转换,因为JPA提供者(如Hibernate)默认支持这种命名策略。 下面是一个使用注解映射的示例: ```java import javax.persistence.*; @Entity @Table(name = "user_table") // 如果表名与名不同,则用此注解指定表名 public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 如果数据库字段名为user_name,而属性名为userName,可以不注解,因为默认命名策略会转换 // 但也可以显式指定 @Column(name = "user_name") private String userName; // 如果数据库字段和属性名相同(比如都是email),可以不@Column注解 private String email; // 省略getter和setter } ``` 在这个例子中: - `@Entity`表示这是一个实体。 - `@Table(name = "user_table")`指定了对应的数据库表名为`user_table`。 - `@Id`和`@GeneratedValue`用于标识主键及其生成策略。 - `@Column(name = "user_name")`显式地将数据库字段`user_name`映射到属性`userName`。如果数据库字段名和属性名遵循驼峰和下划线的对应关系,可以省略这个注解。 注意事项: - 需要确保添加了JPA或Hibernate的依赖。 - 在配置文件中(如`persistence.xml`或Spring Boot的`application.properties`)配置数据源和JPA相关属性。 如果使用Spring Boot,可以在`application.properties`中配置: ```properties spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password=123456 spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect ``` 这样,我们就完成了数据库字段到实体属性的映射。 §§相关问题§§ 1. JPA中除了`@Column`还有哪些常用的映射注解? 2. 如何配置JPA的命名策略以实现自动的下划线转驼峰? 3. 在Spring Boot中如何配置多数据源并分别指定实体包路径? 4. JPA的`@GeneratedValue`有哪些生成策略?分别适用于什么场景?</think>### Java 注解实现数据库字段与实体属性映射 在 Java 中,可以通过 JPA(Java Persistence API)提供的注解实现数据库字段到实体属性的映射。以下是具体实现步骤和代码示例: #### 1. **基础映射注解** ```java import javax.persistence.*; @Entity @Table(name = "user") // 指定数据库表名 public class User { @Id // 标记为主键 @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略 private Long id; @Column(name = "user_name") // 映射字段名 private String username; @Column(name = "email_address") private String email; // 省略 getter/setter } ``` - **`@Entity`**:声明为持久化实体 - **`@Table`**:指定对应的数据库表名 - **`@Id`**:标记主键字段 - **`@Column`**:自定义字段映射(`name`属性解决命名不一致问题) #### 2. **自动驼峰命名映射(无需`@Column`)** 当数据库字段为下划线格式(如 `user_name`),实体属性为驼峰格式(如 `userName`)时,JPA 默认支持自动转换: ```java @Entity public class Product { @Id private Long id; private String productName; // 自动映射到 product_name 字段 private Integer stockCount; // 自动映射到 stock_count 字段 } ``` #### 3. **高级映射注解 ```java // 枚举型映射 @Enumerated(EnumType.STRING) private UserStatus status; // 存储枚举名称(如 "ACTIVE") // 日期型映射 @Temporal(TemporalType.TIMESTAMP) private Date createTime; // 大文本字段 @Lob private String description; // 关联关系映射(一对多) @OneToMany(mappedBy = "user") private List<Order> orders; ``` #### 4. **自定义转换器(复杂映射)** ```java @Converter(autoApply = true) public class BooleanConverter implements AttributeConverter<Boolean, String> { @Override public String convertToDatabaseColumn(Boolean value) { return value ? "Y" : "N"; } @Override public Boolean convertToEntityAttribute(String dbData) { return "Y".equals(dbData); } } // 实体中使用 @Convert(converter = BooleanConverter.class) private Boolean isActive; ``` #### 配置注意事项 1. 在 `persistence.xml` 或 Spring Boot 的 `application.properties` 中配置数据源: ```properties spring.datasource.url=jdbc:mysql://localhost:3306/db spring.datasource.username=root spring.datasource.password=123456 spring.jpa.hibernate.ddl-auto=update ``` 2. 添加依赖(Maven 示例): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ``` > **最佳实践**: > - 优先使用默认的驼峰命名映射,减少冗余注解 > - 复杂字段(如枚举、JSON)通过自定义转换器实现 > - 关联关系使用 `@OneToMany`/`@ManyToOne` 避免 N+1 查询问题 > - 敏感字段用 `@Transient` 排除持久化[^2][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

日出东方一点红

地址:jackzero.eth

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

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

打赏作者

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

抵扣说明:

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

余额充值