【SpringBoot+Neo4j+MySQL+Mybatis-Plus集成和操作实现】

SpringBoot+Neo4j+MySQL集成和操作实现

吐槽:公司领导突然说了一个之前一直没有听说过的数据库->neo4j图形数据库,当时一阵懵逼,一步步填坑过来,留下项目笔记,等一个有缘人。本文讲解了数据库安装、SpringBoot整合、常规CRUD、自定义分页查询、集成Mybatis-Plus、事务处理。


目录:

本文主要分为如下几个章节进行集成操作示例和填坑说明。

1、 neo4j数据库的下载安装
2、 SpringBoot集成neo4j数据库依赖引入和配置
3、 数据库节点CRUD操作
4、 自定义分页实现
5、 集成MySQL+Mybatis-Plus
6、多数据源事务处理

1、neo4j数据库的下载安装

数据库可以直接在官网下载,根据自己的系统选择,neo4j数据库的使用需要注意JDK的版本,3.*版本jdk要求为8,4.*版本jdk要求为11,本文以jdk8为例。
官网地址:neo4j官网地址社区版下载地址
下载好后windows直接解压(感兴趣的也可下载对应的docker镜像使用)
在对应的bin目录下运行cmd启动数据库输入(相关操作指令可自行了解)

neo4j console

指令运行成功后复制cmd中的地址打开即可访问数据库
如图:
在这里插入图片描述
浏览器访问该地址(会自动跳转到http://localhost:7474/browser/)默认会连接上本地运行的neo4j数据库,若未连接可手动连接如图:
在这里插入图片描述
初始账号和密码都是neo4j,第一次登录会提示修改密码,自行修改后记住密码即可。连接成功如图:
在这里插入图片描述
这样就可以直接在上面的命令行中输入对应的CQL语句进行操作了。

2、SpringBoot集成neo4j数据库依赖引入和配置

SpringBoot集成的neo4j数据库操作有很多版本,且每个版本对应的neo4j操作源码存在很大的差异,这点需要特别注意,版本不对应是无法集成成功的。本文采用springboot.version=2.5.3,对应的neo4j的驱动为6.1.3[此版本高出目前网络上大多资料版本,导致一直踩坑]。
在这里插入图片描述
pom关键配置如下:

 <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.5.3</version>
     <relativePath/>
 </parent>
 
 <dependencies>
	 <!--neo4j-->
	 <dependency>
	     <groupId>org.springframework.boot</groupId>
	     <artifactId>spring-boot-starter-data-neo4j</artifactId>
	 </dependency>
	 <!--neo4j end-->
	 <dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	 </dependency>
 </dependencies>

对应的application.yml数据库连接信息如图:
在这里插入图片描述
注意此处的uri地址,有bolt/http/https三种方式,这个在大家安装该数据库时会有了解。

3、数据库节点CRUD操作

实体类创建

其实和常见实体无太大差别,只是需要指定对应的数据库节点和标明字段为属性等。

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;
import java.io.Serializable;

/**
 * @className:
 * @author: zhaopeng
 * @description: TODO(......)
 * @date: 2021年12月27日 9:09
 */
@Data
@Node("dongMan")
public class DongMan implements Serializable {
    @Id
    @GeneratedValue
    @ApiModelProperty(value = "主键,neo4j数据库自生成值")
    private Long id;
    @Property
    @ApiModelProperty(value = "姓名")
    private String name;
    @Property
    @ApiModelProperty(value = "年龄")
    private String age;
    @Property
    @ApiModelProperty(value = "性别")
    private String sex;
}
创建Service

springboot-data-neo4j提供了和mybatis-plus类似的crud操作仓库,我们可以直接继承使用,也可自行编写实现。
service代码:

import org.springframework.data.domain.Page;
import work.order.system.entity.dongman.DongMan;
import work.order.system.entity.dongman.DongManRelation;

import java.util.List;

/**
 * @className:
 * @author: zhaopeng
 * @description: TODO(......)
 * @date: 2021年12月27日 9:46
 */
public interface DongManService {

   /**
    * TODO(添加对象)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param dongMan
    * @return work.order.system.entity.dongman.DongMan
    */
   DongMan addDongMan(DongMan dongMan);

   /**
    * TODO(根据id查询对象)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param id
    * @return work.order.system.entity.dongman.DongMan
    */
   DongMan getInfoById(long id);

   /**
    * TODO(根据id删除对象)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param id
    * @return void
    */
   void delById(long id);

   /**
    * TODO(指定已存在的2个对象间的关系)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param from
    * @param relation
    * @param to
    * @return void
    */
   void createRelation(String from,String relation, String to);

   /**
    * TODO(生成指定节点的关系,基于关系数据生成)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param fromName
    * @return void
    */
   void createRelationByName(String fromName);

   /**
    * TODO(分页查询动漫人物信息[0为第一页])
    * @author zhaoPeng
    * @date 2021/12/28
    * @param current
    * @param pageSize
    * @param Name
    * @return org.springframework.data.domain.Page<work.order.system.entity.dongman.DongMan>
    */
   Page<DongMan> getListByPage(int current, int pageSize, String Name);

   /**
    * TODO(获取数据库中所有的关系类型)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param
    * @return java.util.List<java.lang.String>
    */
   List<String> getAllRealationTypes();

   /**
    * TODO(判定是否存在该用户)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param id
    * @return java.lang.Boolean
    */
   Boolean existById(long id);

   /**
    * TODO(修改节点信息)
    * @author zhaoPeng
    * @date 2021/12/28
    * @param dongMan
    * @return work.order.system.entity.dongman.DongMan
    */
   DongMan updateById(DongMan dongMan);

   //查询指定用户的所有关系
   List<DongMan> getRelationsByName(String name,String relation);

}

创建Dao

dao操作仓库代码其中有自定义操作集合Mybatis自定义SQL执行类似:

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import work.order.system.entity.dongman.DongMan;
import work.order.system.entity.dongman.DongManRelation;

import java.util.List;

/**
 * @className:
 * @author: zhaopeng
 * @description: TODO(......)
 * @date: 2021年12月27日 9:09
 */
@Repository
public interface DongManRepository extends Neo4jRepository<DongMan,Long> {

    //一对一手动指定关系
    @Query("match (n:dongMan {name:{0}}),(m:dongMan {name:{2}})"+
    "create (n)-[:动漫人物关系{relation:{1}}]->(m)")
    void createRelation(String from,String relation, String to);

    //根据关系数据进行当前用户的所有关系生成
    @Query("match (n:dongMan {name:{0}}),(m:dmRelation),(s:dongMan) where m.from={0} and s.name=m.to create(n)-[:动漫人物关系 {relation:m.relation}]->(s)")
    void createRelationByName(String fromName);

    //根据关系数据进行当前用户的所有关系生成
    @Query("CALL db.relationshipTypes()")
    List<String> getAllRealationTypes();

    //修改
    @Query("MATCH (n) WHERE id(n) = :#{#dongMan.id} SET n.name = :#{#dongMan.name},n.age = :#{#dongMan.age},n.sex = :#{#dongMan.sex} RETURN n")
    DongMan updateById(@Param("dongMan") DongMan dongMan);

    @Query("match (n:dongMan {name:{name}})-[r:`动漫人物关系`]->(m:dongMan) where r.relation={relation} return m")
    List<DongMan> getRelationsByName(@Param("name")String name,@Param("relation")String relation);

    @Query("MATCH (n:dongMan {name:'冯宝宝'}) RETURN n")
    DongMan getTest();
}

创建ServiceImpl

需要注意的是,目前springboot-data-neo4j中的分页首页是从0开始的,不是从1开始的,很多人估计在这个地方郁闷了很久,查询第一页数据时输入1的页码,结果看不到数据。就是因为这个差别导致的!!!
接口实现层代码如下:

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import work.order.system.dao.doc.DocSqlMapper;
import work.order.system.dao.dongman.DongManRepository;
import work.order.system.entity.doc.DocSql;
import work.order.system.entity.dongman.DongMan;
import work.order.system.entity.dongman.DongManRelation;
import work.order.system.service.DongManService;
import javax.annotation.Resource;
import java.util.List;

/**
 * @className:
 * @author: zhaopeng
 * @description: TODO(......)
 * @date: 2021年12月27日 9:49
 */
@Service
@Transactional(value="transactionManager")
public class DongManServiceImpl implements DongManService {

    @Resource
    DongManRepository dongManRepository;

    @Override
    public DongMan addDongMan(DongMan dongMan) {
        return dongManRepository.save(dongMan);
    }

    @Override
    public DongMan getInfoById(long id) {
        return dongManRepository.findById(id).get();
    }

    @Override
    public void delById(long id) {
        dongManRepository.deleteById(id);
    }

    @Override
    public void createRelation(String from, String relation, String to) {
        dongManRepository.createRelation(from,relation,to);
    }

    @Override
    public void createRelationByName(String fromName) {
        dongManRepository.createRelationByName(fromName);
    }

    @Override
    public Page<DongMan> getListByPage(int current, int pageSize, String Name) {
        Pageable pageable= PageRequest.of(current,pageSize);
        return dongManRepository.findAll(pageable);
    }

    @Override
    public List<String> getAllRealationTypes() {
        return dongManRepository.getAllRealationTypes();
    }

    @Override
    public Boolean existById(long id) {
        return dongManRepository.existsById(id);
    }

    @Override
    public DongMan updateById(DongMan dongMan) {
        return dongManRepository.updateById(dongMan);
    }

    @Override
    public List<DongMan> getRelationsByName(String name,String relation) {
        return dongManRepository.getRelationsByName(name,relation);
    }
}
创建Controller

提供服务访问入口进行服务消费
控制层代码如下:

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import work.order.system.entity.dongman.DongMan;
import work.order.system.entity.Result;
import work.order.system.service.DongManService;
import javax.annotation.Resource;

/**
 * @className:
 * @author: zhaopeng
 * @description: TODO(......)
 * @date: 2021年12月27日 9:23
 */
@RestController
@RequestMapping("/dongMan")
@Api(tags="动漫人物操作测试")
public class DongManController {

    @Resource
    DongManService dongManService;

    @PostMapping("/addDongMan")
    @ApiOperation(value="添加对象节点")
    public Result addDongMan(DongMan dongMan) {
        return Result.success("操作成功!", dongManService.addDongMan(dongMan));
    }

    @PostMapping("/delById")
    @ApiOperation(value="根据主键删除")
    @ApiImplicitParam(name="id",value="主键",paramType="form")
    public Result delById(Long id) {
        dongManService.delById(id);
        return Result.success("操作成功!");
    }

    @PostMapping("/updateDongMane")
    @ApiOperation(value="修改节点信息")
    public Result updateDongMane(DongMan dongMan) {
        return Result.success("操作成功!", dongManService.updateById(dongMan));
    }

    @GetMapping("/getInfoById")
    @ApiOperation(value="根据主键查询")
    @ApiImplicitParam(name="id",value="主键",paramType="form")
    public Result getInfoById(Long id) {
        return Result.success("操作成功!",dongManService.getInfoById(id));
    }
    
    @GetMapping("/getAllRelationTypes")
    @ApiOperation(value="获取所有的关系类型")
    public Result getAllRelationTypes() {
        return Result.success("操作成功!", dongManService.getAllRealationTypes());
    }

    @PostMapping("/addDMRelationShip")
    @ApiOperation(value="指定两个节点的关系(两个节点须存在)")
    @ApiImplicitParams({
            @ApiImplicitParam(name="name",value="对象名:唐三-",paramType="form"),
            @ApiImplicitParam(name="relation",value="关系[父亲]->",paramType="form"),
            @ApiImplicitParam(name="to",value="对象名:唐昊",paramType = "form")
    })
    public Result addDMRelationShip(String name,String relation,String to) {
        //直接指定关系
        dongManService.createRelation(name, relation, to);
        return Result.success("操作成功!" );
    }

    @GetMapping("/getRelationsByName")
    @ApiOperation(value="获取指定节点指定关系信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name="name",value="对象名:唐三",paramType="form"),
            @ApiImplicitParam(name="relation",value="具体关系",paramType = "form")
    })
    public Result getRelationsByName(String name,String relation) {
        return Result.success("操作成功!" , dongManService.getRelationsByName(name,relation));
    }
}

效果演示

运行项目进行接口操作验证,我这边使用swagger进行测试,(因为我数据库中已经有测试数据了,我们先看下数据库中的数据)。
源数据库中的数据如图:
在这里插入图片描述
共有18条记录,我们验证添加节点操作,新增武庚和逆天而行。如图:
在这里插入图片描述
创建节点时会根据对应实体上的注解指定的节点名进行创建(也可以先手动在数据库中创建好节点)。
创建节点指令:

create (n:dongMan {name:'李四',sex:'男',age:'22'})	

数据库中如图:
在这里插入图片描述
其他的修改和删除操作就不一一演示了,下面演示下给这两个节点添加关系。
在这里插入图片描述
在这里插入图片描述
基础的操作就实现了。

4、 自定义分页实现

在controller中添加如下代码:

    @GetMapping("/getListByPage")
    @ApiOperation(value="分页查询")
    @ApiImplicitParams({
            @ApiImplicitParam(name="current",value="起始页0为第一页",paramType="form"),
            @ApiImplicitParam(name="pageSize",value="页数量",paramType = "form")
    })
    public Result getListByPage(int current,int pageSize) {
        return Result.success("操作成功!",dongManService.getListByPage(current,pageSize,""));
    }

在swagger中查看效果
在这里插入图片描述
该分页实现为源码中的PagingAndSortingRepository仓库实现的,但是源码中只有一个排序操作没有关于参数指定的分页查询实现,即需要我们自己手动实现自定义分页操作。
自定义分页实现service加入如下代码:

   /**
    * TODO(查看当前用户的关系)
    * @author zhaoPeng
    * @date 2021/12/29
    * @param name
    * @return java.util.List<work.order.system.entity.dongman.DongMan>
    */
   Page<DongManRelation> getRelationsByName(int current, int pageSize,String name);

dao加入如下代码:

    @Query(value="match (n:dongMan {name:{name}})-[r:`动漫人物关系`]->(m:dongMan) return id(n) as pid, n.name as name,r.relation as relation" +
            ",m as dongMan skip {skip} limit {pageSize}"
        ,countQuery = "match (n:dongMan {name:{name}})-[r:`动漫人物关系`]->(m:dongMan) return count(r)")
    Page<DongManRelation> getRelationsByName(@Param("name")String name,@Param("skip")int ship,@Param("pageSize")int pageSize,Pageable pageable);
    //不传递pageable分页无效

对应的CQL需要指定skip和limit的值一级获取总条数的SQL,且必须接收Pageable。
serviceImpl加入如下代码:

    @Override
    public Page<DongManRelation> getRelationsByName(int current, int pageSize,String name) {
        Pageable pageable= PageRequest.of(current,pageSize);
        return dongManRepository.getRelationsByName(name,current*pageSize,pageSize,pageable);
    }

controller加入如下代码:

    @GetMapping("/getRelations")
    @ApiOperation(value="获取指节点关系信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name="name",value="对象名:唐三",paramType="form"),
            @ApiImplicitParam(name="current",value="起始页0为第一页",paramType="form"),
            @ApiImplicitParam(name="pageSize",value="页数量",paramType = "form")
    })
    public Result getRelations(int current,int pageSize,String name) {
        return Result.success("操作成功!" , dongManService.getRelationsByName(current,pageSize,name));
    }

效果如图:
在这里插入图片描述
这样就满足了自定义条件分页查询的实现了。

5、 集成MySQL+Mybatis-Plus

pom关键添加如下信息:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

application.yml添加如下信息:

spring:  
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xxxxxxxx:3306/xxx?useUnicode=true&characterEncoding=utf-8
    username: root
    password: xxxx

#mybatis-plus设置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml # Mapper文件的位置
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志的实现类(打印SQL)
    map-underscore-to-camel-case: true # 下划线转驼峰

添加好如上信息后直接按照SpringBoot集成Mybatis-plus的crud操作实现即可进行mysql数据库的操作,无需进行数据源切换(目前未明确为何可自行寻找到数据源进行数据库操作,欢迎大家留言讨论)。这里就直接演示MySQL的操作了,如图:
在这里插入图片描述
注意哈:mybatis的分页首页为1,neo4j的首页为0。

6、多数据源事务处理

在实际的操作中很有可能用到事务的回滚功能,集成了MySQL和neo4j数据库后,只需对事务进行简单配置即可实现。
首先启用事务,在启动类上添加@EnableTransactionManagement注解。因为集成了不同的数据库,直接启用事务,系统无法辨别具体的回滚操作在那个数据库执行,我的实现如下,添加事务配置类:

import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;

/**
 * @className:
 * @author: zhaopeng
 * @description: TODO(......)
 * @date: 2021年12月29日 10:43
 */
@Configuration
public class TransactionConfig {

    /**
     * TODO(mySQL数据库事务,根据数据源控制)
     * @author zhaoPeng
     * @date 2021/12/29
     * @param dataSource
     * @return org.springframework.jdbc.datasource.DataSourceTransactionManager
     */
    @Bean("mysqlTransaction")
    public DataSourceTransactionManager jpaTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * TODO(neo4j数据库事务操作 避坑所在[bean必须是这个名字transactionManager])
     * @author zhaoPeng
     * @date 2021/12/29
     * @param driver
     * @return org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager
     */
    @Bean("transactionManager")
    public Neo4jTransactionManager neo4jTransactionManager(Driver driver) {
        return new Neo4jTransactionManager(driver);
    }
}

此处一定要注意neo4j的事务必须命名为transactionManager,否则会出错。这个地方坑了很长时间。
在对应的serviceImpl类或者具体的实现方法上加入事务注解,指定使用那个事务进行实现。依据数据库进行区分,
mysql数据库操作的添加@Transactional(value=“mysqlTransaction”)
neo4j数据库操作的添加@Transactional(value=“transactionManager”)
这样就可以实现事务的回滚操作了。我们在DongManServiceImpl类中添加异常操作,验证事务是否生效。
修改原有addDongMan方法为:

    @Override
    public DongMan addDongMan(DongMan dongMan) {
        //正常操作
        dongManRepository.save(dongMan);
         //异常操作
       dongManRepository.getTest();
        return dongManRepository.save(dongMan);
    }

修改DongManRepository中的getTest接口的CQL为错误的CQL:

    @Query("MATCH (n:dongMan {name:冯宝宝}) RETURN n")
    DongMan getTest();

进行操作验证,添加一个CSDN的节点,添加成功则事务无效,因为添加成功操作后面有一个异常操作,事务应该回滚才正确。
在这里插入图片描述
后台异常如图:
在这里插入图片描述
数据库中查看是否添加CSDN成功:
在这里插入图片描述
说明neo4j的事务生效了,mysql的事务验证就不演示了,这里再演示下neo4j操作成功,mysql操作失败的数据库混合操作,看事务是否生效:
修改DongManServiceImpl中的addDongMan,引入mysql的数据库操作。代码如下:

    //模拟混合数据源事务管理
    @Resource
    DocSqlMapper sqlMapper;

    @Override
    public DongMan addDongMan(DongMan dongMan) {
        //正常操作
        dongManRepository.save(dongMan);
         //异常操作
        DocSql docSql2 = sqlMapper.selectById("3");
        docSql2.setDocId(null);
        docSql2.setCreateTime("111");
        sqlMapper.updateById(docSql2);
        return dongMan;
    }

运行查看事务是否生效:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
MySQL操作失败,neo4j操作成功,但是2个操作在一个方法中,事务回滚,判定为操作失败,数据库不进行操作成功的数据写入。混合式事务验证成功。

大家不喜勿喷!

  • 13
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Vue是一种用于构建用户界面的JavaScript框架,而Spring Boot是一个用于构建企业级Java应用的框架,Neo4j是一种图形数据库。想要将它们结合起来实现视图可视化,可以按照以下步骤进行: 1. 使用Vue来创建前端界面:使用Vue的组件和路由功能,可以方便地创建用户界面。可以使用Vue的模板语法和数据绑定来实现动态更新界面的功能。 2. 使用Spring Boot来创建后端服务:Spring Boot提供了丰富的功能和库,可以轻松地构建和管理后端服务。可以使用Spring Boot的依赖注入和控制反转来管理组件之间的依赖关系。 3. 使用Neo4j来存储和管理数据:Neo4j是一种图形数据库,可以存储关系型数据,并提供灵活的查询功能。可以使用Neo4j的图形查询语言来查询和操作数据库中的数据。 4. 将Vue和Spring Boot连接起来:可以使用Vue的Ajax或Axios等工具来发送和接收数据。可以使用Spring Boot的REST API来处理前端请求,并与Neo4j进行交互。 5. 实现数据的可视化:根据需要,在前端界面中使用合适的图表库或可视化工具来展示从后端获取的数据。可以根据数据类型选择合适的图表类型,如柱状图、折线图或饼图等。 通过以上步骤,就可以实现将Vue、Spring BootNeo4j结合起来,实现视图可视化的功能。前端界面使用Vue进行开发,后端服务使用Spring Boot进行构建和管理,而数据存储和查询则使用Neo4j进行处理。最终,可以通过前端界面展示从后端获取的数据,并以图表或其他形式进行可视化展示。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值