小工具知识网

当前位置: 首页 > 行业知识 > spring boot框架(java)和 hyperf框架(php)多语言开发微服务,nacos作为服务发现,实现php服务与java服务跨服务调用,实现方案【feign和jsonRpc配合使用】

行业知识

spring boot框架(java)和 hyperf框架(php)多语言开发微服务,nacos作为服务发现,实现php服务与java服务跨服务调用,实现方案【feign和jsonRpc配合使用】

2026-10-25 21:49:17 来源:互联网转载

项目微服务架构,采用php和java 混合开发 避免不了 服务与服务调用,经过php和java的框架的研究,终于两边调用成功了

还是直接上代码 依赖 搭建哪些 都略过了

java 这边 spring cloud + spring boot +nacos + feign + jsonRpc

php 这边 hyperf + nacos + jsonRpc

没有用getway 用 nginx 进行服务切换

hyperf 官方文档地址

https://hyperf.wiki/3.1/#/zh-cn/json-rpc

服务与服务调用 要求是 必须用 jsonRpc 调用

对于jsonRpc的概念 也不多说了

先说一下我测试出来的兼容问题 :

php jsonRpc结构{"method":"findById","context":[],"id":"663b59258ee1b","jsonrpc":"2.0","params":{"id":"1"}}java jsonRpc结构{"id":"64803436","jsonrpc":"2.0","method":"aaa/findById","params":[{"id":"1"}]}

问题1

spring boot 默认起一个端口 可以jsonRpc和http用一个端口

但是jsonRpc一般是 内网服务之间调用 所以端口号不能暴露出去 所以要给jsonRpc单独起一个端口

php 是jsonRpc 一个端口 http 一个端口

问题2

php 在请求的时候 会拼接 服务名 “method”:“服务名前部分/findById” 他会根据nacos上的 Service 取前部分 好像去不掉

而且 “服务名前部分” 如果前半部分是两个以上单词组成 并且是小驼峰 比如 testRpc php就会把小驼峰 变成下划线 test_rpc

java这边也很难受

服务名 可以理解为springboot配置文件中 application.name=xxx 这个

问题3

php jsonRpc传输数据 php调用php 可以"params":{“id”:“1”} 这样

但是 如果php调用java 必须是数组 “params”:[{“id”:“1”}] 外层必须包一个 []

问题4 这个问题是最烦人

java 这边路径有个前缀路径 @JsonRpcService(“bookRpcService”) 而且这个参数是必填的

php这边没有地方定义

问题5 是问题4的解释

php 是 哪个类需要跨服务调用 就把哪个类 注册到 nacos里

java 是整个服务 注册到 nacos

所以 php 不要路径前缀 可以直接找到 调用类

而 java 是整个服务 没有前缀路径 就不知道 调用哪个类

实际把上面的5个问题解决了 ,他们相互调用就ok了

先说依赖

php

composer require hyperf/json-rpccomposer require hyperf/rpc-servercomposer require hyperf/rpc-client

java

                           org.springframework.cloud            spring-cloud-starter-openfeign                                           com.github.briandilley.jsonrpc4j            jsonrpc4j            1.5.3        

一 .java 调用 php

比较顺利 直接使用feign 即可 他默认支持 feign 的使用 就不说了 直接上代码

定义 跨服务接口

package com.xxx.init.feign;import com.xxx.init.out.R;import com.xxx.api.request.JsonRpcRequest;import com.xxx.api.request.feign.publics.AreaRequest;import com.xxx.api.response.JsonRpcResponse;import com.xxx.init.feign.fallback.publics.AreaFeignFallbackFactory;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;/** * User:Json * Date: 2024/3/26 **/@FeignClient(name = "AreaService",fallbackFactory = AreaFeignFallbackFactory.class)public interface AreaFeign {    //定义 php的这个 area/getArea 这个地址名  是服务名 AreaService 取前部分 area/php那边定义的方法名    // 如果前半部分 是两个以上单词组成 比如 testJsonService  取前部分  test_json/php那边定义的方法名    // AreaService 这个服务名命名规则 hyperf 文档里有 详细说明     String getAreaUrl="area/getArea";    //地区查找    @RequestMapping    public ResponseEntity getArea(@RequestBody JsonRpcRequest param);}

定义熔断器

package com.xxx.init.feign.fallback.publics;import com.xxx.init.out.R;import com.xxx.api.request.feign.publics.AreaRequest;import com.xxx.api.response.JsonRpcResponse;import com.xxx.init.feign.AreaFeign;import com.xxx.api.request.JsonRpcRequest;import feign.hystrix.FallbackFactory;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestBody;import java.util.HashMap;import java.util.Map;/** * User:Json * Date: 2024/3/26 **/@Slf4j@Componentpublic class AreaFeignFallbackFactory implements FallbackFactory {    @Override    public AreaFeign create(Throwable cause) {        return new AreaFeign() {            @Override            public ResponseEntity getArea(@RequestBody JsonRpcRequest param) {                // 如果远程请求异常  就会走这里                String msg = cause.getMessage();                if (StringUtils.isEmpty(msg)) {                    msg = "跨AreaService服务 【getArea】 无响应,没有返回值";                }                log.error("【跨AreaService服务调用异常】" + msg, cause);                HashMap errorMap = new HashMap();                errorMap.put("code", HttpStatus.GATEWAY_TIMEOUT.value());                errorMap.put("message", msg);                errorMap.put("data", null);                JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(new HashMap(), errorMap);                return new ResponseEntity(jsonRpcResponse, HttpStatus.GATEWAY_TIMEOUT);            }        };    }}

定义jsonRpc请求实体类

import lombok.Data;import lombok.NoArgsConstructor;import java.util.UUID;/** * User:Json * Date: 2024/3/27 **/@Data@NoArgsConstructorpublic class JsonRpcRequest {    private String jsonrpc="2.0";    private String id= UUID.randomUUID().toString();    private String method;    private T params;    public JsonRpcRequest(String method,T params){        this.method=method;        this.params=params;    }}

定义jsonRpc 相应实体类

import lombok.Data;import java.util.HashMap;/** * User:Json * Date: 2024/3/27 **/@Datapublic class JsonRpcResponse {    private String jsonrpc;    private String id;    private T result;    private HashMap error;    public  JsonRpcResponse(T result,HashMap error){        this.result=result;        this.error=error;    }}

R 的实体类 就不说了 就是返回前端的 数据结构 比如 状态码 data 提示语 那些

调用

   @Resource    private AreaFeign areaFeign;        public Map getArea(Long areaCode) {        JsonRpcRequest multiplier = new JsonRpcRequest(areaFeign.getAreaUrl, new AreaRequest(areaCode));        ResponseEntity hashMapResponseEntity= areaFeign.getArea(multiplier);        JsonRpcResponse body = hashMapResponseEntity.getBody();        if(ObjectUtils.isEmpty(body.getResult())){            log.error("AreaService中【getArea】接口:返回值:====>"+JSONObject.toJSONString(body));           throw new xxxRuntimeException(body.getError().get("message").toString());        }else{            return(Map) body.getResult().getData();        }    }

这样 java 调用php 就好了

我觉得实际还是http请求 只是把 请求体 和响应体 给变成 jsonRpc的数据格式 就行了

主要要注意

这个地址的定义 如果定义错了 老是 找不到方法 我纠结了好久 才发现问题

不清楚 去打印一下 php那边的 请求格式就能看出来

String getAreaUrl="area/getArea";

二 . php 调用 java

上面的5个问题 主要就是php调用java 出现的 这里我就不说 原理了

我是看了好久 hyperf框架底层调用 和 java这边 com.github.briandilley.jsonrpc4j 这个依赖 底层调用 发现的问题

我直接说解决方案

php那边需要采用 jsonRpc方式调用 所以 java这边要起一个jsonRpc的端口来共 php调用

我也想过直接调用控制器 应该也可以 但是 控制器走的 http 接收到的数据 还要自己 转换 很麻烦

com.github.briandilley.jsonrpc4j 这个依赖会帮我们直接转好 到方法里 直接取参数用即可

定义java这边 jsonRpc服务端接口

import com.googlecode.jsonrpc4j.JsonRpcMethod;import com.googlecode.jsonrpc4j.JsonRpcService;import java.util.Map;/** * User:Json * Date: 2024/5/7 **/ // 最烦的就是这个前缀 也是问题4 和 5  // php那边请求的时候不能拼这个前缀 // java这边这个前缀是必填 在网上的方案 改java这边源码 我尝试改过变动太大 不合适//  那这个问题咋解决 主要是   靠   @JsonRpcMethod注解 命名规则 加 拦截器 重定向解决的 //  这里就先说一下啊 @JsonRpcService("bookRpcService")   xxxRpcService  这样起名 拦截器里会用// 下面有使用方案  因为我拦截器里 截取字符串后 会多一个/ 所以这里 就多了一个 / 如果不想写 就在拦截器里 多截取一位 @JsonRpcService("/bookRpcService")public interface BookRpcService {     //这个方法名 也有规则    // 如果java和java 用这个依赖 jsonRpc调用 不需要使用这个注解 因为他会直接调用找到方法    // 但是php 调用java 就必须用 JsonRpcMethod 这个注解    // /iotjava/bookRpcServiceFindById  这个的写法 解释一下 开头必须要加 /  php那边会拼一个    //  1.java这边服务名 比如java 服务名是 iotjavaService 取 iotjava 这部分 因为php那边是这样的 上面定义feign 说过    //  2. @JsonRpcService("bookRpcService")  bookRpcService 取这个注解的全称 bookRpcService    //  3. 方法名 findById    // 所以这个/iotjava/bookRpcServiceFindById 路径 最终的解释就是    // /java这边服务名前部分/@JsonRpcService这个注解里的全程+方法名    // 这样起名 拦截器里会用        @JsonRpcMethod("/iotjava/bookRpcServiceFindById")    public Book findById(Map id);}

实现这个接口

import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImpl;import org.springframework.stereotype.Service;import java.util.Map;/** * User:Json * Date: 2024/5/7 **/@AutoJsonRpcServiceImpl@Servicepublic class BookRpcServiceImpl implements BookRpcService{    @Override    public Book findById(Map id){        Book book = new Book();        book.setId(id.get("id"));        book.setName("JSON-R1111PC");        book.setPrice(99.9);        return book;    }}

定义jsonRpc服务端配置文件

import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * User:Json * Date: 2024/5/7 **/@Configurationpublic class RpcConfiguration {    @Bean    public AutoJsonRpcServiceImplExporter rpcServiceImplExporter(){        return new AutoJsonRpcServiceImplExporter();    }}

再定义一个拦截器 这个拦截器就是为了解决 问题4 和 5

import com.alibaba.fastjson.JSONObject;import lombok.extern.slf4j.Slf4j;import org.springframework.util.StringUtils;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedReader;import java.io.IOException;/** * User:Json * Date: 2024/5/13 **/@Slf4jpublic class JsonRpcInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        // 获取请求路径        String requestURI = request.getRequestURI();        // 如果请求路径为 为 /  就检查是不是JsonRpc请求        if ("/".equals(requestURI)) {            JSONObject postJson = getPostJson(request);            String newURI = "";            if (postJson != null && postJson.containsKey("method")) {                String methodName = postJson.getString("method");                int secondIndex = methodName.indexOf("/", methodName.indexOf("/") + 1);                if (secondIndex >= 0 && methodName.contains("RpcService")) {                    int endIndex = methodName.indexOf("RpcService") + "RpcService".length();                    newURI = methodName.substring(secondIndex, endIndex);                }            }            if (StringUtils.isEmpty(newURI)) {                newURI = "/error";            }            log.info("jsonRpc请求地址为:"+newURI);            log.info("jsonRpc请求参数为:"+postJson);            // 重定向到新的路径            request.getRequestDispatcher(newURI).forward(request, response);            return false;        }        return true; // 其他情况继续执行请求    }    // 获取 POST 请求的 JSON 字符串参数    private JSONObject getPostJson(HttpServletRequest request) throws IOException {        StringBuilder sb = new StringBuilder();        BufferedReader reader = request.getReader();        try {            String line;            while ((line = reader.readLine()) != null) {                sb.append(line);            }        } finally {            if (reader != null) {                reader.close();            }        }        return JSONObject.parseObject(sb.toString());    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {    }}

这样 java这边的服务端就定义好了

如果项目里 还有别的拦截器 可以在别的拦截器里判断 如果是rpc 请求 就 返回true

比如你拦截器里 做了token的验证 但是 服务与服务之间调用 jsonRpc 没有定义token

就可以在别的拦截器里定义

if(handler instanceof JsonServiceExporter)  return true; 

下面再说一下上面的问题解决方案

1. 端口号问题

定义两个端口

server:  port: 10632 #内网端口号 jsonRpc  把springboot 带的端口配置作为 jsonRpc 端口 因为nacos默认取的这个端口external:  port: 10532  #外网端口号  把自定义的端口号 作为 http 端口

定义一个配置文件 spingboot 默认内置tomcat 如果使用的别的 web服务 配置文件改别的就行了

    @Value("${external.port}")    private Integer portInternal;    @Bean    public ServletWebServerFactory servletContainer() {        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();        tomcat.addAdditionalTomcatConnectors(createStandardConnector());        return tomcat;    }    //给 tomcat 再设置一个端口    private Connector createStandardConnector() {        Connector connector = new Connector(Http11NioProtocol.class.getName());        connector.setPort(portInternal);        return connector;    }

服务名的定义规则:

spring:  application:    #  包命名规范一般都小写  所以这里服务名 前半部分为小写    #  而且需要跟 php使用jsonRpc跨服务请求 php那边的服务命名规则 是 xxxService 这种 所以遵顼php那边服务命名规则    #  如果是两个以上的单词 也要 全部小写  不小写的话 php那边使用jsonRpc跨服务调用 method属性里的值  会把小驼峰转成下划线    #  php如果把小驼峰转成下划线  那java这边 在接收 也要转换小写    #  所以为了不转来转去  所以这里服务名 前半部分 全部小写 最省事    name: iotjavaService

2. 问题2

在定义上面的java jsonRpc服务端 已经解决 声明规则 在上面 已经说过

    @JsonRpcMethod("/iotjava/bookRpcServiceFindById")  

3. 问题3

php调用的时候 多个 [] 就行了

4. 问题4和 问题5 上面定义java jsonRpc服务端 已经解决

php这边调用

use Bailing\Helper\ApiHelper;use Hyperf\RpcClient\AbstractServiceClient;class SkeletonC extends AbstractServiceClient implements SkeletonIc{    /**     * 定义对应服务提供者的服务名称     */    protected string $serviceName = 'iotjavaService';    /**     * 定义对应服务提供者的服务协议     */    protected string $protocol = 'jsonrpc-http';        public function bookRpcServiceFindById(string $id): array    {        $arr[]=compact('id' );        return $this->__request(__FUNCTION__, $arr);    }}

php那边测试 打印 可以分析一下源码 有兴趣可以去试试

下面的截图 都是 这两个依赖里的 源码分析

hyperf/json-rpchyperf/rpc-client

文档推荐

打印位置1

打印位置2

打印位置3

java 依赖 源码地址

https://github.com/briandilley/jsonrpc4j

java源码这边核心是这个类 类里主要是收集注解 然后放到springboot里

这个类 是 请求服务端时候会触发 的类

上一篇:响应式网站建设全攻略,轻松实现自适应网站织梦!

下一篇:你在四冶总部还是哪个分公司?