文档章节

基于事件领域模型框架kaka和servlet构建一个简单的自定义json协议接口服务,微服务架构基础

kakai
 kakai
发布于 2018/12/17 00:23
字数 2672
阅读 1.3K
收藏 3

事件领域模型框架地址:https://gitee.com/zkpursuit/kaka-notice-lib

所依赖的其它第三方库:

<dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.5</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

一、定义一个json数据写入器,用于暂存发送到客户端的json数据。

package com.http.core;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.util.JsonUtils;

/**
 * 待发送的JSON对象
 *
 * @author zhoukai
 */
public class HttpJsonRespWriter {

    private ArrayNode array; //待发送的json数据
    private StringBuilder strBuilder; //待发送的字符串数据,与array中的数据互斥,array优先级最高
    private boolean forceArray = false; //是否强制以json数组形式发送数据到客户端

    public HttpJsonRespWriter() {

    }

    public void setForceArray(boolean bool) {
        this.forceArray = bool;
    }

    public boolean getForceArray() {
        return forceArray;
    }

    /**
     * 追加JSON对象
     *
     * @param json 待追加的对象
     */
    private void addJsonObject(JsonNode json) {
        if (array == null) {
            array = JsonUtils.createJsonArray();
        }
        if (json instanceof ArrayNode) {
            ArrayNode arr = (ArrayNode) json;
            array.addAll(arr);
        } else {
            array.add(json);
        }
    }

    /**
     * 追加
     *
     * @param obj 追加的对象实例,支持Json对象、HttpResponseWriter对象、字符串
     *            <p>其它未支持的对象全部以字符串表示</p>
     * @return 本实例
     */
    HttpJsonRespWriter writeObject(Object obj) {
        if (obj instanceof JsonNode) {
            addJsonObject((JsonNode) obj);
        } else if(obj instanceof HttpJsonRespWriter) {
            HttpJsonRespWriter writer = (HttpJsonRespWriter) obj;
            ArrayNode arr = writer.array;
            boolean flag = true;
            if(arr != null) {
                int size = arr.size();
                if(size > 0) {
                    for(int i = 0; i < size; i++) {
                        JsonNode node = arr.get(i);
                        writeObject(node);
                    }
                    flag = false;
                }
            }
            if(flag && strBuilder != null && strBuilder.length() > 0) {
                writeObject(strBuilder.toString());
            }
        } else {
            if (strBuilder == null) {
                strBuilder = new StringBuilder();
            }
            strBuilder.append(String.valueOf(obj));
        }
        return this;
    }

    /**
     * 写入待发送的数据
     *
     * @param cmd 协议号
     * @param sendData 等待发送的数据
     */
    public void write(String cmd, Object sendData) {
        ObjectNode sendDataJson = JsonUtils.createJsonObject();
        if(sendData != null) {
            if(sendData instanceof ObjectNode) {
                ObjectNode jsonObj = (ObjectNode) sendData;
                if(jsonObj.has("error") || jsonObj.has("info")) {
                    sendDataJson.setAll(jsonObj);
                } else {
                    if(!jsonObj.has("data")) {
                        sendDataJson.set("data", jsonObj);
                    } else {
                        sendDataJson.setAll(jsonObj);
                    }
                }
            } else if(sendData instanceof ArrayNode) {
                ArrayNode jsonArr = (ArrayNode) sendData;
                sendDataJson.set("data", jsonArr);
            } else {
                sendDataJson.put("data", sendData.toString());
            }
        } else {
            sendDataJson.put("data", "null");
        }
        String cmdStr = "cmd";
        String systime = "systime";
        if (!sendDataJson.has(cmdStr)) {
            sendDataJson.put(cmdStr, cmd);
        }
        if (!sendDataJson.has(systime)) {
            sendDataJson.put(systime, System.currentTimeMillis());
        }
        writeObject(sendDataJson);
    }

    /**
     * 清除
     */
    public void clear() {
        if (array != null) {
            array.removeAll();
        }
        if (strBuilder != null && strBuilder.length() > 0) {
            strBuilder.delete(0, strBuilder.length());
        }
    }

    /**
     * 判断此对象的字符串表示是否为json格式
     *
     * @return true是json格式
     */
    public boolean isJsonString() {
        if (array != null) {
            return true;
        }
        return JsonUtils.isValidJson(strBuilder.toString());
    }

    /**
     * 是否为空
     *
     * @return true为空
     */
    public boolean isEmpty() {
        if (array != null && array.size() > 0) {
            return false;
        }
        return !(strBuilder != null && strBuilder.length() > 0);
    }

    /**
     * 将本对象转换为字符串
     *
     * @return 本对象的字符串表示
     */
    @Override
    public String toString() {
        if (array != null) {
            if (forceArray) {
                return JsonUtils.toJsonString(array);
            }
            if (array.size() == 1) {
                Object obj = array.get(0);
                if (obj instanceof JsonNode) {
                    return JsonUtils.toJsonString(obj);
                }
                return obj.toString();
            }
            return JsonUtils.toJsonString(array);
        }
        if (strBuilder != null) {
            return strBuilder.toString();
        }
        return super.toString();
    }

}

二、定义一个基于json的事件消息类,具体父类继承于事件领域模型框架中的com.kaka.notice.Message类

package com.http.core;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kaka.notice.Message;

/**
 *
 * @author zhoukai
 */
public class JsonMessage extends Message {

    public final HttpJsonRespWriter out;

    public JsonMessage(Object what, ObjectNode data, HttpJsonRespWriter out) {
        super(what, data);
        this.out = out;
    }

}

 

三、定义json数据处理器(具体父类继承于事件领域模型框架中的com.kaka.notice.Command类),处理自定义数据协议并将业务结果返回给json数据写入器 HttpJsonRespWriter

 

package com.http.core;

import ch.qos.logback.classic.Logger;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kaka.notice.Command;
import com.kaka.notice.Message;
import org.slf4j.LoggerFactory;

/**
 * json数据处理器
 *
 * @author zhoukai
 */
abstract public class JsonDataHandler extends Command {

    private Logger logger;
    protected final String opcode; //一定要在构造方法里赋值,不然反射赋值不会成功

    public JsonDataHandler() {
        this.opcode = "";
    }

    @Override
    public void execute(Message msg) {
        com.kaka.util.ReflectUtils.setFieldValue(this, "opcode", this.cmd());
        ObjectNode netData = (ObjectNode) msg.getBody();
        HttpJsonRespWriter out = null;
        if (msg instanceof JsonMessage) {
            JsonMessage nm = (JsonMessage) msg;
            out = nm.out;
        }
        try {
            execute(netData, out);
        } catch (Exception ex) {
            getLogger().error(ex.getLocalizedMessage(), ex);
        }
    }

    /**
     * 数据包具体解析执行,业务逻辑处理
     *
     * @param requestJson 客户端发送的json协议请求
     * @param out 返回给客户端的json数据写入器
     */
    abstract public void execute(ObjectNode requestJson, HttpJsonRespWriter out);


    protected Logger getLogger() {
        if (logger == null) {
            logger = (Logger) LoggerFactory.getLogger(this.getClass());
        }
        return logger;
    }

}

 

四、重点是定义一个接口服务servlet,将整合kaka事件框架及自定义的json事件消息和json数据处理器

package com.http.core;

import ch.qos.logback.classic.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.util.JsonUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.nio.charset.Charset;

import static com.kaka.notice.Facade.facade;

/**
 *
 * @author zhoukai
 */
public class JsonApiServlet extends HttpServlet {

    private static final Logger logger = (Logger) LoggerFactory.getLogger(JsonApiServlet.class);

    /**
     * 处理请求中的数据通信协议,json格式
     *
     * @param request       http请求
     * @param requestString 请求json格式字符串,可为单个JsonObject或者包含多个JsonObject的JsonArray
     *                      <p>也可能为经过DES加密再经过Hax编码后的字符串,但解码后仍是json格式</p>
     * @param response      http响应
     */
    private void processJson(String requestString, HttpServletRequest request, HttpServletResponse response) {
        String requestJsonString = null;
        if (JsonUtils.isValidJson(requestString)) {
            requestJsonString = requestString;
        }
        logger.info(requestJsonString);
        HttpJsonRespWriter writer = new HttpJsonRespWriter();
        JsonNode jn = JsonUtils.toJson(requestJsonString);
        if (jn != null) {
            if (jn instanceof ObjectNode) {
                ObjectNode jsonObj = (ObjectNode) jn;
                processJson(jsonObj, writer);
            } else if (jn instanceof ArrayNode) {
                ArrayNode jsonArr = (ArrayNode) jn;
                int size = jsonArr.size();
                for (int i = 0; i < size; i++) {
                    ObjectNode jsonObj = (ObjectNode) jsonArr.get(i);
                    processJson(jsonObj, writer);
                }
            }
            if (writer.isEmpty()) {
                ObjectNode jo = JsonUtils.createJsonObject();
                jo.put("info", "module_not_open");
                writer.writeObject(jo);
            }
        } else {
            ObjectNode jo = JsonUtils.createJsonObject();
            jo.put("error", "请求数据非标准json格式");
            writer.writeObject(jo);
        }
        this.send(response, writer.toString());
    }

    /**
     * 处理单个JsonObject数据协议</br>
     * 单个处理当中发生异常不会影响其它处理结果</br>
     *
     * @param jsonObj json协议数据
     * @param out     整合处理结果
     */
    private void processJson(ObjectNode jsonObj, HttpJsonRespWriter out) {
        String cmd = jsonObj.get("cmd").asText();
        HttpJsonRespWriter writer = new HttpJsonRespWriter();
        try {
            facade.sendMessage(new JsonMessage(cmd, jsonObj, writer));
        } catch (Exception ex) {
            ObjectNode jo = JsonUtils.createJsonObject();
            jo.put("info", "error");
            writer.write(cmd, jo);
            logger.error(JsonUtils.toJsonString(jsonObj));
            logger.error("数据处理错误!", ex);
        }
        if (writer.isEmpty()) {
            ObjectNode jo = JsonUtils.createJsonObject();
            jo.put("info", "module_not_open");
            writer.write(cmd, jo);
        }
        out.writeObject(writer);
    }

    /**
     * 发送响应数据到客户端
     *
     * @param resp
     * @param content
     */
    private void send(HttpServletResponse resp, String content) {
        try (PrintWriter pw = resp.getWriter()) {
            pw.write(content);
            pw.flush();
        } catch (IOException e) {
            logger.error(e.getLocalizedMessage(), e);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        String param = req.getQueryString();
        param = URLDecoder.decode(param, "UTF-8");
        processJson(param, req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        ServletInputStream is = req.getInputStream();
        byte[] bytes = IOUtils.toByteArray(is);
        String param = new String(bytes, Charset.forName("UTF-8"));
        processJson(param, req, resp);
    }
}


将此servlet配置到web.xml中,如果基于servlet3.0,直接用注解也行。
<servlet>
    <servlet-name>JsonApiServlet</servlet-name>
    <servlet-class>com.http.core.JsonApiServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>JsonApiServlet</servlet-name>
    <url-pattern>/api</url-pattern>
</servlet-mapping>

 

五、又是个重点了,上面写了一大堆代码,我们得具体实现一个业务了,怎么做呢,当然得继承上面自定义的那个JsonDataHandler了,客官请看:

package com.http.business;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.http.core.HttpJsonRespWriter;
import com.http.core.JsonDataHandler;
import com.kaka.notice.annotation.Handler;
import com.util.JsonUtils;

/**
 * 一个简单的求和
 *
 * @author zhoukai
 */
@Handler(cmd = "my_first_cmd", type = String.class) //cmd一般定义一个静态类,里面集中写好协议号,在此随便定义一个字符串了
public class MyFirstJsonHandler extends JsonDataHandler {

    /**
     *
     * @param requestJson 客户端发送的json协议请求
     * @param out 返回给客户端的json数据写入器
     */
    @Override
    public void execute(ObjectNode requestJson, HttpJsonRespWriter out) {
        int min = requestJson.get("min").asInt();
        int max = requestJson.get("max").asInt();
        int _min = min;
        min = Math.min(_min, max);
        max = Math.max(max, _min);

        ObjectNode json = JsonUtils.createJsonObject();
        int sum = 0;
        for(int i = min; i <= max; i++) {
            sum += i;
        }
        json.put("min", min);
        json.put("max", max);
        json.put("sum", sum);
        out.write(this.opcode, json);
    }
}

 

好像看着一切就绪了,运行看看,尝试在浏览器地址栏中输入http://localhost:8080/项目名/api?{%22cmd%22:%22my_first_cmd%22,%20%22min%22:1,%20%22max%22:100}后回车,怎么回事?输出的结果是模块未开启?

抱歉!抱歉!重中之中当然得调用到com.kaka.Startup实例的scan方法扫描包了,接着编写一个http监听器

package com.http.core;

import com.kaka.Startup;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class InitListener extends Startup implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        this.scan("com.http.business");
    }

}

然后将其注册到web.xml文件中。
<listener>
        <listener-class>com.http.core.InitListener</listener-class>
    </listener>

 

初心大意,忘记发json工具类了,海涵!

package com.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.util.Collection;

/**
 * {@link com.fasterxml.jackson.annotation.JsonProperty}
 * <p>为属性取别名的注解</p>
 * {@link com.fasterxml.jackson.annotation.JsonIgnore}
 * <p>忽略方法</p>
 * {@link com.fasterxml.jackson.annotation.JsonAutoDetect}
 * <p>类名注解,配合JsonProperty注解使用忽略方法</p>
 */
public class JsonUtils {

    /**
     * 线程安全,可以全局使用
     */
    private static final ObjectMapper mapper = new ObjectMapper();
//    static {
//        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//        //将属性字段全部转为小写
//        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
//    }

    /**
     * 将java对象转换为json对象
     * @param javaObject java对象
     * @param <T> json对象的限定类型
     * @return json对象
     */
    public final static <T extends JsonNode> T toJsonObject(Object javaObject) {
        if(javaObject instanceof JsonNode) {
            return (T) javaObject;
        }
        //return mapper.convertValue(javaBean, JsonNode.class);
        JsonNode jsonObject = mapper.valueToTree(javaObject);
        return (T) jsonObject;
    }

    /**
     * 判断字符串是否为一个有效的json格式
     * @param jsonInString
     * @return
     */
    public final static boolean isValidJson(String jsonInString ) {
        try {
            mapper.readTree(jsonInString);
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * 创建一个空的JsonObject对象
     * @return
     */
    public final static ObjectNode createJsonObject() {
        return mapper.createObjectNode();
    }

    /**
     * 创建一个空的JsonArray对象
     * @return
     */
    public final static ArrayNode createJsonArray() {
        return mapper.createArrayNode();
    }

    /**
     * 将java对象转换为json字符串
     * @param value
     * @return
     */
    public final static String toJsonString(Object value) {
        try {
            return mapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            return null;
        }
    }

    /**
     * 将json字符串转换为json对象
     * @param json
     * @param <T>
     * @return
     */
    public final static <T extends JsonNode> T toJson(String json) {
        if(json == null || "".equals(json)) return null;
        try {
            return (T) mapper.readTree(json);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 将json字符串转换为java对象
     * @param json
     * @param type
     * @param <T>
     * @return
     */
    public final static <T>T toObject(String json, Class<T> type) {
        try {
            return mapper.readValue(json, type);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 获取集合类型描述
     * @param collectionClass 集合类型
     * @param elementClasses 集合的元素类型
     * @return 类型描述
     */
    public final static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
        return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
    }

    /**
     * 将json字符串转换为java集合
     * @param json json字符串
     * @param collectionClass 集合类型
     * @param elementClasses 集合的元素类型
     * @param <T> 转换后的集合限定类型
     * @return 集合对象
     */
    public final static <T> Collection<T> toCollection(String json, Class<?> collectionClass, Class<T> elementClasses) {
        JavaType javaType = getCollectionType(collectionClass, elementClasses);
        try {
            return (Collection<T>) mapper.readValue(json, javaType);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 将json字符串转换为java对象
     * @param json json字符串
     * @param type 目标对象类型
     * @param elementClasses 如果目标对象类型为集合,则此参数表示集合的元素类型
     * @return 对象
     */
    public final static Object toObject(String json, Class<?> type, Class<?>... elementClasses) {
        try {
            if(elementClasses.length > 0) {
                JavaType javaType = getCollectionType(type, elementClasses);
                return mapper.readValue(json, javaType);
            }
            return mapper.readValue(json, type);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}

 

再次运行,在浏览器地址栏中输入http://localhost:8080/项目名/api?{"cmd":"my_first_cmd","min":1,"max":100}后回车

哈哈!结果出来了,一切显得那么完美,不是吗?

什么?想现在看到结果,行,具体用应答模式呈现:

浏览器请求:{"cmd":"my_first_cmd","min":1,"max":100}

服务器响应:{"data":{"min":1,"max":100,"sum":5050},"cmd":"my_first_cmd","systime":1544977209943}

 

如想直接体验基于事件领域模型的快感,请打开浏览器直接输入http://47.101.144.30:8080/

试着用浏览器开发者模式看看请求的数据和响应的数据,是不是很方便呢?http://47.101.144.30:8080/此地址使用了guava中的令牌桶限流,请勿恶意访问,谢谢支持!

基于 https://gitee.com/zkpursuit/kaka-notice-lib
构建的一个斗地主游戏  http://47.101.144.30:8080/game.html  (需在浏览器中开三个标签页体验),自定义二进制协议通信,有什么关于本事件领域模型的疑问或者简单的斗地主算法问题可以一起探讨。

申明:博文中提到的斗地主游戏(html5)完全属于一种框架使用范例,不涉及任何广告和其它经济利益,也不是一个完整健全的游戏项目,在此仅做技术探讨使用!

 

 

© 著作权归作者所有

kakai

kakai

粉丝 97
博文 13
码字总数 12842
作品 1
长沙
后端工程师
私信 提问
加载中

评论(0)

Java开发你一定要懂Spring,推荐一份书单送你

对于Java程序员来说,这是一个很好的时代。 在Java近20年的历史中,它经历过很好的时代,也经历过饱受诟病的时代。尽管有很多粗糙的地方,如applet、企业级JavaBean(Enterprise JavaBean,E...

人邮异步社区
03/31
0
0
Java EE 即将死去,毫无疑问!

在Java问世之初,包括IBM、BEA、Oracle在内的一些巨头公司看到了Java作为一门杰出的Web编程语言可能给他们带来的巨大商机。那么如何通过一门编程语言来赚钱呢?答案就是使用这门语言构建复杂...

局长
2016/11/29
2.5W
88
Java专家之路(三)---事务知识体系的总结

引言: 本文的意图在于:在帮助大家,从最简单的事务概念ACID开始, 宏观掌握事务的知识体系 通过demo实战来掌握各类事务,尤其是分布式事务的概念和原理 不同事务模型的事务处理办法 java中...

独孤文彬
2018/03/22
0
0
微服务 WildFly Swarm 简介

我们将看到的最后一个Java微服务框架是一个相对较新的场景,它利用了 JBoss WildFly 应用服务器中已试过且受信任的 JavaEE 功能。WildFly Swarm 是 WildFly 应用服务器的一个完整的拆下来的组...

woshixin
2018/06/20
76
0
Dubbo,SpringCloud,EJB对比

引言 最近一段时间不论互联网还是传统行业,凡是涉及信息技术范畴的圈子几乎都在讨论微服务框架。近期也看到各大技术社区开始组织一些沙龙和论坛来分享spring cloud的相关实施经验。 目前,S...

rock912
2017/07/05
256
0

没有更多内容

加载失败,请刷新页面

加载更多

刚得到一台centos7服务器,作为Java程序员应该做的事

1.JDK的卸载安装 卸载掉服务器本来的OPENJDK,安装ORELOC的JDK 检查是否已经安装了jdk rpm -qa | grep jdk 如果有的话,就卸载了再装 rpm -e --nodeps java-1.7.0-openjdk 去Oracle下载一...

ytuan996
16分钟前
15
0
ConcurrentHashMap(1.8)源码剖析

ConcurrentHashMap(JDK1.8)学习记录 看了忘忘了看系列之ConcurrentHashMap,本文主要记录下通过看ConcurrentHashMap源码学习到的知识点。主要有以下几个点。文章稍长,需要耐心阅读。 1、Con...

DoubleCherish
17分钟前
21
0
mysql之explain详解(分析索引最佳使用)

mysql之explain详解(分析索引最佳使用) mysql explain用于分析sql 语句的执行及数据库索引的使用。本文将致力于帮助大家充分理解explain所返回的各项参数,从而使大家快速掌握explain用法技...

科比可比克
昨天
14
0
如何比较两个不同分支的文件? - How to compare files from two different branches?

问题: I have a script that works fine in one branch and is broken in another. 我有一个脚本在一个分支中工作正常,在另一个分支中被破坏。 I want to look at the two versions side-...

技术盛宴
昨天
19
0
jenkins Euleros镜像打包

一、下载需要的软件 mkdir jenkins_software && cd jenkins_softwarewget --no-check-certificate -q https://mirrors.huaweicloud.com/epel/RPM-GPG-KEY-EPEL-7 -O RPM-GPG-KEY-EPEL-7......

mbzhong
昨天
53
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部