文章目录
前言
提示:既然用到了微服务就会有服务间调用的问题产生,我总结了通过feign和hystrix进行调用的方式,希望对大家有所帮助。
提示:以下是本篇文章正文内容,下面案例可供参考
介绍
服务提供者是sms短信发送服务,服务消费者是系统用户模块;
1. 服务提供者
1.1 jar包引入
<dependencies>
<!-- SpringCloud Openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringCloud Ailibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--阿里大于-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
</dependency>
<!--阿里大于-->
</dependencies>
1.2 配置文件
server.port=9300
spring.application.name=sms
# 注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 允许熔断机制
feign.hystrix.enabled=true
#消息:
sms.accessKeyId=
sms.accessKeySecret=
1.3 controller
/**
* 短息服务
*
* @author lixy
*/
@RestController
@RequestMapping("/sms")
public class SmsController extends BaseController {
@Autowired
private SmsService smsService;
@PostMapping("/send")
public R<Map<String, String>> sendSms(@RequestParam("phoneNumbers") String phoneNumbers, @RequestParam("signName") String signName, @RequestParam("templateCode") String templateCode, @RequestParam("param") String param) {
Map<String, String> map = smsService.sendSms(phoneNumbers, signName, templateCode, param);
return R.ok(map);
}
}
/**
* 文件服务
* @author lixy
*/
@RestController
@RequestMapping("/files")
public class FileController extends BaseController {
@Autowired
private IFileService fileService;
/**
* produces = MediaType.MULTIPART_FORM_DATA_VALUE
* 加这个返回结果会报错: R<?>将...Content-type set null</>
* 单一文件上传接口,上传失败由统一异常处理返回
* @param file
* @return
*/
@PostMapping(value = "/uploadFile")
public R<?> fileUpload(HttpServletRequest request, @RequestPart("file") MultipartFile file){
if(file.isEmpty()){
return R.fail("请选择文件!");
}
SysFile uploadFile = fileService.uploadFile(file, request);
return R.ok(uploadFile, "文件上传成功!");
}
@PostMapping("/uploadFiles")
public R<?> filesUpload(HttpServletRequest request, @RequestPart("files")MultipartFile[] files){
if(files.length <=0 ){
return R.fail("请选择文件!");
}
List<SysFile> filesPath = fileService.uploadFiles(files, request);
return R.ok(filesPath, "文件上传成功!");
}
@GetMapping("/download")
public void fileDownload(HttpServletResponse response,
@RequestParam(value = "filePath", required = false) String filePath,
@RequestParam(value = "fileName", required = false) String fileName,
@RequestParam(value = "isOnline", required = false, defaultValue = "false") Boolean isOnline){
fileService.downloadFile(response, filePath, fileName , isOnline);
}
}
1.4 service
public interface SmsService {
Map<String,String> sendSms(String phoneNumbers, String signName, String templateCode, String param);
}
public interface IFileService {
SysFile uploadFile(MultipartFile file, HttpServletRequest request);
List<SysFile> uploadFiles(MultipartFile[] files, HttpServletRequest request);
void downloadFile(HttpServletResponse response, String filePath, String fileName, Boolean isOnline);
}
1.5 impl
/**
* @author lixy
*/
@Service
public class SmsServiceImpl implements SmsService {
private final Logger logger = LoggerFactory.getLogger(SmsServiceImpl.class);
@Autowired
private SmsUtil smsUtil;
public Map<String,String> sendSms(String phoneNumbers, String signName, String templateCode, String param){
//调用发送短息的方法 ;
try {
SendSmsResponse response = smsUtil.sendSms(phoneNumbers, signName, templateCode, param);
//封装返回值给map
Map<String ,String> resultMap = new HashMap<String ,String>();
resultMap.put("Code",response.getCode());
resultMap.put("Message",response.getMessage());
resultMap.put("RequestId",response.getRequestId());
resultMap.put("BizId",response.getBizId());
return resultMap;
} catch (ClientException e) {
logger.error("短信发送失败:{}" , e.getMessage());
throw new CustomException("短信发送失败:" + e.getErrMsg());
}
}
}
/**
* @author lixy
*/
@Service
@Primary
public class FastDFSFileServiceImpl implements IFileService {
@Autowired
private FastdfsClientService fastdfsClientService;
@Value("${fastdfs.access-path}")
private StringBuffer accessPath;
@Value("${weaponry.manual_path}")
private String manualPath;
@Override
public SysFile uploadFile(MultipartFile file, HttpServletRequest request) {
String filename = file.getOriginalFilename();
String extName = getFileExtName(filename);
String[] strings;
try {
long start = System.currentTimeMillis();
strings = fastdfsClientService.autoUpload(file.getBytes(), extName);
long end = System.currentTimeMillis();
} catch (Exception e) {
throw new CustomException("文件上传失败!", e);
}
// accessPath + strings[0] + "/" + strings[1] + "==" + file.getSize() + "==上传耗时:"
return new SysFile(filename, accessPath + strings[0] + "/" + strings[1]);
}
private String getFileExtName(String filename) {
return filename.substring(filename.lastIndexOf(".") + 1);
}
@Override
public List<SysFile> uploadFiles(MultipartFile[] files, HttpServletRequest request) {
return null;
}
@Override
public void downloadFile(HttpServletResponse response, String filePath, String fileName, Boolean isOnline) {
try {
if (StringUtils.isEmpty(filePath)) {
// 默认下载使用手册 "典型目标知识图谱服务系统使用手册.pdf"
filePath = manualPath;
fileName = new String("典型目标知识图谱服务系统使用手册.pdf".getBytes(StandardCharsets.UTF_8), "ISO8859-1");
} else {
if(StringUtils.isEmpty(fileName)){
fileName = new String(filePath.substring(filePath.lastIndexOf("/") + 1).getBytes(StandardCharsets.UTF_8), "ISO8859-1");
}else {
fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), "ISO8859-1");
}
}
// http://192.168.1.138/group1/M00/00/00/wKgBvWBQY_OAaRBKAAIIxvji4o8112.pdf
String[] strings = filePath.substring(accessPath.length()).split("/", 2);
response.reset(); // 非常重要
response.setContentType("application/force-download");// 设置强制下载不打开
response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);// 设置文件名
byte[] bytes = fastdfsClientService.download(strings[0], strings[1]);
// 写出到指定文件
// File file = new File("d:/测试.pdf");
// FileOutputStream fileOutputStream = new FileOutputStream(file);
// fileOutputStream.write(bytes);
// fileOutputStream.close();
// TODO 测试几个G的图片会不会溢出/报错 会
// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytes.length);
OutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.close();
} catch (Exception e) {
throw new CustomException("文件下载失败!", e);
}
}
}
1.5 SmsUtil
@Component
public class SmsUtil {
//产品名称:云通信短信API产品,开发者无需替换
static final String product = "Dysmsapi";
//产品域名,开发者无需替换
static final String domain = "dysmsapi.aliyuncs.com";
//加载properties中的key
@Value("${sms.accessKeyId}")
private String accessKeyId ;
@Value("${sms.accessKeySecret}")
private String accessKeySecret;
public SendSmsResponse sendSms(String phoneNumbers,String signName,String templateCode,String param) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(phoneNumbers);
//必填:短信签名-可在短信控制台中找到
request.setSignName(signName);
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(templateCode);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam(param);
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
public QuerySendDetailsResponse querySendDetails(String bizId,String phoneNumber) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
QuerySendDetailsRequest request = new QuerySendDetailsRequest();
//必填-号码
request.setPhoneNumber(phoneNumber);
//可选-流水号
request.setBizId(bizId);
//必填-发送日期 支持30天内记录查询,格式yyyyMMdd
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
request.setSendDate(ft.format(new Date()));
//必填-页大小
request.setPageSize(10L);
//必填-当前页码从1开始计数
request.setCurrentPage(1L);
//hint 此处可能会抛出异常,注意catch
QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
return querySendDetailsResponse;
}
}
1.6 启动类
/**
* @author lixy
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class SmsApplication {
public static void main(String[] args) {
SpringApplication.run(SmsApplication.class);
}
}
2. api(Feign+Hystrix熔断降级接口)
2.1 service接口
/**
* Feign调用远程
* ServiceNameConstants.SMS_SERVICE = "sms",短信服务名
*/
@FeignClient(contextId = "remoteSmsService", value = ServiceNameConstants.SMS_SERVICE, fallbackFactory = RemoteSmsServiceFactory.class)
public interface RemoteSmsService {
// 一定要加@RequestParam 否则调用时候会报错: 参数太长
@PostMapping("/sms/send")
public R<Map<String, String>> sendSms(@RequestParam("phoneNumbers") String phoneNumbers, @RequestParam("signName") String signName, @RequestParam("templateCode") String templateCode, @RequestParam("param") String param);
}
/**
* 文件服务
*
* @author guanwei
*/
@FeignClient(contextId = "remoteFileService", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class)
@RequestMapping("/files")
public interface RemoteFileService {
/**
* 单一文件上传接口,上传失败由统一异常处理返回
* @param file
* @return
*/
@PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<SysFile> fileUpload( HttpServletRequest request, @RequestPart("file") MultipartFile file);
/**
* 批量上传
* @param request
* @param files
* @return
*/
@PostMapping("/uploadFiles", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<List<SysFile>> filesUpload( HttpServletRequest request, @RequestPart("files")MultipartFile[] files);
/**
* 下载及预览
* @param response
* @param filePath
* @param fileName
* @param isOnline
*/
@GetMapping("/download")
public void fileDownload( HttpServletResponse response,
@RequestParam(value = "filePath", required = false) String filePath,
@RequestParam(value = "fileName", required = false) String fileName,
@RequestParam(value = "isOnline", required = false, defaultValue = "false") Boolean isOnline);
}
注: api中的上传文件接口需要加 consumes = MediaType.MULTIPART_FORM_DATA_VALUE
2.2 服务熔断降级
/**
* 降级处理
* @author lixy
*/
@Component
public class RemoteSmsServiceFactory implements FallbackFactory<RemoteSmsService> {
private static final Logger log = LoggerFactory.getLogger(RemoteSmsServiceFactory.class);
@Override
public RemoteSmsService create(Throwable cause) {
log.error("短信服务调用失败:{}", cause.getMessage());
return new RemoteSmsService() {
@Override
public R<Map<String, String>> sendSms(String phoneNumbers, String signName, String templateCode, String param) {
return R.fail("短信发送失败:" + cause.getMessage());
}
};
}
}
/**
* @author lixy
*/
@Component
public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileService> {
private static final Logger log = LoggerFactory.getLogger(RemoteFileFallbackFactory.class);
@Override
public RemoteFileService create(Throwable cause) {
return new RemoteFileService() {
@Override
public R<SysFile> fileUpload(HttpServletRequest request, MultipartFile file) {
log.error("服务调用失败:{}", cause.getMessage());
return R.fail("上传文件失败:" + cause.getMessage());
}
@Override
public R<List<SysFile>> filesUpload(HttpServletRequest request, MultipartFile[] files) {
log.error("服务调用失败:{}", cause.getMessage());
return R.fail("批量上传文件失败:" + cause.getMessage());
}
@Override
public void fileDownload(HttpServletResponse response, String filePath, String fileName, Boolean isOnline) {
log.error("服务调用失败:{}", cause.getMessage());
}
};
}
}
2.3 将降级处理加载到spring容器
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yan.sms.api.factory.RemoteSmsServiceFactory
2.4 domain
/**
* @author lixy
*/
public class SysFile {
/**
* 文件名称
*/
private String name;
/**
* 文件地址
*/
private String url;
public SysFile(){
}
public SysFile(String name, String url) {
super();
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("name", getName())
.append("url", getUrl())
.toString();
}
}
3.服务消费者
3.1 jar包引入
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringCloud Openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringCloud Ailibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--atlas-sms-api-->
<dependency>
<groupId>com.yan</groupId>
<artifactId>atlas-sms-api</artifactId>
</dependency>
3.2 配置文件
# 注册地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 开启熔断
feign.hystrix.enabled=true
3.3 服务启动类
/**
* 系统应用启动项
* author: lixy
*/
@SpringBootApplication
@MapperScan("com.guanwei.atlas.system.mapper")
@EnableFeignClients(basePackages = {"com.yan.sms.api"}) //解决消费者找不到feign接口
@EnableDiscoveryClient // 注册到nacos,使其他服务发现
@EnableCircuitBreaker // 熔断
public class AtlasSystemApplicaiton {
public static void main(String[] args) {
SpringApplication.run(AtlasSystemApplicaiton.class);
}
}
@EnableDiscoveryClient // 注册到nacos,使其他服务发现
@EnableCircuitBreaker // 熔断
@SpringBootApplication
这三个可以用
@SpringCloudApplication代替
3.4 SysUserController
/**
* 发送短信
* @param phone
* @return
*/
@RequestMapping("/sendSmsCode")
public R<?> sendSmsCode(String phone){
sysUserService.sendSmsCode(phone);
return R.ok(true,"短信发送成功");
}
3.5 SysUserServiceImpl
/**
* 用户信息Service业务层处理
*
* @author lixy
* @date 2021-01-06
*/
@Service
public class SysUserServiceImpl implements ISysUserService {
@Autowired
private RemoteSmsService smsService;
@Value("岩")
private String signName;
@Value("SMS_16*******")
private String templateCode;
@Autowired
private RedisTemplate redisTemplate;
//发送短信的方法:需要httpClient发送url,就是调用阿里云发送短信;
public void sendSmsCode(String phone) {
//1.生成动态的6位验证码:
int num = (int) (Math.random() + 1);
String smsCode = num + RandomStringUtils.randomNumeric(5);//因为当第一个数字是0的时候阿里云默认去掉0;
//2.将生成的验证码保存到redis中15分钟,
redisTemplate.boundValueOps(phone).set(smsCode, 15L, TimeUnit.MINUTES);
String param = "{\"code\":" + smsCode + "}";
R<Map<String, String>> result = smsService.sendSms(phone, signName, templateCode, param);
if(result.getCode() == 200){
Map<String, String> response = result.getData();
if (!response.get("Code").equals("200")) {
throw new CustomException(result.getMsg());
}
}else{
throw new CustomException(result.getMsg());
}
}
}
如果有其他消费者想调用sms,直接引入api的jar就好啦!!!
补充其他问题:
- 新入职某公司,然后接口返回值用的R这种返回类。发现某保存功能报错。
根据debug发现,人员保存的时候会通过feign调用的人员服务。
那么为什么会走服务降级呢?本身接口没有问题呀!这种情况可能有以下几个原因:- 在feign调用接口的参数无@RequestParam注解、
- 返回值类型和接收的类型不一致。(这个问题就出现在这里)
- 没有包扫描。
返回值类型是R,但是返回的时候调用了R.fail(“用户已存在!”)这个方法,这个方法的返回值类型不对。
package org.core.tool.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import org.core.tool.constant.LaConstant;
import org.core.tool.utils.ObjectUtil;
import org.springframework.lang.Nullable;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Optional;
/**
* 统一API响应结果封装,这个封装的fail用到feign中会出现问题。导致只走服务降级。
*
*/
@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, LaConstant.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 ? LaConstant.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(LaConstant.DEFAULT_SUCCESS_MESSAGE) : fail(LaConstant.DEFAULT_FAILURE_MESSAGE);
}
}
新入职一家公司,然后本地启动system服务总是报错,No fallback instance of type class org.file.feign.IFileClientFallback found for org.file.feign client la-file。找了半天问题,前任大哥在IFileFeignClientFallback中的实现@Overrid 同时又加了@get @post,然后在接口那里也有。注释掉就好了。可能不是这个问题?
希望对大家有所帮助~~