SSM框架整合(注解版)

概述

Spring框架的核心IoC(DI)和AOP的容器框架,最主要的目的就是帮我们管理Bean的生命周期(实例化和初始化操作),Spring的这种特性可以跟很多的框架进行整合,但是Spring MVC框架本身就隶属于Spring框架,他们两个之间不存在整合关系,只是在扫描注解的时候产生了重叠(@Controller @Service @Repository @Component @Configuration

MyBatis过程:

  1. 解析配置文件,生成配置
//1.指定核心配置文件位置
String path = "config/mybatis-config.xml";
//2.读取核心配置文件内容
InputStream in = Resources.getResourceAsStream(path);
  1. 根据配置,创建SqlSessionFactory对象(需要数据源)
//3.建立SqlSession工厂(生产SqlSession对象)
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
  1. 得到一个真正可用的SqlSession对象,
//4.获取SqlSession对象(主要作用:连接数据库,执行数据库操作)
SqlSession session = factory.openSession();
  1. 在通过接口和映射文件在创建接口的实现类对象
//5.可变:动态实例接口回调
/**底层相当根据映射文件构建:StudentMapper studentMapper = new StudentMapperImpl()*/
StudentMapper studentMapper = session.getMapper(StudentMapper.class);

通过描述,创建对象的过程必须交给Spring容器

在以后整合其他框架的时候,过程是一样的,其他框架对象实例化和初始化操作交给Spring容器

1.Spring和SpringMVC的整合

springmvc和spring都是容器,容器就是管理对象的地方(如Tomcat管理servlet对象),而springMVC容器和spring容器,就是管理bean对象的地方.
说的直白点,springmvc就是管理controller对象的容器,spring就是管理service和dao的容器

添加SpringMVC的依赖(隶属于Spring),因为我们没有跟WEB服务器关联,所以需要添加Servlet API,
返回JSON数据的时候,简单需要一个JSON的转换器

使用首先添加四个依赖:

 <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.11.1</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope><!-- 提供作用范围,不会发布到webapps下 -->
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  • @EnableWebMvc是使用Java 注解快捷配置Spring Webmvc的一个注解。在使用该注解后配置一个继承于WebMvcConfigurerAdapter的配置类即可配置好Spring Webmvc。
  • @ControllerAdvice是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:
    • 全局异常处理
    • 全局数据绑定
    • 全局数据预处理
  • @RestControlle官网解释:

This code uses Spring 4’s new @RestController annotation, which marks the class as a controller where every method returns a domain object instead of a view. It’s shorthand for @Controller and @ResponseBody rolled together.
此代码使用 Spring 4 的新 @RestController 注释,它将类标记为控制器,其中每个方法返回域对象而不是视图。它是 @Controller 和 @ResponseBody 组合在一起的简写。
所以可简单理解其为:
@RestController=@Controller+@ResponseBody

  • @ControllerAdvice,@RestControllerAdvice:异常得到期望的返回格式
  • use-default-filters属性的默认值为 true,即使用默认的 Filter 进行包扫描,而默认的 Filter 对标有@Service,@Controller@Repository 的注解的类进行扫描,设为false后不使用默认规则
  • includeFilters:只对指定注解类扫描
  • excludeFilters:对指定注解类不扫描

当两个配置类扫描包的路径有重合的时候,可以用上述属性方法来配置:
WebConfig:

package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
/*设置为只扫描下列指定四个注解*/
@ComponentScan(basePackages = "com.framework.**.web",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, ControllerAdvice.class}),
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {RestController.class, RestControllerAdvice.class})
},useDefaultFilters = false)
public class WebConfig {
}

SpringConfig:

package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Configuration
/*扫描注解的包类路径不重合,设置不扫描下列指定四个注解:*/
@ComponentScan(basePackages = "com.framework",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, ControllerAdvice.class}),
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {RestController.class, RestControllerAdvice.class})
},useDefaultFilters = true)
public class SpringConfig {
}

2.Spring和MyBatis的整合

pom.xml配置

因为Spring5默认不再执行log4j,那么就暂时不配置日志框架,MyBatis需要数据源、事务管理、加载映射文件、创建接口的实现类对象,统一都需要交给Spring容器管理,MyBatis只是剩下默认配置<settings>或者<typeAlias>等一些特殊标签,所以引入Mybatis框架包

    <!-- MyBatis框架包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.5</version>
    </dependency>

MyBatis没有提供可以直接创建SqlSessionFactory对象的方法,那么MyBatis和Spring整合需要第三方的实现类,创SqlSessionFactory类型的对象

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.5</version>
</dependency>

附录代码

<?xml version="1.0" encoding="UTF-8"?>

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.yue.ssm</groupId>
  <artifactId>ssm</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>ssm Maven Webapp</name>

  <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>
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>

    <!--jackson支持-->
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.11.1</version>
    </dependency>

    <!-- MyBatis框架包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.5</version>
    </dependency>

    <!--创建`SqlSessionFactory`对象的第三方实现类-->
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.5</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.49</version>
    </dependency>

    <!--连接池-->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.23</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope><!-- 提供作用范围,不会发布到webapps下 -->
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>ssm</finalName>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>8.1.16.v20140903</version>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds><!--每隔10秒自动扫描一次class文件,如果被修改==>自动重启-->
          <webApp>
            <contextPath>/ssm</contextPath>
          </webApp><!--发布路径-->
          <stopKey/>
          <stopPort/>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

SpringConfig配置过程

1. 建立数据源
 /**
     * 1.建立数据源
     * @return
     */
    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/yue_mybatis");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("");
        return druidDataSource;
    }

2. 创建SqlSessionFactory接口的实现类对象
 /**
     * 2.创建SqlSessionFactory接口的实现类对象
     * @param dataSource
     * @return
     */
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        //读取Mybatis的本地核心配置文件
        sqlSessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));

        //加载映射文件,文件目录为resources下面
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        //读取以Mapper结尾的映射文件
        Resource[] mapperLocationResource = resourcePatternResolver.getResources("classpath:mapper/*Mapper.xml");
        //存储映射文件
        sqlSessionFactory.setMapperLocations(mapperLocationResource);
        //注入数据源
        sqlSessionFactory.setDataSource(dataSource);

        return sqlSessionFactory;
    }

//加载映射文件,文件目录为resources下面 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); //读取以Mapper结尾的映射文件 Resource[] mapperLocationResource = resourcePatternResolver.getResources("classpath:mapper/*Mapper.xml");
相当于
<mappers> <mapper class="com.yue.mapper.StudentMapper"/>
所以此时配置文件中便可取消加载映射文件的配置

3. 接口的动态实例化
 /**
     * 3.接口的动态实例化
     * 以前是这样写的:sqlSession.getMapper(StudentMapper.class)
     * @return
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        //定义接口位置
        configurer.setBasePackage("com.framework.**.mapper");
        //需要由SqlSessionFactoryBean创建的SqlSession
        configurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
        //如果不同的包中出现了相同的接口,那么必须给捷库设置别名
        configurer.setAnnotationClass(Repository.class);//接口上必须定义该注解
        return configurer;
    }

4. 配置事务管理器相关
  • 启动aop注解和事务注解:
@EnableAspectJAutoProxy(proxyTargetClass = true)//启动AOP的代理(注解)
@EnableTransactionManagement(proxyTargetClass = true)//启动事务注解
4.1创建事务管理器
 /**
     *  4.1创建事务管理器
     * @param dataSource
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

4.2告知哪些方法被事务管理器拦截管理:设置方法约定
 /**
     * 4.2告知哪些方法被事务管理器拦截管理:设置方法约定
     * @param transactionManager
     * @return
     */
    @Bean
    public TransactionInterceptor txMethodAdvice(PlatformTransactionManager transactionManager){
        NameMatchTransactionAttributeSource attributeSource = new NameMatchTransactionAttributeSource();
        //设置规则:只读事务,不错更新操作
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//默认值
        //设置规则:当事务存在就在该事务运行,如果不存在创建新的事务,默认值(变更操作)
        RuleBasedTransactionAttribute requriedTx = new RuleBasedTransactionAttribute();
        requriedTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));//默认值RuntimeException
        requriedTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //设置哪些方法的管理规则
        Map<String, TransactionAttribute> methodMap = new HashMap<>();
        methodMap.put("get*",readOnlyTx);
        methodMap.put("load*",readOnlyTx);
        methodMap.put("find*",readOnlyTx);
        methodMap.put("list*",readOnlyTx);
        methodMap.put("query*",readOnlyTx);
        methodMap.put("select*",readOnlyTx);
        methodMap.put("check*",readOnlyTx);
        methodMap.put("valid*",readOnlyTx);
        methodMap.put("login*",readOnlyTx);

        methodMap.put("*",requriedTx);

        attributeSource.setNameMap(methodMap);

        TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
        transactionInterceptor.setTransactionAttributeSource(attributeSource);
        transactionInterceptor.setTransactionManager(transactionManager);
        return transactionInterceptor;
    }
4.3通过AOP设置哪个层次下的方法被事务管理器管理
/**
     * 4.3通过AOP设置哪个层次下的方法被事务管理器管理
     * @param txMethodAdvice
     * @return
     */
    @Bean
    public Advisor serviceAdvisor(TransactionInterceptor txMethodAdvice){
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.framework..service.*Service.*(..))");
        return new DefaultPointcutAdvisor(pointcut,txMethodAdvice);
    }
5. 其它类
建立持久化类
package com.framework.model;

import java.util.Date;

public class Student {
    private Integer studentId;
    private String studentName;
    private String studentSex;
    private Integer age;
    private Date birthday;
    private Integer classId;

    public Integer getStudentId() {
        return studentId;
    }

    public void setStudentId(Integer studentId) {
        this.studentId = studentId;
    }

    public String getStudentName() {
        return studentName;
    }

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }

    public String getStudentSex() {
        return studentSex;
    }

    public void setStudentSex(String studentSex) {
        this.studentSex = studentSex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Integer getClassId() {
        return classId;
    }

    public void setClassId(Integer classId) {
        this.classId = classId;
    }

    @Override
    public String toString() {
        return "Student{" +
                "studentId=" + studentId +
                ", studentName='" + studentName + '\'' +
                ", studentSex='" + studentSex + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                ", classId=" + classId +
                '}';
    }
}

  • 取别名
 <typeAliases>
        <typeAlias type="com.framework.model.Student" alias="Student"/>
    </typeAliases>
StudentMapper接口
package com.framework.mapper;

import com.framework.model.Student;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface StudentMapper {
    List<Student> list();
    void add(Student student);
    void update();

}

StudentMapper.xml(命名空间和StudentMapper对应)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 接口模式:命名空间必须和接口全名称一致 -->
<mapper namespace="com.framework.mapper.StudentMapper">

    <resultMap id="BaseResultMapper" type="Student">
        <!-- <id>标签:做主键映射,MyBatis框架通过ID标签区分是否是相同数据 -->
        <!-- jdbcType/javaType可以省略不写 -->
        <id column="student_id"  property="studentId"/>
        <result column="student_name"  property="studentName"/>
        <result column="student_sex"  property="studentSex"/>
        <result column="age"  property="age" />
        <result column="birthday"  property="birthday"/>
        <result column="class_id"  property="classId"/>
    </resultMap>
    
    <select id="list" resultMap="BaseResultMapper">
        SELECT * FROM student
    </select>
    <insert id="add" parameterType="Student">
        INSERT INTO student (student_name,student_age,age)
        VALUES (#{studentName},#{student_sex},#[age})
    </insert>
     <update id="update">
         UPDATE student SET student_sex="123456789"WHERE student_id = 11
     </update>
</mapper>

注意:此时数据表中student_sex长度设为2,SET student_sex="123456789"这句肯定出错
在这里插入图片描述

StudentService接口
package com.framework.service;

import com.framework.model.Student;

import java.util.List;

public interface StudentService {
    List<Student> list();
    void add(Student student);
    void get(Student student);//get开头,添加操作-测试事务
    void update(Student student);//先添加正确的数据,再更新数据-测试事务
}

注意,此处只可以用service结尾的名称,因为前面配置通过AOP设置哪个层次下的方法被事务管理器管理时设置了:
pointcut.setExpression("execution(* com.framework..service.*Service.*(..))");
com.framework...service包下且以Service结尾的被事务管理器管理

StudentServiceImpl类
package com.framework.service.impl;

import com.framework.mapper.StudentMapper;
import com.framework.model.Student;
import com.framework.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentMapper studentMapper;
    @Override
    public List<Student> list() {
        return this.studentMapper.list();
    }

    @Override
    public void add(Student student) {
        this.add(student);
    }

    @Override
    public void get(Student student) {
        this.add(student);
    }

    @Override
    public void update(Student student) {
        this.add(student);
        this.studentMapper.update();
    }
}

6. 定义StudentController
package com.framework.web;

import com.framework.model.Student;
import com.framework.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/student")
public class StudentController {
    @Autowired
    private StudentService studentService;
    @GetMapping("/list")
    public List<Student> list(){
        System.out.println("studentService = " + studentService);
        return this.studentService.list();
    }

    @GetMapping("/add")//正确数据
    public void add(){
        Student student = new Student();
        student.setStudentName("起点");
        student.setStudentSex("男");
        student.setAge(22);
        this.studentService.add(student);
    }

    @GetMapping("/insert")//GET方法只是只读状态
    public void insert(){
        Student student = new Student();
        student.setStudentName("终点");
        student.setStudentSex("男");
        student.setAge(22);
        this.studentService.get(student);
    }

    @GetMapping("/update")//正确数据
    public void update(){
        Student student = new Student();
        student.setStudentName("起点001");
        student.setStudentSex("男");
        student.setAge(22);
        this.studentService.update(student);//事务相关
    }
    
}

7.启动时加载配置类

javax.servlet.ServletContainerInitializer接口的实现类在Servlet3.0环境中,用于配置容器。它反过来会查找实现WebApplicationInitializer的类,将配置的任务交给他们来完成。
AbstractAnnotationConfigDispatcherServletInitializer就是WebApplicationInitializer的基础实现,所以当部署到servlet容器中时,容器会发现它的子类,并用子类来配置Servlet上下文。

子类中的重写方法:

  • getServletMappings()将一个或多个路径映射到DispatcherServlet上;
  • getServletConfigClasses()返回的带有@Configuration注解的类用来配置DispatcherServlet;
  • getRootConfigClasses()返回的带有@Configuration注解的类用来配置ContextLoaderListener;

DispatcherServlet启动时,创建Spring应用上下文并加载配置文件或配置类中声明的beanDispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射;
在Spring web应用中,通常还有由ContextLoaderListener创建的另一个上下文。ContextLoaderListener加载应用中的其它bean,通常指驱动应用后端的中间层和数据层组件。

AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServlet和ContextLoaderListener。是传统web.xml方式的替代方式。
此段内容来自侵删

package config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class GlobalApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        /*读取springBoot的配置类*/
        return new Class[]{SpringConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        /*读取servlet的配置*/
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

月色夜雨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值