springcloud+nacos+seata系统整合
Seata(Simple Extensible Autonomous Transaction Architecture) 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务 0 侵入的方式,解决微服务场景下面临的分布式事务问题。1.版本JDK : 1.8.0Nacos: 2.2.1.RELEASEOpenFeign: 2.1.3..RELEASENacos: 1.2.1Seata: 1.2.02
Seata(Simple Extensible Autonomous Transaction Architecture) 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务 0 侵入的方式,解决微服务场景下面临的分布式事务问题。
1.版本
JDK : 1.8.0
Nacos: 2.2.1.RELEASE
OpenFeign: 2.1.3..RELEASE
Nacos: 1.2.1
Seata: 1.2.0
2.Seata下载启动
1、seata下载地址
2、创建seata数据库
/*
Navicat Premium Data Transfer
Source Server : 本地
Source Server Type : MySQL
Source Server Version : 80016
Source Host : localhost:3306
Source Schema : seata
Target Server Type : MySQL
Target Server Version : 80016
File Encoding : 65001
Date: 18/05/2020 16:26:39
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`lock_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`gmt_create` datetime(0) NULL DEFAULT NULL,
`gmt_modified` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`gmt_create` datetime(0) NULL DEFAULT NULL,
`gmt_modified` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`transaction_id` mediumtext CHARACTER SET utf8 COLLATE utf8_bin NULL,
`branch_id` mediumtext CHARACTER SET utf8 COLLATE utf8_bin NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`gmt_create` datetime(0) NULL DEFAULT NULL,
`gmt_modified` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
3、修改配置
解压后修改seata-server-1.2.0/seata/conf文件夹下 file.conf, file.conf.example, registry.conf
file.conf
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "db" ##file改为db 并修改数据库url 账号密码
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai" #数据库必须为第二步创建的数据库
user = "root"
password = "root"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
conf.example
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = false
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThreadPrefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
## transaction log store, only used in server side
store {
## store mode: file、db
mode = "db" ##修改file为db 并修改数据库url 账号密码 与file.conf保持一致
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai"
user = "root"
password = "root"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
## server configuration, only used in server side
server {
recovery {
#schedule committing retry period in milliseconds
committingRetryPeriod = 1000
#schedule asyn committing retry period in milliseconds
asynCommittingRetryPeriod = 1000
#schedule rollbacking retry period in milliseconds
rollbackingRetryPeriod = 1000
#schedule timeout retry period in milliseconds
timeoutRetryPeriod = 1000
}
undo {
logSaveDays = 7
#schedule delete expired undo_log in milliseconds
logDeletePeriod = 86400000
}
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
maxCommitRetryTimeout = "-1"
maxRollbackRetryTimeout = "-1"
rollbackRetryTimeoutUnlockEnable = false
}
## metrics configuration, only used in server side
metrics {
enabled = false
registryType = "compact"
# multi exporters use comma divided
exporterList = "prometheus"
exporterPrometheusPort = 9898
}
registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos" ##修改file为nacos 使用nacos作为注册中心
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848" ## 修改nacos地址
namespace = ""
cluster = "default"
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "public"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
3、启动seata
启动命令:修改-p 9200可以改变端口
先启动nacos,再启动seata
Windows:
seata-server.bat -p 9200 -h 127.0.0.1 -m db
Linux:
sh ./seata-server.sh -p 9200 -h 127.0.0.1 -m db
启动之后看nacos服务列表中是否有seata-server
再看 seata启动是否有报错
seata启动完成
启动时有个小坑:seata-server.bat seata-server.sh启动的内存占用比较大:Xms2048m
修改为自己需要的就可以了
seata-server.sh
seata-server.bat
3.给各服务数据库添加undo_log表
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
4.服务置
1、 每个服务导入seata相关依赖
<!--Seata 包-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
2、yml配置 加入seata相关配置
seata:
application-id: ${spring.application.name} # Seata 应用名称,默认使用 ${spring.application.name}
tx-service-group: default # Seata 事务组, 高版本没找到相关配置, 是否可配置未知 选用默认default
# 服务配置项
service:
# 虚拟组和分组的映射 1.0.0以上好像将vgroup-mapping 改为 vgroupMapping, 此处是否影响未测试
vgroupMapping:
# 此处Key对应 tx-service-group 的 Value, 此处 value 默认 default
default: default
# 分组和 Seata 服务的映射 默认端口8091
grouplist:
default: 127.0.0.1:9200
3、feign远程 调用
调用方uc-新闻服方;参与方cc--用户务
调用方:添加事务注解@GlobalTransactional(rollbackFor = Exception.clas),
参方无需添加
调用方
package com.lxtx.uc.controller;
import com.lxtx.cc.feign.ICcAccountClient;
import io.seata.spring.annotation.GlobalTransactional;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperationSupport;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import javax.annotation.Resource;
import javax.validation.Valid;
import com.lxtx.core.mp.support.Condition;
import com.lxtx.core.mp.support.Query;
import com.lxtx.core.tool.api.R;
import com.lxtx.core.tool.utils.Func;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestParam;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.lxtx.uc.entity.UcNews;
import com.lxtx.uc.vo.UcNewsVO;
import com.lxtx.uc.wrapper.UcNewsWrapper;
import com.lxtx.uc.service.IUcNewsService;
import com.lxtx.core.boot.ctrl.LxtxController;
/**
* 新闻表 控制器
*
* @author Lxtx
* @kelly 2020-05-19
*/
@RestController
@AllArgsConstructor
@RequestMapping("/ucNews")
@Api(value = "新闻表", tags = "新闻表接口")
public class UcNewsController extends LxtxController {
private IUcNewsService ucNewsService;
@Resource
private ICcAccountClient iCcAccountClient;
/**
* 新增 新闻表
*/
@GlobalTransactional(rollbackFor = Exception.class)
@PostMapping("/add")
@ApiOperation(value = "新增add", notes = "传入ucNews")
public R add(@Valid @RequestBody UcNews ucNews) {
ucNewsService.save(ucNews);
R r=iCcAccountClient.addAccoount();
if(r.getCode()!=200){
return r;
}
return R.success("新增成功!");
}
}
package com.lxtx.cc.feign;
import com.lxtx.cc.vo.CcAccountVO;
import com.lxtx.core.launch.constant.AppConstant;
import com.lxtx.core.tool.api.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(
value = "lxtx-cc",
fallback = ICcAccountClientFallback.class
)
public interface ICcAccountClient {
String API_PREFIX = "/ccAccount";
@GetMapping(API_PREFIX + "/add")
R addAccoount();
}
package com.lxtx.cc.feign;
import com.lxtx.cc.vo.CcAccountVO;
import com.lxtx.core.tool.api.R;
import org.springframework.stereotype.Component;
@Component
public class ICcAccountClientFallback implements ICcAccountClient {
@Override
public R addAccoount() {
return R.fail("添加账号失败");
}
}
参与方
package com.lxtx.cc.feign;
import com.lxtx.cc.entity.CcAccount;
import com.lxtx.cc.mapper.CcAccountMapper;
import com.lxtx.cc.service.ICcAccountService;
import com.lxtx.cc.vo.CcAccountVO;
import com.lxtx.cc.wrapper.CcAccountWrapper;
import com.lxtx.core.mp.support.Condition;
import com.lxtx.core.tool.api.R;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AllArgsConstructor
public class CcAccountClient implements ICcAccountClient {
private ICcAccountService ccAccountService;
@Override
@GetMapping(API_PREFIX + "/add")
public R addAccoount(){
CcAccount cc=new CcAccount();
cc.setNickname("测试").setAccountType(1).setAccount("5001011992101156999")
.setPhone("13212521259").setTenantId("0000000");
return R.data(ccAccountService.save(cc));
}
}
启动Nacos, Seata后, 启动服务
若Seata控制台输出服务相关信息及数据库信息则连接成功, 如下:
服务配置完成。
5.feign使用降级后不回滚
官方问题:
RootContext.getXID() 获取当前XID后执行手动回滚
编写AOP, 切点事务注解所在的实现类, 各服务都添加此切面类
package com.lxtx.uc.aop;
import io.seata.core.context.RootContext;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Seata 动态事务回滚切面类 (Feign熔断降级回滚)
*/
@Slf4j
@Aspect
@Component
public class AtFeignTransactionalAop {
@Before("execution(* com.lxtx.uc.controller.*.*(..))")
public void before(JoinPoint joinPoint) throws TransactionException, io.seata.core.exception.TransactionException {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
log.info("拦截到需要分布式事务的方法," + method.getName());
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
// 超时时间 , 所在服务
tx.begin(300000, "lxtx-uc");
log.info("创建分布式事务id:{}", tx.getXid());
}
@AfterThrowing(throwing = "e", pointcut = "execution(* com.lxtx.uc.controller.*.*(..))")
public void doRecoveryActions(Throwable e) throws TransactionException, io.seata.core.exception.TransactionException {
log.info("方法执行异常:{}", e.getMessage());
if (!StringUtils.isBlank(RootContext.getXID())) {
log.info("分布式事务Id:{}, 手动回滚!", RootContext.getXID());
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
}
@AfterReturning(value = "execution(* com.lxtx.uc.controller.*.*(..))", returning = "result")
public void afterReturning(JoinPoint point, Object result) throws TransactionException, io.seata.core.exception.TransactionException {
log.info("方法执行结束:{}", result);
// 方法返回值 RespData是自定义的统一返回类
Map map =new org.apache.commons.beanutils.BeanMap(result);
Integer status=200;
if(map.size()>1){
status=Integer.parseInt(map.get("code").toString());
}
if (status != 200) {
if (!StringUtils.isBlank(RootContext.getXID())) {
log.info("分布式事务Id:{}, 手动回滚!", RootContext.getXID());
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
}
}
}
全局异常处理
package com.lxtx.core.log.error;
import com.lxtx.core.log.exception.ServiceException;
import com.lxtx.core.log.publisher.ErrorLogPublisher;
import com.lxtx.core.secure.exception.SecureException;
import com.lxtx.core.tool.api.R;
import com.lxtx.core.tool.api.ResultCode;
import com.lxtx.core.tool.utils.Func;
import com.lxtx.core.tool.utils.UrlUtil;
import com.lxtx.core.tool.utils.WebUtil;
import io.undertow.server.handlers.form.MultiPartParserDefinition;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.Servlet;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
/**
* 全局异常处理,处理可预见的异常
*
* @author Lg
*/
@Slf4j
@Configuration
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@RestControllerAdvice
public class LxtxRestExceptionTranslator {
/**
* 功能描述:全局异常处理
*
* @param e
* @return 返回处理结果
* @throws Exception
*/
@ExceptionHandler(value = Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R errorHandler(Exception e) throws Exception {
// 此处为属性级的错误日志的处理
if (e instanceof ConstraintViolationException) {
log.info("绑定错误日志为:{}", e.getMessage());
return R.fail(ResultCode.INTERNAL_SERVER_ERROR,"请求数据格式错误");
// 此处为方法级别的错误日志处理
} else if (e instanceof MethodArgumentNotValidException) {
log.info("方法级的绑定错误日志为:{}", e.getMessage());
return R.fail(ResultCode.INTERNAL_SERVER_ERROR," 请求数据格式错误");
// 此处为全局错误日志的处理
} else {
log.info("错误日志为:{}", e.getMessage());
return R.fail(ResultCode.INTERNAL_SERVER_ERROR,"未知服务器异常!");
}
}
}
R
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import com.lxtx.core.tool.constant.LxtxConstant;
import com.lxtx.core.tool.utils.ObjectUtil;
import org.springframework.lang.Nullable;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Optional;
/**
* 统一API响应结果封装
*
* @author Lg
*/
@Getter
@Setter
@ToString
@ApiModel(description = "返回信息")
@NoArgsConstructor
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "状态码", required = true)
private int code;
@ApiModelProperty(value = "是否成功", required = true)
private boolean success;
@ApiModelProperty(value = "承载数据")
private T data;
@ApiModelProperty(value = "返回消息", required = true)
private String msg;
private R(IResultCode resultCode) {
this(resultCode, null, resultCode.getMessage());
}
private R(IResultCode resultCode, String msg) {
this(resultCode, null, msg);
}
private R(IResultCode resultCode, T data) {
this(resultCode, data, resultCode.getMessage());
}
private R(IResultCode resultCode, T data, String msg) {
this(resultCode.getCode(), data, msg);
}
private R(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
this.success = ResultCode.SUCCESS.code == code;
}
/**
* 判断返回是否为成功
*
* @param result Result
* @return 是否成功
*/
public static boolean isSuccess(@Nullable R<?> result) {
return Optional.ofNullable(result)
.map(x -> ObjectUtil.nullSafeEquals(ResultCode.SUCCESS.code, x.code))
.orElse(Boolean.FALSE);
}
/**
* 判断返回是否为成功
*
* @param result Result
* @return 是否成功
*/
public static boolean isNotSuccess(@Nullable R<?> result) {
return !R.isSuccess(result);
}
/**
* 返回R
*
* @param data 数据
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> data(T data) {
return data(data, LxtxConstant.DEFAULT_SUCCESS_MESSAGE);
}
/**
* 返回R
*
* @param data 数据
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> data(T data, String msg) {
return data(HttpServletResponse.SC_OK, data, msg);
}
/**
* 返回R
*
* @param code 状态码
* @param data 数据
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> data(int code, T data, String msg) {
return new R<>(code, data, data == null ? LxtxConstant.DEFAULT_NULL_MESSAGE : msg);
}
/**
* 返回R
*
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> success(String msg) {
return new R<>(ResultCode.SUCCESS, msg);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> success(IResultCode resultCode) {
return new R<>(resultCode);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> success(IResultCode resultCode, String msg) {
return new R<>(resultCode, msg);
}
/**
* 返回R
*
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> fail(String msg) {
return new R<>(ResultCode.FAILURE, msg);
}
/**
* 返回R
*
* @param code 状态码
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> fail(int code, String msg) {
return new R<>(code, null, msg);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> fail(IResultCode resultCode) {
return new R<>(resultCode);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> R<T> fail(IResultCode resultCode, String msg) {
return new R<>(resultCode, msg);
}
/**
* 返回R
*
* @param flag 成功状态
* @return R
*/
public static <T> R<T> status(boolean flag) {
return flag ? success(LxtxConstant.DEFAULT_SUCCESS_MESSAGE) : fail(LxtxConstant.DEFAULT_FAILURE_MESSAGE);
}
}
回滚成功!
更多推荐
所有评论(0)