Spring Boot基础搭建(二)

本文介绍了如何在Spring Boot应用中集成和配置Druid数据源,包括基本配置、数据库信息加密以及监控功能的实现。接着详细讲述了如何使用P6Spy进行SQL日志监控,通过重写数据源、加密数据库信息以及定制日志格式,确保数据安全和方便地查看SQL执行情况。最后提到了关闭Hibernate SQL输出,以便专注于P6Spy的日志记录。

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

接到上一篇:Spring Boot的基础搭建

重写数据源:

druid的基本配置:

这里我们要实现使用druid数据源和配置druid的监控功能。

在pom.xml中dependencies

下添加:

<dependency>

 <groupId>com.alibaba</groupId>

 <artifactId>druid</artifactId>

 <version>1.0.29</version>

</dependency>

加载完druid包后:把SpringBootStudyApplication拖到com包下因为spring boot的启动扫描时扫描它同类包和下层包的文件,所以我们尽量放在最顶层的包下面。如下:

然后在com下添加包config,用于配置一些框架的信息到spring中,在com下继续添加包utils用来编写工具类和重写一些接口。如下:

在config包添加DruidConfiguration里面代码如下:

package com.config;


import com.alibaba.druid.pool.DruidDataSource;

import com.alibaba.druid.support.http.StatViewServlet;

import com.alibaba.druid.support.http.WebStatFilter;

import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;

import org.springframework.aop.Advisor;

import org.springframework.aop.support.DefaultPointcutAdvisor;

import org.springframework.aop.support.JdkRegexpMethodPointcut;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.boot.web.servlet.FilterRegistrationBean;

import org.springframework.boot.web.servlet.ServletRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;


import javax.sql.DataSource;

import java.sql.SQLException;


/**

* Created by onion on 2017-03-28 14:01.

*/

@Configuration

public class DruidConfiguration {


  @Bean

  public DataSource dataSource(@Value("${spring.datasource.url}") String url,

                               @Value("${spring.datasource.username}") String username,

                               @Value("${spring.datasource.password}") String password) {

      DruidDataSource druidDataSource = new DruidDataSource();

      druidDataSource.setUrl(url);

      druidDataSource.setUsername(username);

      druidDataSource.setPassword(password);

      try {

          //开启druid sql防火墙

          druidDataSource.setFilters("stat, wall");

      } catch (SQLException e) {

          e.printStackTrace();

      }

      return druidDataSource;

  }


  //配置druid监控结果访问相关配置

  @Bean

  public ServletRegistrationBean druidServlet() {

      ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

      servletRegistrationBean.addInitParameter("resetEnable", "true");

      servletRegistrationBean.addInitParameter("loginUsername", "druid");

      servletRegistrationBean.addInitParameter("loginPassword", "druid");

      servletRegistrationBean.addInitParameter("allow", "127.0.0.1");

      return servletRegistrationBean;

  }


  //配置druid监控过滤器

  @Bean

  public FilterRegistrationBean filterRegistrationBean() {

      FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();

      filterRegistrationBean.setFilter(new WebStatFilter());

      filterRegistrationBean.addUrlPatterns("/*");

      filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

      filterRegistrationBean.setOrder(5);

      return filterRegistrationBean;

  }


  //添加aop的advice

  @Bean

  public DruidStatInterceptor druidStatInterceptor() {

      return new DruidStatInterceptor();

  }


  //添加aop的pointcut

  @Bean

  public JdkRegexpMethodPointcut jdkRegexpMethodPointcut() {

      JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();

      jdkRegexpMethodPointcut.setPatterns("com.tgb.service.*");

      return jdkRegexpMethodPointcut;

  }


  //设置默认的aop配置对应的是原来的<aop:advisor>

  @Bean

  public Advisor druidAdvisor() {

      DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();

      defaultPointcutAdvisor.setPointcut(jdkRegexpMethodPointcut());

      defaultPointcutAdvisor.setAdvice(druidStatInterceptor());

      return defaultPointcutAdvisor;

  }



}


第一个bean中@Value取的是application.properties中的参数,更多参数的配置可以去查一下我这里只是配置了数据库的连接信息,说明一下没配置数据库的驱动类型是因为druid会从url中去读取数据库驱动类型如:mysql中的路径前缀为jdbc:mysql。。sqlserver的前缀为:jdbc:sqlserver。。等等。druid会取第一个冒号后面的字符串去判断你的数据库类型然后去找到对应的数据库驱动。

第二个bean中的addInitParameter是添加初始参数配置对应的是原来web.xml中的init-param标签下的参数,设置了登录信息druid监控页面的信息和只允许本机去访问。

第三个bean是用来监控web资源的。其中addUrlPatterns是设置过滤的请求路径的

更多配置请自行查阅

重新启动服务先访问:http://localhost:8080/allUser.html,在访问http://localhost:8080/druid/

要求我们输入用户名和密码上面配置的都是druid,输入后就可以查看到一些监控信息。

成功后就可以在sql监控中看到我们刚刚执行的sql。

在spring监控中可以看到我们上面配置的对service的监控信息。

如果设置了session的话就可以在session监控中看到session。到此druid的监控功能配置完成。接下来我们要添加一个数据库配置文件单独来使用配置以防止部署人员不小心改到我们application.properties中项目的一些配置信息。

数据库信息加密:

在utils下面创建ReadProperties类,用这个类来读取我们在实施的时候需要更改的信息,如下

代码:

package com.utils;


import java.io.InputStream;

import java.util.Properties;


/**

* Created by onion on 2017-03-28 15:58.

*/

public class ReadProperties {


  private static Properties properties = new Properties();


  static {

      try {

          InputStream inputStream = ReadProperties.class

                  .getResourceAsStream("/properties/basic.properties");

          properties.load(inputStream);

          inputStream.close();

      } catch (Exception e) {

          System.out.println("未找到配置文件properties/basic.properties" );

          e.printStackTrace();

      }

  }


  public static String get(String key) {

      String result = (String) properties.get(key);

      if (result == null) {

          System.out.println("未找到属性名:" + key);

      }

      return result;

  }


}


在resource目录下创建properties目录。并添加basic.properties配置文件内容如下:

url=jdbc:mysql://localhost:3306/testguest?useUnicode=true&characterEncoding=utf-8&useSSL=false

username=yc

password=1234


在utils中添加包override,因为后面还要重写一些东西,所以这里重写一下Druid的数据源在这里去读取数据库配置信息。创建DruidDataSourceOverride类,如下:

代码:

package com.utils.override;


import com.alibaba.druid.pool.DruidDataSource;

import com.utils.ReadProperties;


import java.util.Properties;


/**

* Created by onion on 2017-03-28 16:18.

*/

public class DruidDataSourceOverride extends DruidDataSource {


  public void InitConfig() throws Exception {

      super.setUrl(ReadProperties.get("url"));

      super.setUsername(ReadProperties.get("username"));

      super.setPassword(ReadProperties.get("password"));

  }


}

修改DruidConfiguration中datasource方法为:

@Bean

public DataSource dataSource() throws Exception {

  DruidDataSourceOverride druidDataSource = new DruidDataSourceOverride();

  druidDataSource.InitConfig();

  try {

      //开启druid sql防火墙

      druidDataSource.setFilters("stat, wall");

  } catch (SQLException e) {

      e.printStackTrace();

  }

  return druidDataSource;

}

并注释掉application.properties中关于数据库的配置,如下:

#spring.datasource.url=jdbc:mysql://localhost:3306/testguest?useUnicode=true&characterEncoding=utf-8&useSSL=false

#spring.datasource.username=yc

#spring.datasource.password=1234

#spring.datasource.driverClassName=com.mysql.jdbc.Driver

#spring.datasource.max-active=20

#spring.datasource.max-idle=8

#spring.datasource.min-idle=8

#spring.datasource.initial-size=10

#spring.datasource.name=dataSource

重新启动服务,访问:http://localhost:8080/allUser.html 可以看到数据说明配置成功。

添加数据库加密解密:

在utils下创建DesUtil,这里使用的是des的加密方式,可以换成你自己所需要的加密方式如下

代码:

package com.utils;


import sun.misc.BASE64Decoder;

import sun.misc.BASE64Encoder;


import javax.crypto.Cipher;

import javax.crypto.KeyGenerator;

import java.security.Key;

import java.security.SecureRandom;


/**

* Created by onion on 2017-03-28 16:37.

*/

public class DesUtil {

  private static Key key;

  private static String KEY_STR;

  static {

      try{

          KEY_STR=ReadProperties.get("key");

          KeyGenerator generator=KeyGenerator.getInstance("DES");

          SecureRandom secureRandom=SecureRandom.getInstance("SHA1PRNG");

          secureRandom.setSeed(KEY_STR.getBytes());

          generator.init(secureRandom);

          key=generator.generateKey();

          generator=null;

      }catch (Exception e){

          e.printStackTrace();

          throw new RuntimeException(e);

      }

  }

  //加密

  public static String getEncryptString(String str){

      BASE64Encoder base64Encoder=new BASE64Encoder();

      try{

          byte[] bytes=str.getBytes("UTF-8");

          Cipher cipher=Cipher.getInstance("DES");

          cipher.init(Cipher.ENCRYPT_MODE,key);

          byte[] encrybyte=cipher.doFinal(bytes);

          return base64Encoder.encode(encrybyte);

      }catch (Exception e){

          e.printStackTrace();

          throw new RuntimeException(e);

      }

  }

  //解密

  public static String getDecryptString(String str){

      BASE64Decoder base64Decoder=new BASE64Decoder();

      try{

          byte[] strbyte=base64Decoder.decodeBuffer(str);

          Cipher cipher=Cipher.getInstance("DES");

          cipher.init(Cipher.DECRYPT_MODE,key);

          byte[] decryptbyte=cipher.doFinal(strbyte);

          return new String(decryptbyte,"UTF-8");

      }catch (Exception e){

          e.printStackTrace();

          throw new RuntimeException(e);

      }

  }

}

修改basic.properties内容为(自己的数据库用自己的明文去加密,在上面有加密的代码):

url=oEoctJoXMOcmmCk5El6mX4WUC//P5gc7uPR6Qoa87VnHNOXkW6aWrCdghbliU/8CzP5NEhxKOqMWphLe72EbI+KVde6vu7NfCbuBrYt1jwKtdHCRq0V3AhAnvgPCOm3L

username=Jmdo19dK6VM=

password=pgLvSnFFiyg=

key=create by onion

然后修改DruidDataSourceOverride中的InitConfig方法为:

public void InitConfig() throws Exception {

  super.setUrl(DesUtil.getDecryptString(ReadProperties.get("url")));

  super.setUsername(DesUtil.getDecryptString(ReadProperties.get("username")));

  super.setPassword(DesUtil.getDecryptString(ReadProperties.get("password")));

}

重新启动服务访问:http://localhost:8080/allUser.html 看到数据说明成功。

说明:可以把加密和解密写成两个java swing工具去执行,方便实施人员修改数据库配置。

我的加密和解密工具为:加密工具  解密工具

集成p6spy:

虽然hibernate和druid都能输出我们执行的sql,但是预编译的sql输出的是?号,不方便我们调试,因此加上p6spy。

p6spy是监控sql输出sql日志的工具,会把预编译替换成对应的参数实现完整sql的监控。

在pom.xml中添加:

<dependency>

 <groupId>p6spy</groupId>

 <artifactId>p6spy</artifactId>

 <version>3.0.0</version>

</dependency>

在resources目录下添加spy.properties如下:

配置信息为:



#################################################################

# P6Spy Options File                                            #

# See documentation for detailed instructions                   #

#################################################################


#################################################################

# MODULES                                                       #

#                                                               #

# Modules provide the P6Spy functionality.  If a module, such   #

# as module_log is commented out, that functionality will not   #

# be available.  If it is not commented out (if it is active),  #

# the functionality will be active.                             #

#                                                               #

# Values set in Modules cannot be reloaded using the            #

# reloadproperties variable.  Once they are loaded, they remain #

# in memory until the application is restarted.                 #

#                                                               #

#################################################################


module.log=com.p6spy.engine.logging.P6LogFactory

#module.outage=com.p6spy.engine.outage.P6OutageFactory


#################################################################

# REALDRIVER(s)                                                 #

#                                                               #

# In your application server configuration file you replace the #

# "real driver" name with com.p6spy.engine.P6SpyDriver. This is #

# where you put the name of your real driver P6Spy can find and #

# register your real driver to do the database work.            #

#                                                               #

# If your application uses several drivers specify them in      #

# realdriver2, realdriver3.  See the documentation for more     #

# details.                                                      #

#                                                               #

# Values set in REALDRIVER(s) cannot be reloaded using the      #

# reloadproperties variable.  Once they are loaded, they remain #

# in memory until the application is restarted.                 #

#                                                               #

#################################################################


# oracle driver

# realdriver=oracle.jdbc.driver.OracleDriver


# mysql Connector/J driver

realdriver=com.mysql.jdbc.Driver


# informix driver

# realdriver=com.informix.jdbc.IfxDriver


# ibm db2 driver

# realdriver=COM.ibm.db2.jdbc.net.DB2Driver


# the mysql open source driver

#realdriver=org.gjt.mm.mysql.Driver


#specifies another driver to use

realdriver2=

#specifies a third driver to use

realdriver3=


log4j.additivity.p6spy=false

#the DriverManager class sequentially tries every driver that is

#registered to find the right driver.  In some instances, it's possible to

#load up the realdriver before the p6spy driver, in which case your connections

#will not get wrapped as the realdriver will "steal" the connection before

#p6spy sees it.  Set the following property to "true" to cause p6spy to

#explicitily deregister the realdrivers

deregisterdrivers=false


################################################################

# P6LOG SPECIFIC PROPERTIES                                    #

################################################################

# no properties currently available


################################################################

# EXECUTION THRESHOLD PROPERTIES                               #

################################################################

# This feature applies to the standard logging of P6Spy.       #

# While the standard logging logs out every statement          #

# regardless of its execution time, this feature puts a time   #

# condition on that logging.  Only statements that have taken  #

# longer than the time specified (in milliseconds) will be     #

# logged.  This way it is possible to see only statements that #

# have exceeded some high water mark.                          #

# This time is reloadable.                                     #

#

# executionthreshold=integer time (milliseconds)

#

executionthreshold=


################################################################

# P6OUTAGE SPECIFIC PROPERTIES                                 #

################################################################

# Outage Detection

#

# This feature detects long-running statements that may be indicative of

# a database outage problem. If this feature is turned on, it will log any

# statement that surpasses the configurable time boundary during its execution.

# When this feature is enabled, no other statements are logged except the long

# running statements. The interval property is the boundary time set in seconds.

# For example, if this is set to 2, then any statement requiring at least 2

# seconds will be logged. Note that the same statement will continue to be logged

# for as long as it executes. So if the interval is set to 2, and the query takes

# 11 seconds, it will be logged 5 times (at the 2, 4, 6, 8, 10 second intervals).

#

# outagedetection=true|false

# outagedetectioninterval=integer time (seconds)

#

outagedetection=false

outagedetectioninterval=


################################################################

# COMMON PROPERTIES                                            #

################################################################


# filter what is logged

filter=false


# comma separated list of tables to include when filtering

include     =

# comma separated list of tables to exclude when filtering

exclude     =


# sql expression to evaluate if using regex filtering

sqlexpression =


# class to use for formatting log messages (default is: com.p6spy.engine.spy.appender.SingleLineFormat)

logMessageFormat=com.utils.override.P6LogFormat


# turn on tracing

autoflush   = false


# sets the date format using Java's SimpleDateFormat routine

dateformat=yyyy-MM-dd HH:mm:ss


#list of categories to explicitly include

includecategories=


#list of categories to exclude: error, info, batch, debug, statement,

#commit, rollback and result are valid values

excludecategories=info,debug,result,resultset,batch,commit



#allows you to use a regex engine or your own matching engine to determine

#which statements to log

#

#stringmatcher=com.p6spy.engine.common.GnuRegexMatcher

#stringmatcher=com.p6spy.engine.common.JakartaRegexMatcher

stringmatcher=


# prints a stack trace for every statement logged

stacktrace=false

# if stacktrace=true, specifies the stack trace to print

stacktraceclass=


# determines if property file should be reloaded

reloadproperties=false

# determines how often should be reloaded in seconds

reloadpropertiesinterval=60


#if=true then url must be prefixed with p6spy:

useprefix=false


#specifies the appender to use for logging

#appender=com.p6spy.engine.spy.appender.FileLogger(file)

#appender=com.p6spy.engine.spy.appender.StdoutLogger

appender=com.p6spy.engine.spy.appender.StdoutLogger


# name of logfile to use, note Windows users should make sure to use forward slashes in their pathname (e:/test/spy.log) (used for file logger only)

logfile     = E://logs/sql/sql.log


# append to  the p6spy log file.  if this is set to false the

# log file is truncated every time.  (file logger only)

append=true


#The following are for log4j logging only

log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender

log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout

log4j.appender.STDOUT.layout.ConversionPattern=p6spy - %m%n


#log4j.appender.CHAINSAW_CLIENT=org.apache.log4j.net.SocketAppender

#log4j.appender.CHAINSAW_CLIENT.RemoteHost=localhost

#log4j.appender.CHAINSAW_CLIENT.Port=4445

#log4j.appender.CHAINSAW_CLIENT.LocationInfo=true


log4j.logger.p6spy=INFO,STDOUT



#################################################################

# DataSource replacement                                        #

#                                                               #

# Replace the real DataSource class in your application server  #

# configuration with the name com.p6spy.engine.spy.P6DataSource,#

# then add the JNDI name and class name of the real       #

# DataSource here                          #

#                                                               #

# Values set in this item cannot be reloaded using the          #

# reloadproperties variable.  Once it is loaded, it remains     #

# in memory until the application is restarted.                 #

#                                                               #

#################################################################

#realdatasource=/RealMySqlDS

#realdatasourceclass=com.mysql.jdbc.jdbc2.optional.MysqlDataSource


#################################################################

# DataSource properties                                         #

#                                                               #

# If you are using the DataSource support to intercept calls    #

# to a DataSource that requires properties for proper setup,    #

# define those properties here. Use name value pairs, separate  #

# the name and value with a semicolon, and separate the         #

# pairs with commas.                                            #

#                                      #

# The example shown here is for mysql                          #

#                                                               #

#################################################################

#realdatasourceproperties=port;3306,serverName;ibmhost,databaseName;mydb



#################################################################

# JNDI DataSource lookup                                        #

#                                                               #

# If you are using the DataSource support outside of an app     #

# server, you will probably need to define the JNDI Context     #

# environment.                                                  #

#                                                               #

# If the P6Spy code will be executing inside an app server then #

# do not use these properties, and the DataSource lookup will   #

# use the naming context defined by the app server.             #

#                                                               #

# The two standard elements of the naming environment are  #

# jndicontextfactory and jndicontextproviderurl. If you need    #

# additional elements, use the jndicontextcustom property.      #

# You can define multiple properties in jndicontextcustom,      #

# in name value pairs. Separate the name and value with a       #

# semicolon, and separate the pairs with commas.                #

#                                                               #

# The example shown here is for a standalone program running on #

# a machine that is also running JBoss, so the JDNI context     #

# is configured for JBoss (3.0.4).                              #

#                                                               #

#################################################################

#jndicontextfactory=org.jnp.interfaces.NamingContextFactory

#jndicontextproviderurl=localhost:1099

#jndicontextcustom=java.naming.factory.url.pkgs;org.jboss.nameing:org.jnp.interfaces


#jndicontextfactory=com.ibm.websphere.naming.WsnInitialContextFactory

#jndicontextproviderurl=iiop://localhost:900

上面的logMessageFormat配置的是我们重写的p6spy日志输出前对输出信息格式化的类。如果appender配置的是FileLogger就要在自己的电脑中创建目录E:\logs\sql  因为我们上面配置日志的输出路径为它,我们这里配置的是StdoutLogger表示输出到控制台的意思。



在override下面创建P6LogFormat如下:

代码为:

package com.utils.override;


import com.p6spy.engine.common.P6Util;

import com.p6spy.engine.spy.appender.SingleLineFormat;

import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;


/**

* Created by onion on 2017-03-28 17:24

*/

public class P6LogFormat extends SingleLineFormat {

  private static BasicFormatterImpl formatter = new BasicFormatterImpl();


  private int i = 0;


  @Override

  public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql) {

      if (i == 0) {

          i++;

          return now + "|" +

                  "" + elapsed + "|" +

                  "" + category + "|connection " +

                  "" + connectionId + "|" +

                  "\nAfter Prepared SQL:" + formatter.format(P6Util.singleLine(prepared)) +

                  "\nBefore Prepared SQL:" + formatter.format(P6Util.singleLine(sql));

      } else {

          i = 0;

          return now + "|" + category + "|" + "Time Consumin:" + elapsed + "ms";

      }

  }

}

说明:上面的BasicFormatterImpl是hibernate的sql格式化类。用它来美化输出我们的sql,定义的一个变量i是因为该工具会在执行sql前和sql后都会输出执行的sql,我没在网上和p6spy的官网找到取消输出两次sql,因此我们这里让执行sql前输出sql就可以了。


修改basic.properties的url参数为80UxfftOywVPGTUcd2Exc1ynK7qek8kNhIwZGrYj/VYxjIPICp8DwBosC6gFVtjHZbLEYgK+LuJe

4RlvEqcU6zwmNTiu4n4I2C/b7jXXt4kGZxKva/qkkvmFKkdd/q5Ks46DR7190bQ=

说明:使用p6spy要在连接中加入p6spy即明文链接要这样jdbc:p6spy:mysql://localhost:3306/testguest?useUnicode=true&characterEncoding=utf-8&useSSL=false

在DruidDataSourceOverride中的InitConfig方法里面添加如下代码:

super.setDriverClassName("com.p6spy.engine.spy.P6SpyDriver");

super.setValidationQuery("select 1");

super.setDbType("mysql");

说明:因为之前说了druid是根据jdbc:后面的字符串去判断驱动类型和数据库类型。但是p6spy是判断不出来数据库类型和驱动的所以我们要设置驱动名称和数据库类型,上面设置启动验证sql是因为druid默认的设置是启动了验证数据库配置是否正确,不设置的话会看到控制台出现提示,也可以关闭druid的启动验证。

修改后启动服务访问:http://localhost:8080/allUser.html 然后看到数据后能够在后台看到我们设置的p6spy打印出来了sql。访问http://localhost:8080/addUser.html 添加数据后我们可以在后台看到

After Prepared SQL:

   /* insert com.tgb.model.User

       */ insert

       into

           user

           (age, name)

       values

           (?, ?)

Before Prepared SQL:

   /* insert com.tgb.model.User

       */ insert

       into

           user

           (age, name)

       values

           (3, 'asd')

上面输出的sql中成功的把?替换成对应的数据。现在我们把hibernate的输出sql关闭在application.properties中修改:

spring.jpa.show-sql=false

spring.jpa.properties.hibernate.format_sql=false

spring.jpa.properties.hibernate.use_sql_comments=false

关闭后我们就能看到控制台只输出了p6spy的sql。

到此我们对数据源方面的配置基本完成。

代码下载:SpringBoot基础搭建(二)

下面继续讲解:

重新配置hibernate:

在上面搭建的环境中是通过spring-jpa去完成数据库操作的我没有发现hibernate的接口entityInterceptor,这个接口是hibernate的sql拦截接口,为什么需要是因为hibernate生成的sql里面的表名称和字段是在启动的时候缓存好了的,有的公司业务上需要一个月换表或者按照不同的条件去修改表名称或字段等。

以及配置hibernate的事物处理。

spring相关:

统一异常处理,aop日志处理以及如何不采用hql语句采用 jdbctemplate执行sql。

springboot部署:

将springboot抽出为可执行的jar文件执行。

等等。。


改文档下载地址http://download.youkuaiyun.com/detail/qq_36224522/9797225


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值