大家好,又见面了,我是你们的朋友全栈君。
官网案例:
文档(springcloud):https://dromara.org/zh/projects/hmily/user-springcloud/
官网示例:https://github.com/dromara/hmily/tree/master/hmily-demo/hmily-demo-springcloud
一、说明
hmily是一个高性能异步分布式事务TCC框架,具有以下特点:
-
无缝集成Spring,Spring boot start。
-
无缝集成Dubbo,SpringCloud,Motan等rpc框架。
-
多种事务日志的存储方式(redis,mongdb,mysql等)。
-
多种不同日志序列化方式(Kryo,protostuff,hession)。
-
事务自动恢复。
-
支持内嵌事务的依赖传递。
-
代码零侵入,配置简单灵活。
为什么高性能:
1、采用disruptor进行事务日志的异步读写(disruptor是一个无锁,无GC的并发编程框架)
2、异步执行confrim,cancel方法。
当try方法的AOP切面有异常的时候,采用线程池异步去执行cancel,无异常的时候去执行confrim方法。
这里有人可能会问:那么cancel方法异常,或者confrim方法异常怎么办呢?
答:首先这种情况是非常罕见的,因为你上一面才刚刚执行完try。其次如果出现这种情况,在try阶段会保存好日志,Hmily有内置的调度线程池来进行恢复,不用担心。
有人又会问:这里如果日志保存异常了怎么办?
答:首先这又是一个牛角尖问题,首先日志配置的参数,在框架启动的时候,会要求你配置的。其次,就算在运行过程中日志保存异常,这时候框架会取缓存中的,并不会影响程序正确执行。最后,万一日志保存异常了,系统又在很极端的情况下down机了,恭喜你,你可以去买彩票了,最好的解决办法就是不去解决它。
3、ThreadLocal缓存的使用
4、GuavaCache的使用
二、代码示例
本示例是采用springcloud 集成hmily,服务治理采用的是consul,feign接口调用,另外还集成了mybatis
业务场景:
简单的弄了一个新增订单需要处理账户的事务
1、建表语句
/*
SQLyog Ultimate v12.2.6 (64 bit)
MySQL - 5.7.19-0ubuntu0.16.04.1 : Database - account
*********************************************************************
*/
CREATE DATABASE `hmily` ;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`hmily_account` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;
USE `hmily_account`;
/*Table structure for table `account` */
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(128) ,
`balance` decimal(10,0) COMMENT '用户余额',
`freeze_amount` decimal(10,0) COMMENT '冻结金额,扣款暂存余额',
`create_time` TIMESTAMP,
`update_time` TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `account` */
insert into `account`(`id`,`user_id`,`balance`,`freeze_amount`,`create_time`,`update_time`) values
(1,'10000',10000,0,now(),now());
CREATE DATABASE /*!32312 IF NOT EXISTS*/`hmily_stock` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `hmily_stock`;
/*Table structure for table `inventory` */
DROP TABLE IF EXISTS `inventory`;
CREATE TABLE `inventory` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`product_id` VARCHAR(128) NOT NULL,
`total_inventory` int(10) NOT NULL COMMENT '总库存',
`lock_inventory` int(10) NOT NULL COMMENT '锁定库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*Data for the table `inventory` */
insert into `inventory`(`id`,`product_id`,`total_inventory`,`lock_inventory`) values
(1,'1',1000,0);
CREATE DATABASE /*!32312 IF NOT EXISTS*/`hmily_order` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `hmily_order`;
DROP TABLE IF EXISTS `tcc_order`;
CREATE TABLE `tcc_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`create_time` timestamp DEFAULT now(),
`number` varchar(20),
`status` tinyint(4),
`product_id` varchar(128),
`total_amount` decimal(10,0),
`count` int(4) ,
`user_id` varchar(128),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
2、订单工程
1、pom引入
spring cloud引包版本问题是一件非常痛苦的事情,本次为兼容5.x版本mysql采用的是springboot 2.0.5.RELEASE
<?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.zhouzy.order</groupId>
<artifactId>zhouzyOrder</artifactId>
<version>0.0.1-SNAPSHOT</version>s
<name>zhouzyOrder</name>
<parent>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-parent </artifactId>
<version> 2.0.5.RELEASE </version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<!-- 添加Web应用程序的典型依赖项 -->
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-web </artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6</version>
<!--<scope>compile</scope>-->
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily-annotation</artifactId>
<version>2.0.5-RELEASE</version>
</dependency> -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily-spring-boot-starter-springcloud</artifactId>
<version>2.0.0-RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、application.properties配置
需要注意的是,发起方org.dromara.hmily.started=true配置成true
server.port=8082
spring.application.name=order
spring.cloud.consul.discovery.enabled=true
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.serviceName=order
logging.level.com.zhouzy.boot.zhouzyBoot.service=debug
spring.datasource.url= jdbc:mysql://localhost:3306/hmily_order?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
org.dromara.hmily.serializer=kryo
org.dromara.hmily.recoverDelayTime=128
org.dromara.hmily.retryMax=30
org.dromara.hmily.scheduledDelay=128
org.dromara.hmily.scheduledThreadMax=10
org.dromara.hmily.repositorySupport=db
org.dromara.hmily.started=true
org.dromara.hmily.hmilyDbConfig.driverClassName=com.mysql.jdbc.Driver
org.dromara.hmily.hmilyDbConfig.url=jdbc:mysql://localhost:3306/hmily?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
org.dromara.hmily.hmilyDbConfig.username=root
org.dromara.hmily.hmilyDbConfig.password=123456
3、service层配置
需要分布式事务处理的需要加上hmily注解,指定确认方法和取消方法:@Hmily(confirmMethod = “confirmInsert”,cancelMethod = “cancelInsert”)
package com.zhouzy.order.service;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.dromara.hmily.annotation.Hmily;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zhouzy.order.mapper.OrderMapper;
import com.zhouzy.order.model.Account;
import com.zhouzy.order.model.Order;
/**
* @description order
* @author zhouzhiyao
* @date 2021-07-17
*/
@Service
public class OrderServiceImpl implements OrderService {
Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
@Resource
private OrderMapper orderMapper;
@Resource
private ApiService apiService;
@Override
@Hmily(confirmMethod = "confirmInsert",cancelMethod = "cancelInsert")
@Transactional(rollbackFor = Exception.class)
public void insert(Order order) {
log.info("准备插入订单====");
Account account = new Account();
account.setId(1L);
account.setUpdateTime(new Date());
account.setBalance(99);
account.setUserId("10000");
apiService.update(account);
}
@Override
public void delete(int id) {
orderMapper.delete(id);
}
@Override
public void update(Order order) {
orderMapper.update(order);
}
@Override
public Order load(int id) {
return orderMapper.load(id);
}
@Override
public Map<String,Object> pageList(int offset, int pagesize) {
List<Order> pageList = orderMapper.pageList(offset, pagesize);
int totalCount = orderMapper.pageListCount(offset, pagesize);
// result
Map<String, Object> result = new HashMap<String, Object>();
result.put("pageList", pageList);
result.put("totalCount", totalCount);
return result;
}
@Transactional(rollbackFor = Exception.class, timeout = 120)
public void confirmInsert(Order order) {
log.info("确认插入订单");
orderMapper.insert(order);
}
@Transactional(rollbackFor = Exception.class, timeout = 120)
public void cancelInsert(Order order) {
log.info("cancelMethod");
}
}
4、controller层
package com.zhouzy.order.controller;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.zhouzy.order.model.Order;
import com.zhouzy.order.service.OrderService;
/**
* @description order
* @author zhouzhiyao
* @date 2021-07-17
*/
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Resource
private OrderService orderService;
/**
* 新增
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/insert")
public void insert(Order order){
orderService.insert(order);
}
/**
* 刪除
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/delete")
public void delete(int id){
orderService.delete(id);
}
/**
* 更新
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/update")
public void update(Order order){
orderService.update(order);
}
/**
* 查询 根据主键 id 查询
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/load")
public Object load(int id){
return orderService.load(id);
}
/**
* 查询 分页查询
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/pageList")
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int offset,
@RequestParam(required = false, defaultValue = "10") int pagesize) {
return orderService.pageList(offset, pagesize);
}
}
3、账户工程
1、pom引入
跟订单工程差不多,只不过订单工程需要feign调用,多了个feign包的引入
<?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.zhouzy.account</groupId>
<artifactId>zhouzyAccount</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zhouzyAccount</name>
<parent>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-parent </artifactId>
<version> 2.0.5.RELEASE </version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<!-- 添加Web应用程序的典型依赖项 -->
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-web </artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6</version>
<!--<scope>compile</scope>-->
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily-annotation</artifactId>
<version>2.0.5-RELEASE</version>
</dependency> -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily-spring-boot-starter-springcloud</artifactId>
<version>2.0.0-RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、application.properties
server.port=8081
spring.application.name=account
spring.cloud.consul.discovery.enabled=true
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.serviceName=account
######################配置属性的名称完全遵照 Druid##########################
##
spring.datasource.url= jdbc:mysql://localhost:3306/hmily_account?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
pring.datasource.type=com.zaxxer.hikari.HikariDataSource
#最小空闲连接
spring.datasource.min-idle=5
#最大连接数量
spring.datasource.max-active=100
#检测数据库的查询语句
spring.datasource.validation-query=select 1 from dual
#等待连接池分配连接的最大时长(毫秒)
spring.datasource.connection-timeout=60000
#一个连接的生命时长(毫秒)
spring.datasource.max-left-time=60000
#生效超时
spring.datasource.validation-time-out=3000
#一个连接idle状态的最大时长(毫秒)
spring.datasource.idle-time-out=60000
#设置默认字符集
spring.datasource.connection-init-sql= set names utf8mb4
org.dromara.hmily.serializer=kryo
org.dromara.hmily.recoverDelayTime=128
org.dromara.hmily.retryMax=30
org.dromara.hmily.scheduledDelay=128
org.dromara.hmily.scheduledThreadMax=10
org.dromara.hmily.repositorySupport=db
org.dromara.hmily.started=false
org.dromara.hmily.hmilyDbConfig.driverClassName=com.mysql.jdbc.Driver
org.dromara.hmily.hmilyDbConfig.url=jdbc:mysql://localhost:3306/hmily?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
org.dromara.hmily.hmilyDbConfig.username=root
org.dromara.hmily.hmilyDbConfig.password=123456
logging.level.cn.abel.dao=debug
#Mapper.xml所在的位置
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
smybatis.type-aliases-package=cn.abel.bean
#Mapper.xml所在的位置
## pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
3、service层
同理需要分布式事务处理的需要加上,hmily注解:@Hmily(confirmMethod = “confirmUpdate”,cancelMethod = “cancelUpdate”)指定确认方法和取消方法,本示例模拟账户处理逻辑异常
package com.zhouzy.account.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.dromara.hmily.annotation.Hmily;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zhouzy.account.mapper.AccountMapper;
import com.zhouzy.account.model.Account;
/**
* @description account
* @author zhouzhiyao
* @date 2021-07-17
*/
@Service
public class AccountServiceImpl implements AccountService {
Logger log = LoggerFactory.getLogger(AccountServiceImpl.class);
@Resource
private AccountMapper accountMapper;
@Override
public void insert(Account account) {
accountMapper.insert(account);
}
@Override
public void delete(int id) {
accountMapper.delete(id);
}
@Override
@Hmily(confirmMethod = "confirmUpdate",cancelMethod = "cancelUpdate")
@Transactional(rollbackFor = Exception.class)
public void update(Account account) {
int a = 0;
int b = 1;
int c = b/a;
log.info("c:",c);
accountMapper.update(account);
}
@Override
public Account load(int id) {
return accountMapper.load(id);
}
@Override
public Map<String,Object> pageList(int offset, int pagesize) {
List<Account> pageList = accountMapper.pageList(offset, pagesize);
int totalCount = accountMapper.pageListCount(offset, pagesize);
// result
Map<String, Object> result = new HashMap<String, Object>();
result.put("pageList", pageList);
result.put("totalCount", totalCount);
return result;
}
@Transactional(rollbackFor = Exception.class, timeout = 120)
public void confirmUpdate(Account account) {
log.info("confirmUpdate");
}
@Transactional(rollbackFor = Exception.class, timeout = 120)
public void cancelUpdate(Account account) {
log.info("cancelUpdate");
}
}
4、controller层
package com.zhouzy.account.controller;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.zhouzy.account.model.Account;
import com.zhouzy.account.service.AccountService;
/**
* @description account
* @author zhouzhiyao
* @date 2021-07-17
*/
@RestController
@RequestMapping(value = "/account")
public class AccountController {
@Resource
private AccountService accountService;
/**
* 新增
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/insert")
public void insert(Account account){
accountService.insert(account);
}
/**
* 刪除
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/delete")
public void delete(int id){
accountService.delete(id);
}
/**
* 更新
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/update")
public void update(Account account){
accountService.update(account);
}
/**
* 查询 根据主键 id 查询
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/load")
public Object load(int id){
return accountService.load(id);
}
/**
* 查询 分页查询
* @author zhouzhiyao
* @date 2021/07/17
**/
@RequestMapping("/pageList")
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int offset,
@RequestParam(required = false, defaultValue = "10") int pagesize) {
return accountService.pageList(offset, pagesize);
}
}
4、启动测试
先启动consul:consul agent -dev
然后分别启动order和account工程
访问:新增一个订单:http://localhost:8082/order/insert?id=1&number=2&status=0&productId=1&totalAmount=10
feign接口调用异常
执行了取消的方法
另外如果在confirm方法里执行异常了,会在hmily里添加记录,为了后面定时重试
正常执行:
都执行了try和cofirm方法
表里数据都更新成功
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143617.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...