【SSH框架整合】

目录

1 整合前准备

2 整合并进行测试


1 整合前准备

1.1 添加struts2的jar包(2.3版本)和配置文件:

step1 导入struts2的jar包,如下:

step2 添加struts2的配置文件struts.xml到src目录下,struts.xml中常用的常量如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>
    <!-- 配置成开发模式 -->
    <constant name="struts.devMode" value="true" />
    <!-- 禁用调用动态方法访问 -->
    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
    <!-- 配置拓展名为action -->
    <constant name="struts.action.extention" value="action" />
    <!-- 把主题配置成simple -->
    <constant name="struts.ui.theme" value="simple" />
</struts>

step3 配置struts2的过滤器(StrutsPrepareAndExecuteFilter)到web.xml,含spring的监听器,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 
    id="WebApp_ID"
    version="3.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

<!-- start spring框架环境准备搭建 -->
    <!-- start 通过listener加载spring容器。监听器的类是ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- end -->
    <!-- start 指定spring配置文件路径 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
  	<param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- end -->
<!-- end -->

<!-- start struts2框架环境准备搭建 -->
    <!-- start 配置struts2的过滤器。过滤器的类是StrutsPrepareAndExecuteFilter -->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <!-- end -->
    <!-- start 配置拓展名为.action -->
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>*.action</url-pattern>
    </filter-mapping>
    <!-- end -->
<!-- end -->

    <display-name>taxServices</display-name>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

1.2 添加spring的jar包(3.0版本)和配置文件:

step1 导入spring的jar包,如下:

step2 添加spring的配置文件applicationContext.xml到src目录下,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" >

</beans>

step3 配置spring的监听器(ContextLoaderListener)到web.xml。

spring监听器的作用:监听应用服务器的启动,启动完以后实例化springIOC容器。

1.3 添加hibernate的jar包(3.6版本):

spring来整合管理,所以hibernate.cfg.xml文件不再需要,直接导入hibernate的jar包即可,如下:

2 整合并进行测试

2.1 整合struts和spring

预期:在action层如果能调用service层的某个方法,并返回到视图层,那么就认定struts和spring的整合是成功的。

step1 测试spring配置文件加载是否成功。编写JUnit测试类

package:net.csdn.test,class:TestMerge

@Before的意思是比如在选择testSpring()执行前,会先执行@Before注解的方法,因为在接下来的测试都需要用到ApplicationContext接口的getBean()获取bean的实例。代码如下:

ClassPathXmlApplicationContext initSpringContainer;
	
@Before
public void initSpringContainer() {
    //使用ApplicationContext接口的实现类ClassPathXmlApplicationContext的构造方法初始化spring容器
    initSpringContainer = new ClassPathXmlApplicationContext("applicationContext.xml");
}

@Test
public void testSpring() {
    initSpringContainer = new ClassPathXmlApplicationContext("applicationContext.xml");
}

有@Test注解就可以不写main(),双击testSpring,右击选择Run As > JUnit Test,绿色条,初始化spring容器成功

step2 测试spring框架是否出错。spring容器中注册一个service bean;在TestMerge类调用service层的方法输出一句话,说明spring框架是没问题的。

package:net.csdn.test.service,interface:TestService

代码如下:

public abstract void say(); 

package:net.csdn.test.service.impl,class:TestServiceImpl implements TestService

service层使用注册的注解@Service,加了这个注解就会将service bean注册到spring容器中,这里的注解只是配置的意思,而不是动作,动作是要在spring配置文件进行扫描service;到时候TestMerge类的getBean("testService")要检索bean的名称,返回bean的实例,注解的括号值必须要与getBean()的参数值一样,代码如下:

@Service("testService")
public class TestServiceImpl implements TestService {
    @Override
    public void say() {
        System.out.println("service say hi...");
    }
}

package:net.csdn.test.conf,xml:test-spring

注册service bean到spring配置文件中。代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" >

    <!-- service层使用注解,告诉spring配置文件,扫描被service注解标注的业务类。使用注解方式注册一个bean到spring配置文件中 -->
    <context:component-scan base-package="net.csdn.test.service.impl"></context:component-scan>
</beans>

将test-spring.xml文件导入到总spring配置文件applicationContext.xml中,applicationContext.xml的其他代码在前面步骤已写,这里只写导入的代码,如下:

<!-- 导入外部spring配置文件,使用通配符 -->
<import resource="net/csdn/*/conf/*-spring.xml"/>

在TestMerge类的testSpring()中调用注册在spring容器中service bean的方法。

通过ApplicationContext接口的getBean()获取bean的实例;getBean(String name):要检索的bean的名称,方法返回bean的实例;这里要获取TestService的bean实例,必须把TestService的实现类所在的包注册到spring配置文件中

只写调用注册在spring容器中service bean的方法的代码,代码如下:

TestService testService = (TestService)initSpringContainer.getBean("testService");
testService.say();

成功调用service层方法,如下,说明spring框架无误。

step3 测试struts和spring

package:net.csdn.test.action,class:TestAction extends ActionSupport

action中要使用service对象,service已注册到spring容器中,而action还没有注册到spring容器中,所以是不能随便调用注册在spring容器中的bean的 。

代码如下:

@Resource
TestService testService; //根据变量的名称去spring容器中检索和该变量一样的bean的名称

public String execute() {
    testService.say();
    return SUCCESS;
}

package: net.csdn.test.conf,xml:test-struts

action没有注册到spring容器中,又要调用注册在容器中的service对象(如下代码,在action元素的class属性中,如果class的属性值是testAction,就是要注册到spring容器中的,这种就需要在action层使用注册的注解@Controller;这里没有把action注册到spring容器中,class的属性值是action的类,这种方式是交给struts创建管理的,struts的就交给struts管理创建)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
     
<struts>
    <package name="test-action" namespace="/" extends="struts-default">
        <action name="test_*" method="{1}" class="net.csdn.test.action.TestAction">
            <result name="{1}">/WEB-INF/jsp/test/{1}.jsp</result>
	</action>
    </package>
</struts>

为什么action没有注册在spring容器中,还能调用注册在容器中的service对象?这里需要struts2-spring-plugin的插件包,这个包就会判断如果用了@Resource注解,会根据变量的名称去spring容器中检索和该变量一样的bean的名称,testService已注册到spring容器中了,所以这里action中可以调用注册在spring容器中的service对象。

在WEB-INF文件夹下新建jsp文件夹,在jsp文件夹下新建test文件夹,test文件夹下创建test.jsp,jsp的内容:hello java server page.

将test-struts.xml文件包含到总struts配置文件struts.xml中。这里没有使用通配符,struts的支持没有spring友好,在低一些版本的应用服务器,如果使用通配符会扫描不到struts的配置文件,tomcat服务器是可以的,这里不使用通配符的方式。struts.xml的其他代码在前面步骤已写,这里只写包含的代码,如下:

<include file="net/csdn/test/conf/test-struts.xml"></include>

浏览器输入访问路径,struts的文档有说明,如果是默认方法,默认方法名称可以不写,并且方法的前一个字符也可以不写。符合整合的预期结果,如下图所示:

2.2 整合hibernate和spring

mysql客户端工具是navicat,新建数据库buildssh,如下图所示:


在applicationContext.xml中配置(原本需要在hibernate.cfg.xml中配置)c3p0数据库连接源,如下:

<!-- 导入外部的properties配置文件 -->
<context:property-placeholder location="classpath:db.properties" />	
<!-- 为hibernate配置数据源对象。使用c3p0连接池技术 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <!-- 连接数据库的驱动类 -->
    <property name="driverClass" value="${driverClass}"></property>
    <!-- 连接数据库的url -->
    <property name="jdbcUrl" value="${jdbcUrl}"></property>
    <!-- 连接数据库的用户名 -->
    <property name="user" value="${user}"></property>
    <!-- 连接数据库的密码 -->
    <property name="password" value="${password}"></property>
    <!-- 连接池的最大连接数。default: 15 -->
    <property name="maxPoolSize" value="${maxPoolSize}"></property>
    <!-- 连接池的最小连接数。default: 3 -->
    <property name="minPoolSize" value="3"></property>
    <!-- 初始化连接数,取值应在minPoolSize与maxPoolSize之间。default: 3 -->
    <property name="initialPoolSize" value="${initialPoolSize}"></property>
    <!-- 连接的最大空闲时间,1800秒内未使用则连接被丢弃,若为0则永不丢弃。default: 0 -->
    <property name="maxIdleTime" value="1800"></property>
    <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。default: 3 -->
    <property name="acquireIncrement" value="3"></property>
</bean>

添加db.properties文件到src目录下。buildssh为数据库的名称,配置如下:

jdbcUrl=jdbc:mysql://localhost:3306/buildssh?useUnicode=true&characterEncoding=utf8
driverClass=com.mysql.jdbc.Driver
user=root
password=123456
initialPoolSize=10
maxPoolSize=30

在applicationContext.xml中配置sessionFactory。属性ref的dataSource引用的是配置数据源对象bean的id的属性值。

sessionFactory有3个property元素:

第一个property:数据库的连接信息;

第二个property:hibernate属性;

第三个property:映射文件的位置。

如下:

<!-- 配置sessionFactory(Session)对象 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <!-- 数据库方言  -->
            <prop key="hibernate.show_sql">true</prop> <!-- 是否将执行的SQL语句输出到控制台 -->
            <prop key="hibernate.hbm2ddl.auto">update</prop> <!-- 根据hibernate映射文件更新数据库表结构  -->
            <prop key="javax.persistence.validation.mode">none</prop> <!-- 默认为auto,不设置会自动扫描bean-validation.jar,找不到就会报错,这里不让它扫描 -->
        </props>
    </property>
    <property name="mappingLocations">
        <list>
	    <value>classpath:net/csdn/test/entity/*.hbm.xml</value>
        </list>
    </property>
</bean>

package:net.csdn.test.entity,class:Person implements Serializable

实现Serializable接口的原因:可以直接将实体类的对象写到硬盘地址,也可以读取一个对象;可以在请求中传递对象;可以将对象作为主键传到后台查询,主键需要序列化的对象才可以,不然不能作为主键。

private String id;
private String name;

public Person() {
}

public Person(String name) {
    this.name = name;
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

package:net.csdn.test.entity,xml:Person.hbm.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="net.csdn.test.entity.Person" table="person">
        <id name="id" type="java.lang.String">
            <column name="id" length="32" />
            <generator class="uuid.hex" /> <!-- 由hibernate基于128位唯一值产生算法生成十六进制数值作为主键 -->
        </id>
        <property name="name" type="java.lang.String">
            <column name="name" length="20" not-null="true" />
        </property>
    </class>
</hibernate-mapping>

在测试hibernate之前,导入如下jar包:

如果没有导入,则会报错,如下:

测试hibernate。在TestMerge类的testHibernate()中通过id检索spring容器的sessionFactory bean实例。

@Test
public void testHibernate() {
    SessionFactory sessionFactory = (SessionFactory) initSpringContainer.getBean("sessionFactory");
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    session.save(new Person("张三")); //保存人员
    transaction.commit();
    session.close();
}

双击testHibernate,右击选择Run As > JUnit Test。成功把张三保存到数据库中,hibernate测试成功,如下图所示:

 

 

接下来将事物控制进行抽取。JUnit充当的是控制层,按照层次测试,还需要dao层。凡是操作数据库的,都要进行事物控制,以达到一次操作要么全部成功,要么全部失败。TestMerge类的save()就用到了事物控制,如果在来个查询,又需要再次开启事物,代码重复。

以保存跟查询进行数据库操作为例,进行分层测试。在TestService类编写,如下:

//保存人员
public abstract void save(Person person);

//根据id查询人员
public abstract Person findPerson(Serializable id);

业务层的实现类TestServiceImpl。

业务层不直接获取注册在spring容器中的bean的名称,而是获取dao层的代码来操作。

此时TestDao还没有注册在spring容器中,详细步骤看下面描述。 代码如下:

@Resource
TestDao testDao;

@Override
public void save(Person person) {
    testDao.save(person);
}

@Override
public Person findPerson(Serializable id) {
    return testDao.findPerson(id);
}

package:net.csdn.test.dao,interface:TestDao

代码和TestService的保存,查询是一样的,略。

package:net.csdn.test.dao.impl,class:TestDaoImpl extends HibernateDaoSupport implements TestDao

dao层就是用来操作数据库的,而如果在dao层也同样有很多事物控制的重复代码,是不现实的,这样最基础的功能,spring提供了HibernateDaoSupport类,只需继承该类即可,调用该类的getHibernateTemplate(),就可以实现这些最基础的功能。而且继承该类,不需要关心事物控制使用openSession还是getCurrentSession。

@Override
public void save(Person person) {
    getHibernateTemplate().save(person);
}

@Override
public Person findPerson(Serializable id) {
    return getHibernateTemplate().get(Person.class, id);
}

此时的保存跟查询还是不能够操作数据库的,因为数据库的连接信息还不知道。

查看spring的API,HibernateTemplate(getHibernateTemplate()的返回值类型是HibernateTemplate)也是一个bean,调用了getSessionFactory(),这个方法也是HibernateDaoSupport类中的方法,返回SessionFactory,如下图:

也就是HibernateTemplate的构造方法需要传入sessionFactory bean,才能进行数据库操作。

在spring配置文件中已经有sessionFactory对象了,只差给到HibernateTemplate的构造方法。有2种方式:

第一种方式:通过注解@Resource; 第二种方式:通过配置。

注解的方式又有2种:以下这种方式sessionFactory给了子类TestDaoImpl了,现在是HibernateDaoSuppor类的HibernateTemplate构造方法需要sessionFactory,所以这种方式pass;

@Resource
SessionFactory sessionFactory;

注解的第二种方式:以下是通过set()的方式。 在HibernateDaoSupport类中有个setSessionFactory(),如下图:

private SessionFactory sessionFactory;
@Resource
public void setSessionFactory(SessionFactory sessionFactory) {
    super.setSessionFactory(sessionFactory);
    this.sessionFactory = sessionFactory;
}

所以 ,你会发现报错了,如下图:

父类的setSessionFactory()是final修饰符,父类明摆着让子类去“使用”,不让子类“重写”父类的功能。所以第一种方式通过注解全部被pass,只能使用配置的方式!

在test-spring.xml文件中,进行配置,这样dao层也注册到了spring容器中 ,如下:

<bean id="testDao" class="net.csdn.test.dao.impl.TestDaoImpl">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

将sessionFactory对象的引用指向TestDaoImpl类,这时HibernateTemplate的构造方法就传入了sessionFactory,到这一步具体怎么做的,用的是openSession还是getCurrentSession,并不关心,HibernateTemplate的构造方法需要的值已给到位。这时TestDaoImpl类就有了数据库的连接信息,保存跟查询就可以正常操作数据库了。

分了层之后,测试层与层之间的衔接,主要看dao层能否保存跟查询成功。这时,dao层与service层都注册在了spring容器中,可以互相调用 。在TestMerge类进行测试,代码如下:

@Test
public void testServiceAndDao() {
    TestService testService = (TestService) initSpringContainer.getBean("testService");
    testService.save(new Person("王五"));
}

双击testServiceAndDao,右击选择Run As > JUnit Test。成功把王五保存到数据库中,测试成功,如下图所示:

把测试保存方法的注释了,测试查询也成功,代码如下:

System.out.println(testService.findPerson("4028abe37450275901745027639c0000").getName());

 

 

接下来要做的是配置声明式事物控制,事物控制有2种方式:① 编程式;②声明式

编程式就是写在要控制事物的代码当中,如TestMerge类中的testHibernate()用到的事物控制;声明式就是通过扫描的方式控制的范围。一般使用声明式。

事物控制3个步骤:

①配置事物管理(得到session,开启事物,事物管理就是对sessionFactory进行事物控制);

②配置事物通知(为事物管理配置事物属性,也就是事物的处理规则);

③配置事物aop切入点(事物要控制的类,是一个表达式)。
在applicationContext.xml中,代码如下:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="find*" read-only="true" /> <!-- aop扫描到的类中所有以find开头的方法,要做的事物是"只读",除此之外不允许任何操作数据库的行为  -->
        <tx:method name="get*" read-only="true" />
        <tx:method name="list*" read-only="true" />
        <tx:method name="*" rollback-for="Throwable" /> <!-- 除了以上这三个方法,任何操作数据库出现异常,对这一次的操作进行回滚 -->
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="servicePointcut" expression="execution(* net.csdn..service.impl.*.*(..))" /> <!-- 表达式的意思是:任意方法的返回值,任意中间路径,service.impl包下的任意类的任意方法的任意参数值 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" />
</aop:config>

 配置事物aop切入点有两种方式,如下:

<!-- 如果service层注册到了spring容器中,这里的expression表达式可以这样写 -->
<aop:config>
    <aop:pointcut id="servicePointcut" expression="bean(*Service)" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" />
</aop:config>

<!-- 还有一种写法是:切入点直接是execution要执行的事物控制 -->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* net.csdn..service.impl.*.*(..))" />
</aop:config>

PS:如果在配置aop切入点那里报错,是eclipse校验器的问题,myeclipse也是,以前觉得eclipse低版本不行,现在安装了2020-06最新版还是这怂式子,把aop:pointcut元素里的id属性放在表达式的前面就不报错了,还有applicationContext.xml文件也出现了报错,debug模式启动都是没有报任何错误的,就是校验器的问题。

配置了事物控制,验证事物控制能否成功。在TestMerge类,代码如下:

@Test
public void testTransactionReadyOnly() {
    TestService testService = (TestService) initSpringContainer.getBean("testService");
    System.out.println(testService.findPerson("4028abe37450275901745027639c0000").getName());
}

双击testTransactionReadyOnly,右击选择Run As > JUnit Test,如下:

先测试只读事务是否成功。事物的规则已经明确说明只要是在该类下的以find开头的方法只能是只读,不允许任何操作数据库的行为,在TestServiceImpl的findPerson()中,在执行查询操作之前添加保存的动作,看是否报错:

save(new Person("test"));

双击testTransactionReadyOnly,右击选择Run As > JUnit Test,如下:

只读事务测试成功,测试回滚事物,在TestMerge类,代码如下:

@Test
public void testTransactionRollback() {
    TestService testService = (TestService) initSpringContainer.getBean("testService");
    testService.save(new Person("赵六"));
}

同样,事物的规则明确说明除了以上三种方法之外,任何操作数据库出现异常,对这一次的操作进行回滚。在TestServiceImpl的save()中,在执行保存操作之后添加1/0的动作,看保存是否成功,是否进行回滚操作:

int i = 1/0;

双击testTransactionRollback,右击选择Run As > JUnit Test,如下:

控制台也没有插入语句,数据库也没有找到赵六这个人,回滚事务测试成功。

 


配置log4j。控制台之所以出现红色字体是因为:spring容器使用的日志记录组件是log4j,项目里没有相关的配置文件以及jar包。

添加log4j.properties文件到src目录下,配置如下:

#显示在控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 

#日志输出级别:fatal/error/warn/info/debug
log4j.rootLogger=warn, stdout, file

#记录在文件中,E:buildSSH/buildSSH.log
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.File=E:buildSSH/buildSSH.log
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p [%t] %c{1}:%L - %m%n

 添加如下jar包:

这时控制台就不会出现红色字体了,如下:

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值