可配置输入参数的接口如何设计

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


作为程序员,我们绝大多数场景需要根据业务需求来设计系统,开发后端接口,这些接口通常都是在设计阶段就确定了,但是还有一些场景需要实现支持的配置的接口,包含以下功能

1、可以配置接口url

这个通常在一些论坛博客类系统中比较常见,用于设置个人主页,但其实这个又是不同概念,个人主页通常是get请求,然后通过path索引用户主页。

2、可选择接口具体实现

流程引擎、规则引擎通常就可以实现

3、输入参数和输出参数可配置

应用场景包括数据收集等

前面乱讲一通,最终回归本题——可配置输入参数的接口如何设计?

AI的回答

以下是AI的回答——可配置输入参数的接口如何设计?

设计支持可配置输入参数的接口时,主要目标是保证接口的灵活性和易用性,同时兼顾健壮性和可维护性。以下是一个通用的设计策略:

  1. 使用Map或Dictionary类型的参数
    可以定义一个Map<String, Object>作为接口的输入参数,其中键(key)表示参数名,值(value)表示参数值。这样可以根据需要动态添加或减少参数。
public interface MyService {
    Response handleRequest(Map<String, Object> parameters);
}

这种方法简单灵活,但对于复杂的参数校验和类型转换需要额外处理。

  1. 使用泛型请求类
    定义一个包含所有可能参数的请求类,使用Java Bean或者Builder模式,通过构造方法或setters注入参数,同时可以用@JsonCreator@AllArgsConstructor等注解支持JSON序列化和反序列化。
public class RequestParams<T> {
    private String param1;
    private int param2;
    private T customParam; // 泛型参数,可根据需求定制
     
    // 构造方法、getter、setter省略...

    // 或者使用Builder模式
    public static class Builder<T> {
        // ...
        public RequestParams build() { /*...*/ }
    }
}

public interface MyService {
    Response handleRequest(RequestParams<?> params);
}
  1. 使用策略模式
    如果参数的处理方式因参数类型而异,可以设计不同的策略类处理不同类型的参数,接口接收这些策略类作为参数。
public interface ParamStrategy {
    void process(RequestContext context);
}

public interface MyService {
    Response handleRequest(List<ParamStrategy> strategies);
}
  1. 使用命令模式
    对于复杂的操作,可以将参数封装成一个命令对象,命令对象包含所有执行操作所需的参数。
public interface Command {
    void execute();
}

public class SomeCommand implements Command {
    private final String param1;
    private final int param2;
    
    public SomeCommand(String param1, int param2) {
        this.param1 = param1;
        this.param2 = param2;
    }

    @Override
    public void execute() {
        // 使用param1和param2执行操作
    }
}

public interface MyService {
    Response handleRequest(Command command);
}

在设计时,请务必充分考虑参数的有效性检查、默认值设定以及错误处理机制,确保接口的健壮性。同时,良好的文档说明对于使用者来说非常重要,特别是当参数众多且复杂时。

实现思路

有了AI的帮助,思路一下子打开了是吧!

这里主要讨论Map作为参数具体该怎么实现。

若仅着眼于数据采集的目标,采用Map作为输入参数无疑是一种可行方案,然而这种方式也存在显著不足。尽管如此便捷,但数据的原始形态较为零散无序,在实际应用前仍需额外进行整理加工,因此从整体效果上看并不尽如人意。

所以还是需要一些设计的,简化流程如下,我讲的不好,也可以直接跳到下面demo看实现。

具体实现Demo

Controller

/**
 * @author wnhyang
 * @date 2023/11/29
 **/
@RestController
@RequestMapping("/risk")
@RequiredArgsConstructor
@Slf4j
public class RiskController {

    private final RiskService riskService;
    
    @PostMapping("/{name}/sync")
    public String syncRisk(@PathVariable("name") String name, @RequestBody Map<String, Object> params) {

        log.info("syncRisk {}", name);
        log.info("param {}", params);

        return riskService.syncRisk(name, params);
    }
    
}

Service

/**
 * @author wnhyang
 * @date 2023/11/29
 **/
@Service
@Slf4j
@RequiredArgsConstructor
public class RiskServiceImpl implements RiskService {

    /**
     * 模拟服务配置
     */
    private static final Map<String, Set<InputFieldVO>> SERVICE_CONFIG = Map.of(
            "publicInterface", new HashSet<>(Arrays.asList(
                    new InputFieldVO().setId(1L).setParamName("eventId").setDisplayName("事件标识").setName("N_S_eventId").setRequired(true).setType(0),
                    new InputFieldVO().setId(2L).setParamName("appName").setDisplayName("应用标识").setName("N_S_appName").setRequired(true).setType(0),
                    new InputFieldVO().setId(3L).setParamName("customerId").setDisplayName("客户号").setName("N_S_customerId").setRequired(true).setType(0),
                    new InputFieldVO().setId(5L).setParamName("age").setDisplayName("年龄").setName("N_N_age").setRequired(true).setType(1),
                    new InputFieldVO().setId(4L).setParamName("money").setDisplayName("金额").setName("N_F_money").setRequired(true).setType(2),
                    new InputFieldVO().setId(6L).setParamName("transTime").setDisplayName("交易时间").setName("N_D_transTime").setRequired(true).setType(3),
                    new InputFieldVO().setId(7L).setParamName("transType").setDisplayName("交易类型").setName("N_E_transType").setRequired(true).setType(4),
                    new InputFieldVO().setId(8L).setParamName("isPayee").setDisplayName("是否收款人").setName("N_B_isPayee").setRequired(true).setType(5),
                    new InputFieldVO().setId(9L).setParamName("event").setDisplayName("事件标识").setName("N_S_event").setRequired(true).setType(0)
            )));

    @Override
    public String syncRisk(String name, Map<String, Object> params) {

        // 通过SERVICE_CONFIG取name判空
        log.info("name {}", name);
        log.info("params {}", params);

        Set<InputFieldVO> fields = SERVICE_CONFIG.get(name);
        if (fields == null) {
            return "服务未找到";
        }
        // 使用增强for循环代替迭代器
        for (InputFieldVO field : fields) {
            Object value = params.get(field.getParamName());
            if (value != null) {
                // 设置field的值
                field.setValue(value);
                log.info("field {}", field);
            }
        }

        return fields.toString();
    }

    @Override
    public String asyncRisk(String name, Map<String, Object> params) {
        log.info("name {}", name);

        return null;
    }
}

InputFieldVO

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class InputFieldVO {

    /**
     * 参数名
     */
    private String paramName;

    /**
     * 是否必填
     */
    private Boolean required;

    /**
     * 值,默认null
     */
    private Object value;

    /**
     * 自增编号
     */
    private Long id;

    /**
     * 显示名
     */
    private String displayName;

    /**
     * 字段名
     */
    private String name;

    /**
     * 字段类型
     */
    private Integer type;

    /**
     * 字段分组
     */
    private Long groupId;

    /**
     * 描述
     */
    private String description;

    /**
     * 默认值
     */
    private String defaultValue;

    /**
     * 是否动态字段 N(normal)和D(dynamic)
     */
    private Boolean dynamic = Boolean.FALSE;

}

FieldTypeEnum

/**
 * @author wnhyang
 * @date 2023/12/5
 **/
@AllArgsConstructor
@Getter
public enum FieldTypeEnum {

    /**
     * 字符型
     */
    STRING(0, "S", "字符型"),

    /**
     * 整数型
     */
    NUMBER(1, "N", "整数型"),

    /**
     * 小数型
     */
    FLOAT(2, "F", "小数型"),

    /**
     * 日期型
     */
    DATE(3, "D", "日期型"),

    /**
     * 枚举型
     */
    ENUM(4, "E", "枚举型"),

    /**
     * 布尔型
     */
    BOOLEAN(5, "B", "布尔型");


    private final Integer code;

    private final String name;

    private final String desc;

}

测试

请求http://localhost:8080/risk/publicInterface/sync

{
  "eventId": "手机交易",
  "appName": "手机",
  "customerId": "123456",
  "age": "24",
  "money": "3.14159",
  "transTime": "2024-03-01 20:35:45",
  "transType": "2",
  "isPayee": "true",
  "event": "true"
}

日志打印如下

小结

就先这样草草结束了,还有以下需要说明一下。

1、字段类型区分的目的是为了便于后续规则或其他计算类方法能直接使用整数或浮点类字段,而且这类字段还可以通过比较大小来区分,当然枚举和布尔类也有其他用处。

2、动态字段是为脚本类字段预留的。

3、以上Demo没有加入数数据库,这个可以设计一下。

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/434607.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java微服务技术选型,Java学习的三个终极问题及学习路线规划

前言 在网络技术中基于浏览器的B/S结构无论在PC端还是手机端都充当着至关重要的角色。 PC端自不必说&#xff0c;手机中很多应用虽然是以APP的形式存在&#xff0c;但它采用的还是B/S结构。如今日头条、微信的朋友圈等&#xff0c;这些应用在内部封装了浏览器&#xff0c;后端…

mysql 时间精度问题

timestamp到2038年&#xff0c;还有14年时间&#xff0c;一个系统如果能活到那一刻也是相当不错了。 这里先看一下个datetime的问题,下面的插入数据的时间戳是2024-03-06 21:20:50.839 INSERT INTO psi_io_balance ( id, as_id, bill_date, order_id, busi_type, direction, c…

二分查找算法:高效搜索有序数据的利器

二分查找算法&#xff1a;高效搜索有序数据的利器 在计算机科学中&#xff0c;搜索是一项基本而重要的操作。对于有序数据&#xff0c;二分查找算法是一种高效的搜索方法。本文将介绍二分查找算法的原理、实现以及其在实际应用中的优势&#xff0c;帮助读者理解和应用这一常用的…

最强AI Claude 3有意识了?四个问题看出和ChatGPT差距

原文&#xff1a;赵侠客 前言 sora的热点还没有褪去&#xff0c;这两天又大火了Clude3&#xff0c;有的说超越GPT-4&#xff0c;还有的说有意识了&#xff0c;连马斯克都说人类也是文件也。我们这些吃瓜群众看着AI每隔几天一个热点&#xff0c;心理素质差的人有可能越来越焦虑…

飞塔防火墙开局百篇——002.FortiGate上网配置——WAN口配置PPPoE上网/拨号宽带上网

WAN口配置IP上网 修改wan口配置 修改wan口配置 登陆FortiGate防火墙界面&#xff0c;配置中文界面。 点击网络点击接口点击接口模式&#xff0c;选择PPPoE配置用户名&#xff08;eq.123456163.gd&#xff09;和密码(运营商提供)单击确认 欢迎关注个人公众号&#xff0c;采购设…

自动化测试的定位及一些思考

大家对自动化的理解&#xff0c;首先是想到Web UI自动化&#xff0c;这就为什么我一说自动化&#xff0c;公司一般就会有很多人反对&#xff0c;因为自动化的成本实在太高了&#xff0c;其实自动化是分为三个层面的&#xff08;UI层自动化、接口自动化、单元测试&#xff09;&a…

FC-AE-1553 协议

FC-AE-1553 协议 MIL-STD-1553B总线协议总线结构字格式消息传输方式 FC协议FC协议栈拓扑结构服务类型帧/序列/交换FC帧格式 FC-AE-1553网络构成帧类型命令帧状态帧数据帧 Information UnitsNC1NC2NC3-4NC5-7NT1-7 传输模式1. NC-NT2. NT-NC3. NT-NT4. 无数据字的模式命令5. 带数…

Android开发必须要会,android性能优化面试

前言 前一段时间和一些大牛们交流了一下&#xff0c;据反馈现在Android岗位也没有以前那么多了&#xff0c;没这么好找了&#xff0c;寒冬季节&#xff0c;大量公司模仿O2O模式导致死掉企业的很多&#xff0c;导致供大于求&#xff0c;当然这不意味着饱和&#xff0c;只是市场…

中文版国产Figma简单好上手

在过去的两年里&#xff0c;国内外协同办公室发展迅速。一方面&#xff0c;它是由突如其来的疫情推动的&#xff0c;另一方面&#xff0c;它是科学技术不断进步的必然结果。在市场的推动下&#xff0c;市场上出现了越来越多的协同办公软件&#xff0c;使工作场所的工作更加高效…

门电路加法器乘法器

前言 大家好我是jiantaoyab&#xff0c;这是我所总结作为学习的笔记第六篇,在这里分享给大家,还有一些书籍《深入理解计算机系统》《计算机组成&#xff1a;结构化方法》《计算机体系结构&#xff1a;量化研究方法》《程序员的自我修养》&#xff0c;今天我们来了解门电路,加法…

保留数据的重装系统教程!(win10系统)

上车警告&#xff01;&#xff01;&#xff01; 本教程无需思考&#xff0c;跟着操作一步一步来就能完成系统的重装。原理是将C盘系统重装&#xff0c;其他盘符数据保存。适用于系统盘重装数据或更改系统版本。 重要提示&#xff01;&#xff01;&#xff01; C盘有重要学习资…

【OpenAI Triton】理解矩阵乘法中的super-grouping 21a649eddf854db5ad4c7753afb7cb72

【OpenAI Triton】理解矩阵乘法中的super-grouping 前言 最近做推理加速&#xff0c;会涉及一些底层算子的工作&#xff0c;老早就听说triton写算子比较方便&#xff0c;最近正好有一些应用场景&#xff0c;就根据官方文档和大佬们的见解记录一下自己的所学所得&#xff1b; …

CMake-深入理解find_package()的用法

前言&#xff1a; CMake给我们提供了find_package()命令用来查找依赖包&#xff0c;理想情况下&#xff0c;一句find_package()命令就能把一整个依赖包的头文件包含路径、库路径、库名字、版本号等情况都获取到&#xff0c;后续只管用就好了。但实际使用过程可能会出现这样那样…

#微信小程序创建(获取onenet平台数据)

1.IDE&#xff1a;微信开发者工具 2.实验&#xff1a;创建一个小程序&#xff08;http get获取onenet平台数据&#xff09; 3.记录&#xff1a; 百度网盘链接&#xff1a;https://pan.baidu.com/s/1eOd-2EnilnhPWoGUMj0fzw 提取码: 2023 &#xff08;1&#xff09;新建一个工…

【工具相关】zentao用例管理平台部署实践

文章目录 一、备份还原1、数据备份1.1、前言1.2、版本备份1.3、数据备份 2、数据恢复2.1、版本恢复2.2、数据恢复 二、问题处理1、ERROR: SQLSTATE[HY000] [2002] Connection refused 一、备份还原 1、数据备份 1.1、前言 禅道系统从10.6版本以后&#xff0c;新增数据备份设…

lv20 QT进程线程编程

知识点&#xff1a;启动进程 &#xff0c;线程 &#xff0c;线程同步互斥 1 启动进程 应用场景&#xff1a;通常在qt中打开另一个程序 process模板 QString program “/bin/ls"; QStringList arguments; arguments << "-l" << “-a";QPro…

Java进阶-IO(4)

前面几篇介绍了java IO的基础部分&#xff0c;现在进入核心内容的学习&#xff0c;如File类、动态读取和序列化等&#xff0c;如下。 一、File类 1、概述 是 java.io 包中唯一代表磁盘文件本身的对象&#xff08;可以通过 File 类操作文件和目录&#xff09;&#xff0c;定义…

【Flutter 】get-cli init报错处理

报错内容 get init 命令跳出,报错内如下 Select which type of project you want to creat Synchronous waiting using dart:cli waitFor Unhandled exceotion . Dart WaitforEvent is deprecated and disabled by default. This feature will be fully removed in Dart 3.4 …

Docker安装MySQL镜像实战分享

今天我们对Docker安装MySQL镜像进行实战分享&#xff0c;以更深入的了解容器的使用场景。我们在云付服务器Ubuntu环境上已经安装好了Docker&#xff0c;接下来我们开始安装mysql5.7版本&#xff0c;安装mysql有两种思路&#xff0c;直接拉取mysql镜像和自己做mysql镜像&#xf…

【python基础学习10课_面向对象、封装、继承、多态】

一、类与对象 1、类的定义 在类的里面&#xff0c;称之为方法。 在类的外面&#xff0c;称之为函数。类&#xff1a;人类&#xff0c;一个族群&#xff0c;是一个群体类的语法规则&#xff1a;class 自定义的类名():属性 -- 变量方法 -- 函数类&#xff0c;首字母大写&#x…