微服务
Spring Cloud Alibaba
项目继承 spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent>
依赖管理Spring Cloud
和Spring Cloud Alibaba
的依赖版本管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Nacos服务注册与发现
所需依赖(使用dubbo nacos不能使用改依赖不用配置)
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
配置
spring:
application:
# 服务名
name: service-provider
cloud:
nacos:
discovery:
# 服务注册中心
server-addr: 192.168.141.132:8848
management:
# 端点检查(健康检查)
endpoints:
web:
exposure:
include: "*"
为启动类添加@EnableDiscoveryClient
示例
服务注册发现依赖配置都相同
依赖
<dependencies>
<!-- Spring Boot Begin -->
<dependency>
<!-- 提供WEB服务 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<!-- 健康检查 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<!-- 测试 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot End -->
<!-- Spring Cloud Begin -->
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<!-- Spring Cloud End -->
</dependencies>
配置
spring:
application:
# 服务名
name: service-provider
cloud:
nacos:
discovery:
# 服务注册中心
server-addr: 192.168.141.132:8848
server:
# 服务端口
port: 8070
management:
# 端点检查(健康检查)
endpoints:
web:
exposure:
include: "*"
Application
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
Nacos分布式配置中心
Nacos Feign
增加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
通过 @EnableFeignClients
注解开启 Feign 功能
在要通过feign请求Http的服务上添加
如果报错 无法为EchoService创建Bean 时 为@EnableFeignClients添加@EnableFeignClients(basePackage="EchoService的包名")
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
service创建业务结构,通过 @FeignClient("服务名")
注解来指定调用哪个服务
@FeignClient(value = "service-provider")
public interface EchoService {
//对应要请求的WEB服务
@GetMapping(value = "/echo/{string}")
String echo(@PathVariable("string") String string);
}
使用Feign访问Http服务
@RestController
public class TestEchoController {
@Autowired
private EchoService echoService;
@GetMapping(value = "/feign/echo/{str}")
public String echo(@PathVariable String str) {
return echoService.echo(str);
}
}
Sentinel客户端接入
增加依赖
<!-- sentinel 服务熔断 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置
spring:
application:
# 服务名
name: service-consumer
cloud:
nacos:
discovery:
# 服务注册中心
server-addr: 192.168.141.132:8848
# 熔断限流
sentinel:
transport:
dashboard: localhost:8888
# 开启 Feign 对 Sentinel 的支持
feign:
sentinel:
enabled: true
server:
# 服务端口
port: 8080
management:
# 端点检查(健康检查)
endpoints:
web:
exposure:
include: "*"
配置熔断类
编写一个 Feign 接口的实现类并增加 @Component
注解
@Component
public class EchoServiceFallback implements EchoService {
@Override
public String echo(String string) {
return "echo fallback";
}
@Override
public String lb() {
return "lb fallback";
}
}
修改Feign接口
在 @FeignClient
接口上增加 fallback
属性指定熔断类即可
@FeignClient(value = "service-provider", fallback = EchoServiceFallback.class)
public interface EchoService {
@GetMapping(value = "/echo/{string}")
String echo(@PathVariable("string") String string);
@GetMapping(value = "/lb")
String lb();
}
Dubbo
依赖
服务提供方和 消费方增加依赖
<!-- Apache Dubbo -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
实战项目的配置(???不知道为啥排除)
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${dubbo-nacos.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-kryo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>${alibaba-spring-context-support.version}</version>
</dependency>
服务提供方配置
主要增加了 Dubbo 包扫描路径和 Nacos Server 配置
spring:
application:
name: dubbo-provider
main:
# 表示后发现的bean会覆盖之前相同名称的bean
allow-bean-definition-overriding: true
dubbo:
scan:
base-packages: com.grq.provider.service
protocol:
name: dubbo
port: -1
# 配置注册中心
registry:
# dubbo nacos 注册中心
# 配置之后 不需要spring.cloud.nacos.discovery.server-addr=localhost:8080
address: nacos://192.168.141.132:8848
服务提供方Service
通过 org.apache.dubbo
包下的 @Service
注解将服务暴露出去
import com.funtl.apache.dubbo.provider.api.EchoService;
import org.apache.dubbo.config.annotation.Service;
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {
@Override
public String echo(String string) {
return "Echo Hello Dubbo " + string;
}
}
服务消费方配置
spring:
application:
name: dubbo-consumer
main:
allow-bean-definition-overriding: true
dubbo:
scan:
base-packages: com.grq.consumer.controller
protocol:
name: dubbo
port: -1
registry:
address: nacos://192.168.141.132:8848
server:
port: 8080
endpoints:
dubbo:
enabled: true
management:
health:
dubbo:
status:
defaults: memory
extras: threadpool
endpoints:
web:
exposure:
include: "*"
服务消费方Controller
通过 org.apache.dubbo
包下的 @Reference
注解像调用本地服务一样调用远程服务,轻松实现透明的远程过程调用
import com.funtl.apache.dubbo.provider.api.EchoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EchoController {
@Reference(version = "1.0.0")
private EchoService echoService;
@GetMapping(value = "/echo/{string}")
public String echo(@PathVariable String string) {
return echoService.echo(string);
}
}
Dubbo实现高速序列化
在 Provider 和 Consumer 项目启用 Kryo 高速序列化功能,两个项目的配置方式相同
依赖
增加 org.apache.dubbo:dubbo-serialization-kryo
依赖
<properties>
<dubbo-kryo.version>2.7.2</dubbo-kryo.version>
</properties>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-kryo</artifactId>
<version>${dubbo-kryo.version}</version>
</dependency>
配置
服务双方增加 dubbo.protocol.serialization=kryo
配置
注意: 想要使用 Kryo 序列化只需要 DTO/Domain/Entity 这类传输对象实现序列化接口即可,无需额外再做配置,如:public class User implements Serializable{}
dubbo:
protocol:
serialization: kryo
Dubbo 负载均衡
修改 dubbo-provider
项目的负载均衡策略,默认的负载均衡策略是 随机,我们修改为 轮循,可配置的值分别是:random
,roundrobin
,leastactive
,consistenthash
dubbo:
provider:
loadbalance: roundrobin
Dubbo外部化配置
Tk.Mybatis
依赖
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
配置
mybatis:
type-aliases-package: com.grq.provider.domain
mapper-locations: classpath:mapper/*.xml
启动类添加@MapperScan(basePackages = "")
@SpringBootApplication
@MapperScan(basePackages = "com.grq.provider.mapper")
public class UmsAdminProviderApplication {
public static void main(String[] args) {
SpringApplication.run(UmsAdminProviderApplication.class, args);
}
}
Oauth2
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。oAuth 2.0 定义了四种授权方式。
-
implicit:简化模式,不推荐使用
-
authorization code:授权码模式
-
resource owner password credentials:密码模式
-
client credentials:客户端模式
依赖
认证服务器与资源服务器都需配置
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
配置及常用URL
-
配置认证服务器
-
配置客户端信息:
ClientDetailsServiceConfigurer
inMemory
:内存配置withClient
:客户端标识secret
:客户端安全码authorizedGrantTypes
:客户端授权类型scopes
:客户端授权范围redirectUris
:注册回调地址
-
-
配置 Web 安全
-
通过 GET 请求访问认证服务器获取授权码
- 端点:
/oauth/authorize
- 端点:
-
通过 POST 请求利用授权码访问认证服务器获取令牌
- 端点:
/oauth/token
- 端点:
默认的端点 URL
/oauth/authorize
:授权端点/oauth/token
:令牌端点/oauth/confirm_access
:用户确认授权提交端点/oauth/error
:授权服务错误信息端点/oauth/check_token
:用于资源服务访问的令牌解析端点/oauth/token_key
:提供公有密匙的端点,如果你使用 JWT 令牌的话
认证服务器
服务器安全配置(账号密码,权限)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
}
认证服务器配置(认证模式,存储模式)
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
}
资源服务器
配置
security:
oauth2:
client:
client-id: client
client-secret: secret
access-token-uri: http://localhost:8080/oauth/token
user-authorization-uri: http://localhost:8080/oauth/authorize
resource:
token-info-uri: http://localhost:8080/oauth/check_token
资源配置类
添加@EnableResourceServer
,@EnableGlobalMethodSecurity
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
}
oAuth2 相关表
CREATE TABLE `clientdetails` (
`appId` varchar(128) NOT NULL,
`resourceIds` varchar(256) DEFAULT NULL,
`appSecret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`grantTypes` varchar(256) DEFAULT NULL,
`redirectUrl` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additionalInformation` varchar(4096) DEFAULT NULL,
`autoApproveScopes` varchar(256) DEFAULT NULL,
PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(128) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
`authentication` blob,
`refresh_token` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) DEFAULT NULL,
`clientId` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`status` varchar(10) DEFAULT NULL,
`expiresAt` timestamp NULL DEFAULT NULL,
`lastModifiedAt` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(128) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_client_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(128) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_code` (
`code` varchar(256) DEFAULT NULL,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
RBAC基于角色的访问控制相关表
CREATE TABLE `tb_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父权限',
`name` varchar(64) NOT NULL COMMENT '权限名称',
`enname` varchar(64) NOT NULL COMMENT '权限英文名称',
`url` varchar(255) NOT NULL COMMENT '授权路径',
`description` varchar(200) DEFAULT NULL COMMENT '备注',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8 COMMENT='权限表';
insert into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values
(37,0,'系统管理','System','/',NULL,'2019-04-04 23:22:54','2019-04-04 23:22:56'),
(38,37,'用户管理','SystemUser','/users/',NULL,'2019-04-04 23:25:31','2019-04-04 23:25:33'),
(39,38,'查看用户','SystemUserView','',NULL,'2019-04-04 15:30:30','2019-04-04 15:30:43'),
(40,38,'新增用户','SystemUserInsert','',NULL,'2019-04-04 15:30:31','2019-04-04 15:30:44'),
(41,38,'编辑用户','SystemUserUpdate','',NULL,'2019-04-04 15:30:32','2019-04-04 15:30:45'),
(42,38,'删除用户','SystemUserDelete','',NULL,'2019-04-04 15:30:48','2019-04-04 15:30:45');
CREATE TABLE `tb_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父角色',
`name` varchar(64) NOT NULL COMMENT '角色名称',
`enname` varchar(64) NOT NULL COMMENT '角色英文名称',
`description` varchar(200) DEFAULT NULL COMMENT '备注',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='角色表';
insert into `tb_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values
(37,0,'超级管理员','admin',NULL,'2019-04-04 23:22:03','2019-04-04 23:22:05');
CREATE TABLE `tb_role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL COMMENT '角色 ID',
`permission_id` bigint(20) NOT NULL COMMENT '权限 ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
insert into `tb_role_permission`(`id`,`role_id`,`permission_id`) values
(37,37,37),
(38,37,38),
(39,37,39),
(40,37,40),
(41,37,41),
(42,37,42);
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(64) NOT NULL COMMENT '密码,加密存储',
`phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
`email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE,
UNIQUE KEY `phone` (`phone`) USING BTREE,
UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='用户表';
insert into `tb_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values
(37,'admin','$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi','15888888888','lee.lusifer@gmail.com','2019-04-04 23:21:27','2019-04-04 23:21:29');
CREATE TABLE `tb_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户 ID',
`role_id` bigint(20) NOT NULL COMMENT '角色 ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='用户角色表';
insert into `tb_user_role`(`id`,`user_id`,`role_id`) values
(37,37,37);
HikariCP
依赖
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.141.130:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
OKHttp3
依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.0.1</version>
</dependency>
测试 Get 请求
@Test
public void testGet() {
String url = "https://www.baidu.com";
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
测试 POST 请求
@Test
public void testPost() {
String url = "http://localhost:9001/oauth/token";
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.add("grant_type", "password")
.add("client_id", "client")
.add("client_secret", "secret")
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
路由网关
依赖
主要增加了 org.springframework.cloud:spring-cloud-starter-gateway
依赖
<dependencies>
<!-- Spring Boot Begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot End -->
<!-- Spring Cloud Begin -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud End -->
<!-- Commons Begin -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- Commons Begin -->
</dependencies>
注意: Spring Cloud Gateway 不使用 Web 作为服务器,而是 使用 WebFlux 作为服务器 ,Gateway 项目已经依赖了 starter-webflux
,所以这里 千万不要依赖 starter-web
注意: 由于过滤器等功能依然需要 Servlet 支持,故这里还需要依赖 javax.servlet:javax.servlet-api
Application
package com.grq.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
// ----------------------------- 解决跨域 Begin -----------------------------
private static final String ALL = "*";
private static final String MAX_AGE = "3600L";
@Bean
public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
@Bean
public ServerCodecConfigurer serverCodecConfigurer() {
return new DefaultServerCodecConfigurer();
}
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
};
}
// ----------------------------- 解决跨域 End -----------------------------
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
配置
base:
config:
nacos:
hostname: nacos.funtl.com
port: 80
spring:
application:
# 应用名称
name: gateway
main:
allow-bean-definition-overriding: true
cloud:
# 使用 Nacos 作为服务注册发现
nacos:
discovery:
server-addr: ${base.config.nacos.hostname}:${base.config.nacos.port}
# 路由网关配置
gateway:
# 设置与服务注册发现组件结合,这样可以采用服务名的路由策略
discovery:
locator:
enabled: true
# 配置路由规则
routes:
# 采用自定义路由 ID(有固定用法,不同的 id 有不同的功能,详见:https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-route-filters)
- id: BUSINESS-OAUTH2
# 采用 LoadBalanceClient 方式请求,以 lb:// 开头,后面的是注册在 Nacos 上的服务名
uri: lb://business-oauth2
# Predicate 翻译过来是“谓词”的意思,必须,主要作用是匹配用户的请求,有很多种用法
predicates:
# 路径匹配,以 api 开头,直接配置是不生效的,看 filters 配置
- Path=/api/user/**
filters:
# 前缀过滤,默认配置下,我们的请求路径是 http://localhost:8888/business-oauth2/** 这时会路由到指定的服务
# 此处配置去掉 1 个路径前缀,再配置上面的 Path=/api/**,就能按照 http://localhost:8888/api/** 的方式访问了
- StripPrefix=1
- id: BUSINESS-PROFILE
uri: lb://business-profile
predicates:
- Path=/api/profile/**
filters:
- StripPrefix=1
server:
port: 8888
# 配置日志级别,方别调试
logging:
level:
org.springframework.cloud.gateway: debug
注意: 由于之前的 RestController
代码中使用了 @CrossOrigin(value = "*", maxAge = 3600)
注解解决跨域问题,此时我们的网关已经一并处理了跨域请将之前的注解删除,否则会报 The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
错误,表示配置了多个跨域
Feign-okHttp
Feign
提供了 3 种方式分别为:
- URLConnection(default)
- OkHttp
- ApacheHttpClient
增加 io.github.openfeign:feign-okhttp:10.2.3
依赖,让 Feign
客户端通过 OKHttp3
访问
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
配置
增加开启 Feign
的 OKHttp3
方式请求
# 开启 Feign 的 OKHttp 方式
feign:
okhttp:
enabled: true
httpclient:
enabled: false
RocketMQ
系统服务调用消息生产者产生消息
消息生产者将消息发送到RocketMQ服务器
消息消费者订阅RocketMQ服务器
在RocketMQ服务器有消息时消息消费者调用消息服务提供者将消息存储到数据库中
依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
消息生产者
配置
spring:
cloud:
# RocketMQ 相关配置
stream:
rocketmq:
binder:
name-server: ${base.config.rocketmq.hostname}:${base.config.rocketmq.port}
bindings:
admin-login-log-topic: {destination: admin-login-log-topic, content-type: application/json}
自定义Binding
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
public interface MessageSource {
@Output("admin-login-log-topic")
MessageChannel adminLoginLog();
}
创建消息生产者
import com.funtl.myshop.plus.cloud.dto.UmsAdminLoginLogDTO;
import com.funtl.myshop.plus.cloud.message.MessageSource;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class MessageProducer {
@Resource
private MessageSource source;
/**
* 管理登录日志
*
* @param dto {@link UmsAdminLoginLogDTO}
* @return {@code boolean}
*/
public boolean sendAdminLoginLog(UmsAdminLoginLogDTO dto) {
return source.adminLoginLog().send(MessageBuilder.withPayload(dto).build());
}
}
Controller
@RestController
@RequestMapping(value = "message")
public class MessageController {
@Resource
private MessageProducer messageProducer;
@PostMapping(value = "admin/login/log")
public ResponseResult<Void> sendAdminLoginLog(@RequestBody UmsAdminLoginLogDTO dto) {
boolean flag = messageProducer.sendAdminLoginLog(dto);
// 发送成功
if (flag) {
return new ResponseResult<Void>(ResponseResult.CodeStatus.OK, "管理员登录日志发送成功");
}
// 发送失败
else {
return new ResponseResult<Void>(ResponseResult.CodeStatus.FAIL, "管理员登录日志发送失败");
}
}
}
Application
@SpringBootApplication
@EnableDiscoveryClient
@EnableBinding({MessageSource.class})
public class CloudMessageApplication {
public static void main(String[] args) {
SpringApplication.run(CloudMessageApplication.class, args);
}
}
消息消费者
配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: ${base.config.rocketmq.hostname}:${base.config.rocketmq.port}
bindings:
admin-login-log-topic: {consumer.orderly: true}
bindings:
# consumer.maxAttempts
# 消息最大可以被尝试消费的次数,包含第一次投递
# 设为 1,表示不重试,注意该值必须大于 0
admin-login-log-topic: {destination: admin-login-log-topic, content-type: text/plain, group: admin-login-log-group, consumer.maxAttempts: 1}
自定义Binding
public interface AdminLoginLogSink {
@Input("admin-login-log-topic")
SubscribableChannel adminLoginLog();
}
Application
@SpringBootApplication
@EnableBinding({AdminLoginLogSink.class})
public class MessageAdminLoginLogApplication {
public static void main(String[] args) {
SpringApplication.run(MessageAdminLoginLogApplication.class, args);
}
}
创建消费者
@Service
public class AdminLoginLogReceive {
@Reference(version = "1.0.0")
private UmsAdminLoginLogService umsAdminLoginLogService;
//监听admin-login-log-topic主题
//主题如果有新消息就调用该方法
@StreamListener("admin-login-log-topic")
public void receiveAdminLoginLog(String umsAdminLoginLogJson) throws Exception {
UmsAdminLoginLog umsAdminLoginLog = MapperUtils.json2pojo(umsAdminLoginLogJson, UmsAdminLoginLog.class);
umsAdminLoginLogService.insert(umsAdminLoginLog);
}
}
问题
解决 JSON 日期转换问题
问题的原因是日期的形式是字符串 yyyy-MM-dd HH:mm:ss
,Jackson 无法将该字符串转成一个 Date
对象;只需要在领域模型的日期字段上增加 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
注解即可
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Column(name = "create_time")
private Date createTime;
工具类
响应结果集ResponseResult
package com.funtl.myshop.plus.commons.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 通用数据传输对象
*/
@Data
public class ResponseResult<T> implements Serializable {
private static final long serialVersionUID = 3468352004150968551L;
/**
* 状态码
*/
private Integer code;
/**
* 消息
*/
private String message;
/**
* 返回对象
*/
private T data;
public ResponseResult() {
super();
}
public ResponseResult(Integer code) {
super();
this.code = code;
}
public ResponseResult(Integer code, String message) {
super();
this.code = code;
this.message = message;
}
public ResponseResult(Integer code, Throwable throwable) {
super();
this.code = code;
this.message = throwable.getMessage();
}
public ResponseResult(Integer code, T data) {
super();
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String message, T data) {
super();
this.code = code;
this.message = message;
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
result = prime * result + ((message == null) ? 0 : message.hashCode());
result = prime * result + ((code == null) ? 0 : code.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ResponseResult<?> other = (ResponseResult<?>) obj;
if (data == null) {
if (other.data != null) {
return false;
}
} else if (!data.equals(other.data)) {
return false;
}
if (message == null) {
if (other.message != null) {
return false;
}
} else if (!message.equals(other.message)) {
return false;
}
if (code == null) {
if (other.code != null) {
return false;
}
} else if (!code.equals(other.code)) {
return false;
}
return true;
}
/**
* 通用状态码
* <p>
* Description:
* </p>
*
* @author Lusifer
* @version v1.0.0
* @date 2019-07-30 05:02:49
* @see com.funtl.myshop.plus.commons.dto
*/
public class CodeStatus {
/**
* 请求成功
*/
public static final int OK = 20000;
/**
* 请求失败
*/
public static final int FAIL = 20002;
/**
* 熔断请求
*/
public static final int BREAKING = 20004;
/**
* 非法请求
*/
public static final int ILLEGAL_REQUEST = 50000;
/**
* 非法令牌
*/
public static final int ILLEGAL_TOKEN = 50008;
/**
* 其他客户登录
*/
public static final int OTHER_CLIENTS_LOGGED_IN = 50012;
/**
* 令牌已过期
*/
public static final int TOKEN_EXPIRED = 50014;
}
}
MapperUtils
package com.grq.commons.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Jackson 工具类
* <p>Title: MapperUtils</p>
* <p>Description: </p>
*
* @author Lusifer
* @version 1.0.0
* @date 2018/3/4 21:50
*/
public class MapperUtils {
private final static ObjectMapper objectMapper = new ObjectMapper();
public static ObjectMapper getInstance() {
return objectMapper;
}
/**
* 转换为 JSON 字符串
*
* @param obj
* @return
* @throws Exception
*/
public static String obj2json(Object obj) throws Exception {
return objectMapper.writeValueAsString(obj);
}
/**
* 转换为 JSON 字符串,忽略空值
*
* @param obj
* @return
* @throws Exception
*/
public static String obj2jsonIgnoreNull(Object obj) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.writeValueAsString(obj);
}
/**
* 转换为 JavaBean
*
* @param jsonString
* @param clazz
* @return
* @throws Exception
*/
public static <T> T json2pojo(String jsonString, Class<T> clazz) throws Exception {
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
return objectMapper.readValue(jsonString, clazz);
}
/**
* 字符串转换为 Map<String, Object>
*
* @param jsonString
* @return
* @throws Exception
*/
public static <T> Map<String, Object> json2map(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.readValue(jsonString, Map.class);
}
/**
* 字符串转换为 Map<String, T>
*/
public static <T> Map<String, T> json2map(String jsonString, Class<T> clazz) throws Exception {
Map<String, Map<String, Object>> map = objectMapper.readValue(jsonString, new TypeReference<Map<String, T>>() {
});
Map<String, T> result = new HashMap<String, T>();
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
result.put(entry.getKey(), map2pojo(entry.getValue(), clazz));
}
return result;
}
/**
* 深度转换 JSON 成 Map
*
* @param json
* @return
*/
public static Map<String, Object> json2mapDeeply(String json) throws Exception {
return json2MapRecursion(json, objectMapper);
}
/**
* 把 JSON 解析成 List,如果 List 内部的元素存在 jsonString,继续解析
*
* @param json
* @param mapper 解析工具
* @return
* @throws Exception
*/
private static List<Object> json2ListRecursion(String json, ObjectMapper mapper) throws Exception {
if (json == null) {
return null;
}
List<Object> list = mapper.readValue(json, List.class);
for (Object obj : list) {
if (obj != null && obj instanceof String) {
String str = (String) obj;
if (str.startsWith("[")) {
obj = json2ListRecursion(str, mapper);
} else if (obj.toString().startsWith("{")) {
obj = json2MapRecursion(str, mapper);
}
}
}
return list;
}
/**
* 把 JSON 解析成 Map,如果 Map 内部的 Value 存在 jsonString,继续解析
*
* @param json
* @param mapper
* @return
* @throws Exception
*/
private static Map<String, Object> json2MapRecursion(String json, ObjectMapper mapper) throws Exception {
if (json == null) {
return null;
}
Map<String, Object> map = mapper.readValue(json, Map.class);
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object obj = entry.getValue();
if (obj != null && obj instanceof String) {
String str = ((String) obj);
if (str.startsWith("[")) {
List<?> list = json2ListRecursion(str, mapper);
map.put(entry.getKey(), list);
} else if (str.startsWith("{")) {
Map<String, Object> mapRecursion = json2MapRecursion(str, mapper);
map.put(entry.getKey(), mapRecursion);
}
}
}
return map;
}
/**
* 将 JSON 数组转换为集合
*
* @param jsonArrayStr
* @param clazz
* @return
* @throws Exception
*/
public static <T> List<T> json2list(String jsonArrayStr, Class<T> clazz) throws Exception {
JavaType javaType = getCollectionType(ArrayList.class, clazz);
List<T> list = (List<T>) objectMapper.readValue(jsonArrayStr, javaType);
return list;
}
/**
* 获取泛型的 Collection Type
*
* @param collectionClass 泛型的Collection
* @param elementClasses 元素类
* @return JavaType Java类型
* @since 1.0
*/
public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
/**
* 将 Map 转换为 JavaBean
*
* @param map
* @param clazz
* @return
*/
public static <T> T map2pojo(Map map, Class<T> clazz) {
return objectMapper.convertValue(map, clazz);
}
/**
* 将 Map 转换为 JSON
*
* @param map
* @return
*/
public static String mapToJson(Map map) {
try {
return objectMapper.writeValueAsString(map);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 将 JSON 对象转换为 JavaBean
*
* @param obj
* @param clazz
* @return
*/
public static <T> T obj2pojo(Object obj, Class<T> clazz) {
return objectMapper.convertValue(obj, clazz);
}
/**
* 将指定节点的 JSON 数据转换为 JavaBean
*
* @param jsonString
* @param clazz
* @return
* @throws Exception
*/
public static <T> T json2pojoByTree(String jsonString, String treeNode, Class<T> clazz) throws Exception {
JsonNode jsonNode = objectMapper.readTree(jsonString);
JsonNode data = jsonNode.findPath(treeNode);
return json2pojo(data.toString(), clazz);
}
/**
* 将指定节点的 JSON 数组转换为集合
*
* @param jsonStr JSON 字符串
* @param treeNode 查找 JSON 中的节点
* @return
* @throws Exception
*/
public static <T> List<T> json2listByTree(String jsonStr, String treeNode, Class<T> clazz) throws Exception {
JsonNode jsonNode = objectMapper.readTree(jsonStr);
JsonNode data = jsonNode.findPath(treeNode);
return json2list(data.toString(), clazz);
}
}
OkHttpClientUtil
package com.funtl.myshop.plus.commons.utils;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* OKHttp3
* <p>
* Description:
* </p>
*
* @author Lusifer
* @version v1.0.0
* @date 2019-07-29 14:05:08
* @see com.funtl.myshop.plus.commons.utils
*/
public class OkHttpClientUtil {
private static final int READ_TIMEOUT = 100;
private static final int CONNECT_TIMEOUT = 60;
private static final int WRITE_TIMEOUT = 60;
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final byte[] LOCKER = new byte[0];
private static OkHttpClientUtil mInstance;
private OkHttpClient okHttpClient;
private OkHttpClientUtil() {
okhttp3.OkHttpClient.Builder clientBuilder = new okhttp3.OkHttpClient.Builder();
// 读取超时
clientBuilder.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS);
// 连接超时
clientBuilder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);
//写入超时
clientBuilder.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);
okHttpClient = clientBuilder.build();
}
/**
* 单例模式获取 NetUtils
*
* @return {@link OkHttpClientUtil}
*/
public static OkHttpClientUtil getInstance() {
if (mInstance == null) {
synchronized (LOCKER) {
if (mInstance == null) {
mInstance = new OkHttpClientUtil();
}
}
}
return mInstance;
}
/**
* GET,同步方式,获取网络数据
*
* @param url 请求地址
* @return {@link Response}
*/
public Response getData(String url) {
// 构造 Request
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(url).build();
// 将 Request 封装为 Call
Call call = okHttpClient.newCall(request);
// 执行 Call,得到 Response
Response response = null;
try {
response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
/**
* POST 请求,同步方式,提交数据
*
* @param url 请求地址
* @param bodyParams 请求参数
* @return {@link Response}
*/
public Response postData(String url, Map<String, String> bodyParams) {
// 构造 RequestBody
RequestBody body = setRequestBody(bodyParams);
// 构造 Request
Request.Builder requestBuilder = new Request.Builder();
Request request = requestBuilder.post(body).url(url).build();
// 将 Request 封装为 Call
Call call = okHttpClient.newCall(request);
// 执行 Call,得到 Response
Response response = null;
try {
response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
/**
* GET 请求,异步方式,获取网络数据
*
* @param url 请求地址
* @param myNetCall 回调函数
*/
public void getDataAsync(String url, final MyNetCall myNetCall) {
// 构造 Request
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(url).build();
// 将 Request 封装为 Call
Call call = okHttpClient.newCall(request);
// 执行 Call
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
myNetCall.failed(call, e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
myNetCall.success(call, response);
}
});
}
/**
* POST 请求,异步方式,提交数据
*
* @param url 请求地址
* @param bodyParams 请求参数
* @param myNetCall 回调函数
*/
public void postDataAsync(String url, Map<String, String> bodyParams, final MyNetCall myNetCall) {
// 构造 RequestBody
RequestBody body = setRequestBody(bodyParams);
// 构造 Request
buildRequest(url, myNetCall, body);
}
/**
* 同步 POST 请求,使用 JSON 格式作为参数
*
* @param url 请求地址
* @param json JSON 格式参数
* @return 响应结果
* @throws IOException 异常
*/
public String postJson(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
/**
* 异步 POST 请求,使用 JSON 格式作为参数
*
* @param url 请求地址
* @param json JSON 格式参数
* @param myNetCall 回调函数
* @throws IOException 异常
*/
public void postJsonAsync(String url, String json, final MyNetCall myNetCall) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
// 构造 Request
buildRequest(url, myNetCall, body);
}
/**
* 构造 POST 请求参数
*
* @param bodyParams 请求参数
* @return {@link RequestBody}
*/
private RequestBody setRequestBody(Map<String, String> bodyParams) {
RequestBody body = null;
okhttp3.FormBody.Builder formEncodingBuilder = new okhttp3.FormBody.Builder();
if (bodyParams != null) {
Iterator<String> iterator = bodyParams.keySet().iterator();
String key = "";
while (iterator.hasNext()) {
key = iterator.next().toString();
formEncodingBuilder.add(key, bodyParams.get(key));
}
}
body = formEncodingBuilder.build();
return body;
}
/**
* 构造 Request 发起异步请求
*
* @param url 请求地址
* @param myNetCall 回调函数
* @param body {@link RequestBody}
*/
private void buildRequest(String url, MyNetCall myNetCall, RequestBody body) {
Request.Builder requestBuilder = new Request.Builder();
Request request = requestBuilder.post(body).url(url).build();
// 将 Request 封装为 Call
Call call = okHttpClient.newCall(request);
// 执行 Call
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
myNetCall.failed(call, e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
myNetCall.success(call, response);
}
});
}
/**
* 自定义网络回调接口
*/
public interface MyNetCall {
/**
* 请求成功的回调处理
*
* @param call {@link Call}
* @param response {@link Response}
* @throws IOException 异常
*/
void success(Call call, Response response) throws IOException;
/**
* 请求失败的回调处理
*
* @param call {@link Call}
* @param e 异常
*/
void failed(Call call, IOException e);
}
}