基于feign原理封装一个http
1.业务原因:公司有4个系统,领导要求将这4个平台资源整合到一个系统上
1.核心模块定义接口
新建一个maven项目 (只写Controller和service就行了一些其他的dto和vo根据自己业务去写,因公司项目只能提供方法思路)
Controller
@Controller
@RequestMapping("/xxx-sale/api")
public class XXXXXXApiController { //自己改名
@Autowired
private ISubSystemApiService subSystemApiService;
private Logger logger = LoggerFactory.getLogger(SaleSubSystemApiController.class);
@GetMapping("/seller")
@ResponseBody
public BaseResultSubSystem<List<SyncSellerVO>> getSellerListByUpdateTime(@RequestParam(value = "start") String start, @RequestParam(value = "end") String end,
@RequestParam(value = "current") int current, @RequestParam(value = "size") int size) {
try {
BaseResultSubSystem result = subSystemApiService.getSellerListByUpdateTime(null, start, end, current, size);
return result;
} catch (Exception e) {
logger.error("获取用户信息异常", e);
return new ErrorResultSubSystem<>(500, "获取用户信息异常");
}
}
/**
* 校验账号密码是否匹配
*
* @return
*/
@PostMapping("/checkUserPassword")
@ResponseBody
public BaseResultSubSystem<Boolean> checkUserPassword(@RequestBody CheckUserPasswordDTO dto) {
try {
return subSystemApiService.checkUserPassword(dto);
} catch (Exception e) {
logger.error("校验账号密码是否匹配异常", e);
return new ErrorResultSubSystem<>(500, "校验账号密码是否匹配异常");
}
}
}
interface
public interface ISubSystemApiService {
BaseResultSubSystem getSellerListByUpdateTime(String platform ,String start, String end, int current, int size);
BaseResultSubSystem<Boolean> checkUserPassword(CheckUserPasswordDTO dto);
}
2.模块依赖
在这里将这个新建的maven模块install我成为核心模块,让其他主系统和子系统平台去依赖这个模块
1.子系统负责实现这个接口
2.主系统调用这个接口
3.标题写一个切面编程(主系统)
切面编程
@Component
@Aspect
@Slf4j
public class SubSystemApiAspect {
/**
* 定义切点 写自己的路径
*/
@Pointcut("execution(public * com.xxx.xxx.core.service.ISubSystemApiService.* (..)) ")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
try {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
//通过方法名获取 (path+请求方式)
String[] method = SubApiChooser.CHOOSE_MAP.get(name);
//获取平台 get请求的第一个参数 post请求的参数需继承基础类BaseApiDTO
//这里是根据自己的业务去实现的,我这边对接子平台
String platform = "get".equals(method[1])? (String) args[0]:((BaseApiDTO)args[0]).getPlatform();
//获取参数
String params = getParams(joinPoint, args, method[1]);
log.info("远程调用 {}子系统 接口{} 参数:{}",args[0],joinPoint.getSignature().getName(),params);
return SaleUtil.httpSubApi(PlatfromEnum.getByCode(platform), name, params);
}catch (Exception e){
e.printStackTrace();
return new ErrorResultSubSystem(500, "系统繁忙,请稍后再试");
}
}
private String getParams(ProceedingJoinPoint joinPoint, Object[] args, String httpMethod) {
//get请求 封装Map 参数名和参数
String params = null;
if ("get".equals(httpMethod)){
if (args.length>1){
Object[] newArgs = deleteFirst(args);
String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String,Object> getParams = new HashMap<>();
for (int i = 0; i < newArgs.length; i++) {
getParams.put(parameterNames[i+1],newArgs[i]);
}
params = JSONUtil.toJsonStr(getParams);
}
}else {
//Post请求 默认取第1个参数为Json对象
if (args.length>0){
params = JSONUtil.toJsonStr(args[0]);
}
}
return params;
}
static Object[] deleteFirst(Object[] arr) {
Object[] temp = new Object[arr.length - 1];
System.arraycopy(arr, 1, temp, 0, temp.length);
return temp;
}
}
http调用工具类
@Slf4j
@Component
public class HttpUtil {
//这里是我4个子平台系统的标识
private static String A_URL;
private static String B_URL;
private static String C_URL;
private static String D_URL;
public static String getBUrl() {
return B_URL;
}
@Value("${seller.api.url.b}")
public void setbUrl(String BUrl) {
B_URL= BUrl;
}
public static String getDUrl() {
return D_URL;
}
@Value("${seller.api.url.d}")
public void setDUrl(String DUrl) {
D_URL= DUrl;
}
public static String getAUrl() {
return A_URL;
}
@Value("${seller.api.url.a}")
public void setDyUrl(String AUrl) {
A_URL= AUrl;
}
public static String getCUrl() {
return C_URL ;
}
@Value("${seller.api.url.c}")
public void setKsUrl(String CUrl) {
C_URL = CUrl;
}
/**
* 请求子系统接口通用方法
* @param platfromEnum 平台枚举
* @param path 接口路径 如 "/aaa/ccc"
* @param httpMethod 接口类型 get post
* @param getParams get请求时请求参数
* @param postBody post请求时请求参数(json字符)
* @return
*/
public static BaseResultSubSystem httpSubApi(PlatfromEnum platfromEnum, String path, String httpMethod, Map<String, Object> getParams, String postBody) {
try {
String url = getSubApiUrl(path, platfromEnum);
String result;
if ("get".equals(httpMethod)) {
result = HttpUtil.get(url, getParams, 30000);
} else {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(30000);
RestTemplate restTemplate = new RestTemplate(factory);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(postBody, headers);
ResponseEntity<String> r = restTemplate.postForEntity(url, entity, String.class);
result = r.getBody();
}
JSONObject jsonObject = JSONUtil.parseObj(result);
if (jsonObject.getInt("code", 0) == 200) {
return JSONUtil.toBean(result, SuccessResultSubSystem.class);
} else {
return new ErrorResultSubSystem(500, jsonObject.getStr("message", "子系统接口调用异常"));
}
} catch (Exception e) {
log.error("子系统接口调用异常 信息:{}", e.getMessage());
return new ErrorResultSubSystem(500, "子系统接口调用异常");
}
}
/**
* 根据方法名 请求子系统接口通用方法 V2
* @param platfromEnum 平台枚举
* @param methodName 方法名称 如 "getUserRole"
* @param params 请求时请求参数 json
* @return
*/
public static BaseResultSubSystem httpSubApi(PlatfromEnum platfromEnum, String methodName, String params) {
//通过方法名获取 path+请求方式
String[] method = SubApiChooser.CHOOSE_MAP.get(methodName);
Map<String, Object> getParams = null;
if ("get".equals(method[1])){
getParams = JSONUtil.toBean(params,Map.class);
params=null;
}
return httpSubApi(platfromEnum,method[0],method[1],getParams,params);
}
/**
* 拼接接口地址
* @param path
* @param platfromEnum
* @return
*/
private static String getSubApiUrl(String path, PlatfromEnum platfromEnum){
String url = "";
String code = platfromEnum.getCode();
switch (code) {
case "B" :
url = B_URL + path;
break;
case "D" :
url = D_URL + path;
break;
case "A" :
url = A_URL + path;
break;
case "C" :
url = C_URL + path;
break;
default:
break;
}
return url;
}
}
/**
* @description 归属平台
*/
@AllArgsConstructor
@Getter
public enum PlatfromEnum {
D("D", "XXX","8021","dLoginName"),
A("A", "XXX","9021","aLoginName"),
C("C", "XXX","8021","cLoginName"),
B("B", "XXX","7021","bLoginName"),
;
private final String code;
private final String msg;
private final String post;
private final String filed;
public static PlatfromEnum getByCode(String code) {
if(StrUtil.isBlank(code)){
return null;
}
for (PlatfromEnum platformEnum : PlatfromEnum.values()) {
if (platformEnum.getCode().equals(code)) {
return platformEnum;
}
}
return null;
}
private static Map<String, PlatfromEnum> cache;
static {
cache = Arrays.stream(PlatfromEnum.values()).collect(Collectors.toMap(PlatfromEnum::getCode, Function.identity()));
}
public static PlatfromEnum of(String code) {
return cache.get(code);
}
}
4.核心的一步获取接口(主系统)
获取子系统平台的接口请求地址和参数名和方法名
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @Author Lgc
* @Date 2023/3/22 16:48
* @Description 子系统接口信息选择器
* @Version 1.0
**/
@Component
@Slf4j
public class SubApiChooser {
@Resource
private RequestMappingHandlerMapping handlerMapping;
/**
* 方法选择器
* 通过方法名获取 (path+请求方式)
* key:方法名 value: 请求方式 路径 例:getUserRole -> ["/yeion-xxx/api/user/role","get"]
*/
public static final Map<String, String[]> CHOOSE_MAP = new ConcurrentHashMap<>();
/**
* 注册 XXXXApiController控制器的API信息
*/
@PostConstruct
public void registerApiUrlPath() {
SaleSubSystemApiController bean = SpringUtils.getBean(SaleSubSystemApiController.class);
System.out.println(bean);
//获取XXXXXXController的所有方法
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods().entrySet().stream()
.filter(h-> SaleSubSystemApiController.class.equals(h.getValue().getBeanType())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
for (RequestMappingInfo info :handlerMethods.keySet()){
//获取每个方法的映射路径
Set<String> urlSet = info.getPatternsCondition().getPatterns();
//获取每个方法的请求方式 get post
Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
HandlerMethod handlerMethod = handlerMethods.get(info);
//获取每个方法名
String name = handlerMethod.getMethod().getName();
for (String path : urlSet) {
for (RequestMethod method : methods) {
CHOOSE_MAP.put(name, new String[]{path, method.name().toLowerCase()});
}
}
}
log.info("XXXXXXApiController的API信息注册,方法数量:{} | 注册数量:{}",handlerMethods.size(),CHOOSE_MAP.size());
}
}
5使用方法(主系统调用)
@SneakyThrows
@Override
public Boolean bindPlatform(List<PlatformReq> platformReqList) {
Assert.isTrue(platformReqList.size() > 0, "数据不存在");
for (PlatformReq platformReq : platformReqList) {
CheckUserPasswordDTO dto = new CheckUserPasswordDTO();
dto.setPlatform(platformReq.getSource());
dto.setLoginName(platformReq.getAccount());
dto.setPassword(platformReq.getPwd());
BaseResultSubSystem<Boolean> result = subSystemApiService.checkUserPassword(dto); //通过调用core模块的接口,自动走切面编程捕获
Assert.isTrue(result.getCode() == 200, "绑定失败,请检查["+platformReq.getSource()+"] "+result.getMessage());
Assert.isTrue(result.getData(), "绑定失败,请检查["+platformReq.getSource()+"]账号密码是否输入错误");
}
SysUser sysUser = new BaseController().getLoginUser().getUser();
CompletableFuture[] futures = new CompletableFuture[2];
futures[0] = CompletableFuture.runAsync(() -> updateCrmUserCustomer(platformReqList, sysUser));
futures[1] = CompletableFuture.runAsync(() -> updateSysUserById(platformReqList, sysUser));
CompletableFuture.allOf(futures).get();
return true;
}