手把手教你写一个简单的项目---->物联网环境监测系统

需要用到的知识
  • SVN版本管理工具
  • maven项目管理工具
  • JDBC、连接池
  • IO流
  • 线程池
  • javaEE的一些基础知识
  • log4j日志
  • lombok的一些简单操作
项目整体思路
  1. 使用maven搭建好项目环境,使用聚合关系搭建environment-parent项目和它的一些子模块项目environment-common(写一些共用的代码)environment-gateway(用来解析数据、把解析后的数据存到容器中发送给服务器)environment-server(服务器接收到数据后,把接收到的数据发送给数据库保存起来)
  2. 下载一个jar比较全的本地仓库
  3. 处理好各个项目的依赖关系:environment-common依赖父项目environment-parent,environment-gatewayenvironment-server都依赖于environment-common
  4. environment-common项目下创建一个环境的实体类
  5. environment-gateway中写一个类GatheImpl进行解析数据,并发送给服务端
  6. environment-server中写一个类ReviceImpl用来接收,StoreImpl类用来给数据库发送数据
创建maven项目,配置pom文件

environment-parent的pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.briup</groupId>
  <artifactId>environment-parent</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>
   <!--父项目的三个子模块-->
  <modules>
  	<module>environment-gateway</module>
  	<module>environment-server</module>
  	<module>environment-common</module>
  </modules>
  <!-- 需要用到的jar的版本 -->
  	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<junit.version>4.13</junit.version>
		<druid.version>1.1.23</druid.version>
		<lombok.version>1.18.12</lombok.version>
		<ojdbc8.version>19.3.0.0</ojdbc8.version>
		<log4j.version>1.2.17</log4j.version>
	</properties>
	 <!-- 对依赖进行管理 -->
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>junit</groupId>
				<artifactId>junit</artifactId>
				<version>${junit.version}</version>
			</dependency>
			
			<dependency>
				<groupId>com.alibaba</groupId>
				<artifactId>druid</artifactId>
				<version>${druid.version}</version>
			</dependency>
			
			<dependency>
				<groupId>org.projectlombok</groupId>
				<artifactId>lombok</artifactId>
				<version>${lombok.version}</version>
			</dependency>
			
			<dependency>
				<groupId>com.oracle.ojdbc</groupId>
				<artifactId>ojdbc8</artifactId>
				<version>${ojdbc8.version}</version>
			</dependency>
			
			<dependency>
				<groupId>log4j</groupId>
				<artifactId>log4j</artifactId>
				<version>${log4j.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>
</project>

environment-common的pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.briup</groupId>
    <artifactId>environment-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>environment-common</artifactId>
  <!--把整个项目所需要的jar全部都依赖进来,供其他两个项目使用,减少了项目jar的冗余-->
  <dependencies>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  	</dependency>
  	<dependency>
  		<groupId>org.projectlombok</groupId>
  		<artifactId>lombok</artifactId>
  	</dependency>
  	
  	<dependency>
  		<groupId>com.alibaba</groupId>
  		<artifactId>druid</artifactId>
  	</dependency>
  	
  	<dependency>
  		<groupId>com.oracle.ojdbc</groupId>
  		<artifactId>ojdbc8</artifactId>
  	</dependency>
  	
  	<dependency>
  		<groupId>log4j</groupId>
  		<artifactId>log4j</artifactId>
  	</dependency>
  </dependencies>
</project>

environment-gateway的pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.briup</groupId>
    <artifactId>environment-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>environment-gateway</artifactId>
  <!--在这里直接依赖environment-common就可以使用其下的所有jar-->
  <dependencies>
  	<dependency>
  		<groupId>com.briup</groupId>
  		<artifactId>environment-common</artifactId>
  		<version>0.0.1-SNAPSHOT</version>
  	</dependency>
  </dependencies>
</project>

environment-server的pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.briup</groupId>
    <artifactId>environment-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>environment-server</artifactId>
   <!--在这里直接依赖environment-common就可以使用其下的所有jar-->
 <dependencies>
  	<dependency>
  		<groupId>com.briup</groupId>
  		<artifactId>environment-common</artifactId>
  		<version>0.0.1-SNAPSHOT</version>
  	</dependency>
  </dependencies>
</project>

这个文件radwtmp就是检测到的环境数据,只写一部分。

100|101|2|16|1|3|5d606f7802|1|1516323596029
1001|101|2|16|1|3|5d606f7802|1|1516323597007
1002|101|2|16|1|3|5d646f7802|1|1516323598382
100|101|2|1280|1|3|031101|1|1516536453850
100|101|2|1280|1|3|031101|1|1516536454877
100|101|2|1280|1|3|031001|1|1516536455808
100|101|2|256|1|3|011b03|1|1516599160680
100|101|2|256|1|3|011b03|1|1516599161579
100|101|2|256|1|3|011c03|1|1516599162522
100|101|2|256|1|3|011b03|1|1516599163528
100|101|2|16|1|3|5d706fac02|1|1516323603959
...

在这里插入图片描述

项目开始:

在这里插入图片描述

Environment实体类

maven项目配置完开始写我们的environment实体类。以上环境数据以 | 为分隔符按顺序对应以下实体类的属性(从第二个属性开始)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Environment implements Serializable{
	private static final long serialVersionUID = 1L;
	//环境数据编号
	private long id;
	//发送端的id
	private String stcId;
	//树莓派id
	private String systemId;
	//实验箱区域id
	private String regionId;
	//数据类型名:
		//二氧化碳、光照强度、温度、湿度
	private String dataType;
	private String name;
	//设置传感器个数
	private int sensor;
	//数据状态
		//3 : 接受数据 ,  6 : 发送数据
	private byte dataStatus;
	//发送来的数据
	private double data;
	//接收数据的标志
	//1 : 成功   
	private byte reviceStatus;
	//传感器采集数据的时间
	private Date date;
}
Utiles.class工具类

写一个工具类Utiles.clss便于后期写代码时候直接调用

//生成一个日志对象	
private static final Logger logger = Logger.getLogger(Utiles.class);
	/**
	 * 将数据备份
	 * @param file   备份文件的目的地
	 * @param data	备份的数据
	 * @param append   是否追加
	 */
	public static void store(File file,Object data,Boolean append) {
		try {
			if (!file.exists()) {
				file.createNewFile();
			}
			ObjectOutputStream oos = 
						new ObjectOutputStream(new FileOutputStream(file,append));
			
			oos.writeObject(data);
			oos.flush();
			oos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 读取数据
	 * @param file 读取数据的文档
	 * @param delete  是否删除源文档
	 * @return
	 */
	public static Object read(File file,Boolean delete) {
		try {
			if (file == null || !file.exists()) {
				return null;
			}
			ObjectInputStream ois =
					new ObjectInputStream(
							new FileInputStream(file));
			Object object = ois.readObject();
			
			if (delete) {
				file.delete();
			}
			ois.close();
			logger.info("读取成功!");
			return object;
		}catch (EOFException e) {
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	} 
	
	//读取properties文件
	public static Properties load(InputStream is) {
		try {
			if (Objects.isNull(is)) {
				throw new EnvironmentException(ExceptionMessageEnum.PARM_IS_NULL.getMsg());
			}
			Properties properties = new Properties();
			properties.load(is);
			return properties;
		} catch (Exception e) {
			throw new EnvironmentException(e.getMessage());
		}
	}
	//关闭数据库
	public static void close(Connection connection,Statement statement,ResultSet resultSet) {
		try {
			if (connection != null) {
				connection.close();
			}
			if (statement != null) {
				statement.close();
			}
			if (resultSet != null) {
				resultSet.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
异常信息类

准备一个手动抛出的自定义异常类EnvironmentException继承自RuntimeException,使用枚举ExceptionMessageEnum获取异常信息

//自定义异常类
public class EnvironmentException extends RuntimeException{
	private static final long serialVersionUID = 1L;
	
	public EnvironmentException(String msg) {
		super(msg);
	}
	public EnvironmentException() {
		
	}
}

//枚举类
@Getter
@AllArgsConstructor
public enum ExceptionMessageEnum {
	PARM_IS_NULL("参数为空");
	
	private String msg;
}
一些配置文件

把要用到的一些文件地址,进行网络通信的东西都存放到properties文件中,方便后期进行修改和维护

SKIP_NAME=src/main/resources/reader.txt
READER_NAME=src/main/resources/radwtmp
STORE_READ=src/main/resources/storeData.txt

HOST=127.0.0.1
POST=10099

DATA_ROLL=src/main/resources/dataroll.txt

log4j日志的一些配置文件

log4j.rootLogger=debug,stdout,D

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%-5p]  %l-%m%n

log4j.appender.D = org.apache.log4j.FileAppender
log4j.appender.D.File =src/main/resources/log.txt
log4j.appender.D.Append=true
log4j.appender.D.Threshold = debug
log4j.appender.D.layout =org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r] -[ %p] %m%n
环境数据解析

在这里插入图片描述

项目架构基本完成,开始进行数据的解析,创建解析类GatheImpl.class

public class GatheImpl implements IGathe{
	//已经读取过的数据将在下次读取时跳过,这个key值对应的文件保存的是已经解析过的数据
	private static String SKIP_NAME = null;
    //key值对应的保存检测到环境数据的文件
	private static String READER_NAME = null;
    //key值对应的解析异常,将数据进行备份的文件
	private static String STORE_READ = null;
	//获取日志对象
	private static final Logger logger = Logger.getLogger(GatheImpl.class);
	//这里用雪花算法来写环境数据编号,具体不知道的可以百度一下
	private static IdWorker idWorker;
    //用来存储解析后的数据
	private static Collection<Environment> list;
	static int j= 0;
	static {
		idWorker = new IdWorker();
		list = new ArrayList<>();
		Properties load = 
	Utiles.load(GatheImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
		SKIP_NAME = load.getProperty("SKIP_NAME");
		READER_NAME = load.getProperty("READER_NAME");
		STORE_READ = load.getProperty("STORE_READ");
	}

	/*
	 * 解析发送来的数据
	 */
	@SuppressWarnings("unused")
	@Override
	public Collection<Environment> gathe() throws EnvironmentException {
		if(list == null) {
			logger.warn("参数为空");
			throw new EnvironmentException(ExceptionMessageEnum.PARM_IS_NULL.getMsg());
		}
		try{
            //读取检测到环境的数据
			BufferedReader br = new BufferedReader(
					new InputStreamReader(
							new FileInputStream(READER_NAME)));
			String string = "";
			String[] split = null;
			Environment environment = null;
			long count = 0;
			logger.info("开始读取SKIP文件");
			Object read = Utiles.read(new File(SKIP_NAME), true);
            //判断SKIP文件是否有数据,有的话加上,用br.skip(count)方法跳过已经保存的数据
			if (!Objects.isNull(read)) {
				count  += (Long) read;
			}
			br.skip(count);
			logger.info("解析器开始解析数据");
			while ((string = br.readLine()) != null) {
				count += string.length() + 1;
                //对数据进行分割
				split = string.split("\\|");
				GatheImpl.analysis(split);
			}
			Utiles.store(new File(SKIP_NAME), count, false);
			br.close();
			logger.info("解析数据完成!");
			return list;
		} catch (Exception e) {
			logger.warn("解析异常,开始备份!");
			Utiles.store(new File(STORE_READ), list, false);
		}
		return list;
	}
	/*
	 * 解析一个字符串,返回一个environment对象
	 */
	private static void analysis(String[] s) {
		Environment e = new Environment();
		Environment e1 = new Environment();
		//设置温度
		//设置环境数据编号
		e.setId(idWorker.nextId());
		for (int i = 0; i < s.length; i++) {
			choice(i, e, s);
		}
		list.add(e);
		//设置湿度
		if (s[6].length() >= 8) {
			e1.setId(idWorker.nextId());
			for (int i = 0; i < s.length; i++) {
				choice(i, e1, s);
			}
			Long ll = Long.parseLong(s[6].substring(0, 4), 16);
			e1.setDataType("16");
			e1.setName("湿度");
			e1.setData(((float)ll*0.00190735)-6);
			list.add(e1);
		}
	}
	/*
	 * 对数组初始化赋值
	 */
	private static void choice(int num,Environment e,String[] s) {
		switch (num) {
			case 0:
				//设置发送端id
				e.setStcId(s[0]);
				break;
			case 1:
				//设置树莓派id
				e.setSystemId(s[1]);
				break;
			case 2:
				//设置实验箱区域id
				e.setRegionId(s[2]);
				break;
			case 3:
				Long l = Long.parseLong(s[6].substring(0, 4), 16);
				//设置数据类型名
				if ("16".equals(s[3])) {
					e.setDataType("16");
					e.setName("温度");
					e.setData(((float)l*0.00268127)-46.85);
				}else if ("256".equals(s[3])) {
					e.setName("光照强度");
					e.setDataType("256");
					e.setData(l);
				}else if ("1280".equals(s[3])) {
					e.setName("CO2");
					e.setDataType("1280");
					e.setData(l);
				}else {
					e.setDataType("null");
				}
				break;			
			case 4:
				//设置传感器个数
				e.setSensor(Integer.valueOf(s[4]));
				break;			
			case 5:
				//设置数据状态
				e.setDataStatus(Byte.valueOf(s[5]));
				break;	
			case 6:
				break;
			case 7:
				//设置接收数据的标志
				e.setReviceStatus(Byte.valueOf(s[7]));
				break;			
			case 8:
				//设置传感器采集数据的时间
				e.setDate(new java.sql.Date(Long.valueOf(s[8])));
				break;			
			default:
				System.err.println("采集错误");
				break;
		}
	}
}

将解析后的数据以list形式发送到服务端

创建Application类

public class Application {
	//IP地址
	private static String HOST = null;
    //端口号
	private static String POST = null;
	//创建日志对象
	private static final Logger logger = Logger.getLogger(Application.class);
	static {
		Properties load = 
	Utiles.load(GatheImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
		HOST = load.getProperty("HOST");
		POST = load.getProperty("POST");
	}
	
	/*
	 * 客户端向服务端发送数据
	 */
	public static void main(String[] args) {
        //使用定时器,定时向服务端发送数据
        //创建带有四个线程的定时线程池对象
		ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
        //实现ISender接口中的向服务端发送数据的方法
		ISender sender = (t->{
			try {
				Socket socket = new Socket(HOST, Integer.valueOf(POST));
				ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
				oos.writeObject(t);
				oos.flush();
				socket.close();
				oos.close();
			} catch (Exception e) {
				logger.warn("数据发送异常");
				e.printStackTrace();
			} 
		});
        //延迟1秒发送,每5秒发送一次数据
		service.scheduleAtFixedRate(new Runnable() {
			
			@Override
			public void run() {
				IGathe gather = new GatheImpl();
				Collection<Environment> gathe = gather.gathe();
				logger.info("客户端向服务端开始发送数据");
				sender.send(gathe);
				System.out.println();
			}
		}, 1, 5, TimeUnit.SECONDS);
	}
}
服务端进行数据的接收

在这里插入图片描述

创建ReviceImpl

public class ReviceImpl implements IRevice{
	//端口号
	private static String POST = null;
	//创建日志对象
	private static final Logger logger = Logger.getLogger(ReviceImpl.class);
	//创建serversocket对象
	private static ServerSocket ss;
    //将接受到的数据存储到list中
	private static Collection<Environment> list;
	static {
		Properties load = 		Utiles.load(ReviceImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
		POST = load.getProperty("POST");
		 POOL = Executors.newCachedThreadPool();
		 list = new ArrayList<>();
		 try {
			ss = new ServerSocket(Integer.valueOf(POST));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/*
	 * 服务端接收数据
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Collection<Environment> revice() throws EnvironmentException {
		logger.info("服务器连接中...");
		try {
				Socket socket = ss.accept();
				logger.info("服务器连接成功");
				logger.info("服务端开始接收数据");
				ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
				Object obj = ois.readObject();
				list = (Collection<Environment>)obj;
				logger.info("服务端数据接收完成");
				ois.close();
				socket.close();
				return list;
			} catch (Exception e) {
				logger.warn("数据接收异常");
				e.printStackTrace();
			}
		return list;
	}
}

创建StoreImpl类,把数据发送到数据库

/*
 * 把集合中数据存储到数据库中
 */
public class StoreImpl implements IStore{
	//创建日志对象
	private static final Logger logger = Logger.getLogger(StoreImpl.class);
	//上传数据库异常时,对象文件进行备份
	private static String DATA_ROLL = null;
	static {
		Properties load = 
	Utiles.load(StoreImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
		DATA_ROLL = load.getProperty("DATA_ROLL");
	}
	
	
	@SuppressWarnings({ "null", "unchecked" })
	@Override
	public void store(Collection<Environment> data) throws EnvironmentException {
		//判断要传的数据是否为空
        if (data.size() == 0) {
			logger.warn("没有数据要发送到数据库");
			System.out.println();
			return;
		}
        //读取备份文件,如果有数据,就赋给data集合,重新进行上传数据库
		Object read = Utiles.read(new File(DATA_ROLL), true);
		if (!Objects.isNull(read)) {
			data = (Collection<Environment>) read;
		}
		Connection conn = null;
        //创建一个解析日期的对象,获取日期在当前月份的天数。因为每条数据有对应的日期,在一个月中,每一天的数据要存储到一个表中
		SimpleDateFormat day = new SimpleDateFormat("dd");
		try {
			Properties properties = new Properties();
			properties.load(
			DruidWebUtils.class.getClassLoader().getResourceAsStream("datasource.properties"));
             //连接池。创建DateSource对象,用来获取数据库连接
			DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
			int i = 0;
			//获取连接
			conn = dataSource.getConnection();
           	 //设置为手动提交
			conn.setAutoCommit(false);
          	 //随机定义一个字符串,用来标识一张表(也就是一天的数据)中创建的preparedStatement对象只有一个
			String dayStr = "0";
             //创建一个字符串,用来存储,数据中日期解析后的天数
			String dayString = "";
			String sql = null;
			Date date = null;
			PreparedStatement ps = null;
			logger.info("开始向数据库传数据...");
			for (Environment environment : data) {
                 //获取一条数据中的日期
				date = environment.getDate();
                 //获取日期的天数
				dayString = day.format(date);
                 //判断相邻的两条数据是否是同一天,是的话直接存储,否则创建preparedStatement对象
				if (!Objects.equals("dayStr", "dayString")) {
                      //如果天数不相同,则把之前的数据提交数据库后再创建新的对象
					if (!Objects.isNull(ps)) {
						ps.executeBatch();
						ps.clearBatch();
                          //必须关闭否则会报错
						Utiles.close(null, ps, null);
					}
                      //把数据中解析的天数赋值给dayStr,这样下次再遇到相同的天数的数据,则直接跳过创建对象
					dayStr = dayString;
                      //创建对象
					sql = "insert into env_tab_"+dayStr+" values(?,?,?,?,?,?,?,?,?,?,?)";
					ps = conn.prepareStatement(sql);
				}
                  //对数据中的字段赋值
				ps.setLong(1, environment.getId());
				ps.setString(2, environment.getStcId());
				ps.setString(3, environment.getSystemId());
				ps.setString(4, environment.getRegionId());
				ps.setString(5, environment.getDataType());
				ps.setString(6, environment.getName());
				ps.setInt(7, environment.getSensor());
				ps.setByte(8, environment.getDataStatus());
				ps.setDouble(9, environment.getData());
				ps.setByte(10, environment.getReviceStatus());
				ps.setDate(11, date);
				ps.addBatch();
				i++;
                 //批处理,每500条处理一次
				if (i % 500 == 0) {
					ps.executeBatch();
					ps.clearBatch();
				}
			}
			if (Objects.isNull(ps)) {
				logger.warn("没有数据要上传");
				System.out.println();
			}else {
				ps.executeBatch();
				ps.close();
				logger.info("上传完成!");
				System.out.println();
			}
			conn.commit();
		} catch (SQLException e) {
			Utiles.store(new File(DATA_ROLL), data, false);
			try {
				conn.rollback();
				System.out.println();
				return;
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			Utiles.close(conn, null, null);
		}
	}
}
调用方法开启服务端接受数据,把数据发送到数据库

创建Application

public class Application {

	public static void main(String[] args) {
        //因为客户端为定时发送,所以服务端一直开启,随时准备接收数据
		while (true) {
			IRevice iRevice = new ReviceImpl();
			Collection<Environment> list = iRevice.revice();
			IStore iStore = new StoreImpl();
			iStore.store(list);
		}
	}
}
  • 6
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值