ERP和MES对接接口模拟实现(待内部对接),接口映射初步工具实现(待应用)
57个文件已添加
6个文件已修改
4690 ■■■■■ 已修改文件
rsf-open-api/pom.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/annotation/OperationLog.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/aspect/LogAspect.java 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/WebMvcConfig.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/AuthController.java 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/TaskController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/WmsErpController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/WmsRcsController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/ApiAuthExampleController.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/AppAuthExampleController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/FieldMappingExampleController.java 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/JsonReplaceExampleController.java 326 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/TokenAuthExampleController.java 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/phyz/ERPController.java 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/phyz/MESController.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/platform/ApiFunctionController.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/platform/ApiMapController.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/platform/AppController.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/AppAuthParam.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/ApiForeignLog.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/ApiFunction.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/ApiMap.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/App.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/constant/Constants.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/dto/CommonResponse.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/CheckOrder.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/CheckResult.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Customer.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/InventoryDetails.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/InventoryQueryCondition.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/InventorySummary.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/MatPreparationOrder.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/MatPreparationOrderItem.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Material.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Order.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/OrderItem.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Pallet.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/SimpleProductionTask.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Station.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Supplier.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Task.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/TaskResult.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Warehouse.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/ApiForeignLogMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/ApiFunctionMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/ApiMapMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/AppMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/filter/AppIdAuthenticationFilter.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/service/AppAuthService.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/utils/AuthUtils.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/utils/TokenUtils.java 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/ApiForeignLogService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/ApiFunctionService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/ApiMapService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/AppService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/ApiForeignLogServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/ApiFunctionServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/ApiMapServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/AppServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/utils/JsonReplaceTest.java 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/utils/ParamsMapUtils.java 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/resources/logback-spring.xml 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/pom.xml
@@ -21,6 +21,24 @@
            <artifactId>rsf-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- JWT依赖 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>rsf-open-api</finalName>
rsf-open-api/src/main/java/com/vincent/rsf/openApi/annotation/OperationLog.java
New file
@@ -0,0 +1,42 @@
package com.vincent.rsf.openApi.annotation;
import java.lang.annotation.*;
/**
 * 操作日志记录注解
 *
 * @author vincent
 * @since 2020-03-21 17:03:08
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
    /**
     * 操作功能
     */
    String value() default "";
    /**
     * 操作模块
     */
    String module() default "";
    /**
     * 备注
     */
    String comments() default "";
    /**
     * 是否记录请求参数
     */
    boolean param() default true;
    /**
     * 是否记录返回结果
     */
    boolean result() default true;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/aspect/LogAspect.java
New file
@@ -0,0 +1,264 @@
package com.vincent.rsf.openApi.aspect;
import com.alibaba.fastjson.JSON;
import com.vincent.rsf.common.utils.Utils;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.openApi.annotation.OperationLog;
import com.vincent.rsf.openApi.entity.app.ApiForeignLog;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.service.ApiForeignLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.rmi.NoSuchObjectException;
import java.util.*;
/**
 * Created by Administrator on 2019-07-09.
 */
@Component
@Aspect
@Slf4j
@Order(2)
public class LogAspect {
    // 参数、返回结果、错误信息等最大保存长度
    private static final int MAX_LENGTH = 1000;
    // 用于记录请求耗时
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    @Resource
    private ApiForeignLogService apiForeignLogService;
    public LogAspect() {
    }
    /**
     * 切入点
     * 匹配controller包及其所有子包下的所有类的所有方法
     */
    @Pointcut("execution(* com.vincent.rsf.openApi.controller..*.*(..))")
    public void controllerPc() {
    }
    /**
     * 环绕通知
     * @param pjp ProceedingJoinPoint
     * @return 方法结果
     */
    @Around("controllerPc()")
    public Object around(ProceedingJoinPoint pjp) {
        String methodName = pjp.getSignature().getName();
        try {
            ServletRequestAttributes attributes = Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .orElseThrow(() -> new NoSuchObjectException("前置通知中获取的 ServletRequestAttributes 对象为空"));
            HttpServletRequest request = attributes.getRequest();
//            if("getToken".equals(methodName) || "getToken".equals(methodName))
//                return pjp.proceed();
            // 前置通知
            log.info("------【前置通知】------");
            // 记录请求内容
            log.info("浏览器输入的网址:{}", request.getRequestURL().toString());
            log.info("HTTP_METHOD:{}", request.getMethod());
            log.info("IP:{}", request.getRemoteAddr());
            log.info("执行的业务方法名:{}", pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName());
            log.info("业务方法获得的参数:{}", Arrays.toString(pjp.getArgs()));
            Object result = pjp.proceed();
            // 后置通知
            log.info("------【后置通知】------");
            log.info("{}方法的返回值:{}", pjp.getSignature().getName(), result);
            saveLog1(pjp, result, null);
            return result;
        } catch (Throwable e) {
            // 异常通知
            log.error("------【异常通知】------");
            log.error("{}方法异常,参数:{},异常:", pjp.getSignature().getName(), Arrays.toString(pjp.getArgs()), e);
            saveLog1(pjp, null, (Exception) e);
            return CommonResponse.error("服务器处理数据异常");
        } finally {
            // 最终通知
            if(!methodName.isEmpty() && !"getRegistered".equals(methodName)){
                log.info("------【最终通知】------");
                log.info("{}方法执行结束", pjp.getSignature().getName());
            }
        }
    }
    private void saveLog1(JoinPoint joinPoint, Object result, Exception e) {
        ApiForeignLog record = new ApiForeignLog();
        Long endTime = startTime.get();
        record.setCreateTime(new Date());
        // 记录操作耗时
        if (endTime != null) {
            record.setSpendTime((int) (System.currentTimeMillis() - endTime));
        }
        record.setTimestamp(String.valueOf(endTime));
//        // 记录当前登录用户id、租户id
//        User user = getLoginUser();
//        if (user != null) {
//            record.setUserId(user.getId());
//            record.setTenantId(user.getTenantId());
//        }
        // 记录请求地址、请求方式、ip
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (attributes == null ? null : attributes.getRequest());
        if (request != null) {
            record.setUrl(request.getRequestURI());
//            record.setClientIp(IpTools.gainRealIp(request));
        }
        // 记录异常信息
        if (e != null) {
            record.setResult(0);
            record.setErr(Utils.sub(e.toString(), MAX_LENGTH));
        } else {
            record.setResult(1);
        }
//        // 记录操作功能
//        record.setNamespace(desc);
//        // 记录备注
//        if (!Cools.isEmpty(ol.comments())) {
//            record.setMemo(ol.comments());
//        }
        // 记录请求参数
        record.setRequest(Utils.sub(Arrays.toString(joinPoint.getArgs()), MAX_LENGTH));
        record.setResponse(Utils.sub(JSON.toJSONString(result), MAX_LENGTH));
        apiForeignLogService.saveAsync(record);
    }
    /**
     * 保存操作记录
     */
    private void saveLog(JoinPoint joinPoint, Object result, Exception e) {
        // 记录模块名、操作功能、请求方法、请求参数、返回结果
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (null == method) {
            return;
        }
        OperationLog ol = method.getAnnotation(OperationLog.class);
        if (null == ol) {
            return;
        }
        String desc = getDescription(method, ol);
        if (Cools.isEmpty(desc)) {
            return;
        }
        ApiForeignLog record = new ApiForeignLog();
        Long endTime = startTime.get();
        record.setCreateTime(new Date());
        // 记录操作耗时
        if (endTime != null) {
            record.setSpendTime((int) (System.currentTimeMillis() - endTime));
        }
        record.setTimestamp(String.valueOf(endTime));
//        // 记录当前登录用户id、租户id
//        User user = getLoginUser();
//        if (user != null) {
//            record.setUserId(user.getId());
//            record.setTenantId(user.getTenantId());
//        }
        // 记录请求地址、请求方式、ip
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (attributes == null ? null : attributes.getRequest());
        if (request != null) {
            record.setUrl(request.getRequestURI());
//            record.setClientIp(IpTools.gainRealIp(request));
        }
        // 记录异常信息
        if (e != null) {
            record.setResult(0);
            record.setErr(Utils.sub(e.toString(), MAX_LENGTH));
        } else {
            record.setResult(1);
        }
        // 记录操作功能
        record.setNamespace(desc);
        // 记录备注
        if (!Cools.isEmpty(ol.comments())) {
            record.setMemo(ol.comments());
        }
        // 记录请求参数
        if (ol.param() && request != null) {
            record.setRequest(Utils.sub(getParams(joinPoint, request), MAX_LENGTH));
        }
        // 记录请求结果
        if (ol.result() && result != null) {
            record.setResponse(Utils.sub(JSON.toJSONString(result), MAX_LENGTH));
        }
        apiForeignLogService.saveAsync(record);
    }
    /**
     * 获取操作功能
     *
     * @param method Method
     * @param ol     OperationLog
     * @return String
     */
    private String getDescription(Method method, OperationLog ol) {
        if (!Cools.isEmpty(ol.value())) {
            return ol.value();
        }
        return null;
    }
    /**
     * 获取请求参数
     *
     * @param joinPoint JoinPoint
     * @param request   HttpServletRequest
     * @return String
     */
    private String getParams(JoinPoint joinPoint, HttpServletRequest request) {
        String params;
        Map<String, String> paramsMap = new HashMap<>();
        Map<String, String[]> map = Collections.unmodifiableMap(request.getParameterMap());
        for (Map.Entry<String, String[]> entry : map.entrySet()) {
            paramsMap.put(entry.getKey(), Utils.join(entry.getValue(), ","));
        }
        if (paramsMap.keySet().size() > 0) {
            params = JSON.toJSONString(paramsMap);
        } else {
            StringBuilder sb = new StringBuilder();
            for (Object arg : joinPoint.getArgs()) {
                if (null == arg
                        || arg instanceof MultipartFile
                        || arg instanceof HttpServletRequest
                        || arg instanceof HttpServletResponse) {
                    continue;
                }
                sb.append(JSON.toJSONString(arg)).append(" ");
            }
            params = sb.toString();
        }
        return params;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java
New file
@@ -0,0 +1,36 @@
package com.vincent.rsf.openApi.config;
import com.vincent.rsf.openApi.security.filter.AppIdAuthenticationFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
 * API安全配置类
 * 用于注册API认证过滤器
 */
@Configuration
public class ApiSecurityConfig {
    @Resource
    private AppIdAuthenticationFilter appIdAuthenticationFilter;
    /**
     * 注册API认证过滤器(支持AppId/AppSecret和Token认证)
     *
     * @return 过滤器注册Bean
     */
    @Bean
    public FilterRegistrationBean<AppIdAuthenticationFilter> apiAuthenticationFilter() {
        FilterRegistrationBean<AppIdAuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(appIdAuthenticationFilter);
        registrationBean.addUrlPatterns("/api/*", "/erp/*", "/mes/*", "/agv/*"); // 拦截API请求、ERP请求、MES请求、管理AGV任务请求
        registrationBean.setName("apiAuthenticationFilter");
        registrationBean.setOrder(1); // 设置过滤器优先级
        return registrationBean;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/WebMvcConfig.java
@@ -2,7 +2,9 @@
import com.vincent.rsf.openApi.entity.constant.Constants;
import com.vincent.rsf.openApi.security.filter.AppIdAuthenticationFilter;
import com.vincent.rsf.openApi.utils.Http;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
@@ -23,36 +25,15 @@
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private AppIdAuthenticationFilter appIdAuthenticationFilter;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getAsyncHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/swagger-resources/**", "/webjars/**","/erp/**", "/v2/**","/v3/**","/doc.html/**", "/swagger-ui.html/**");
    }
    @Bean
    public AsyncHandlerInterceptor getAsyncHandlerInterceptor() {
        return new AsyncHandlerInterceptor(){
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                Http.cors(response);
                return true;
            }
        };
    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    @Override
    public void addCorsMappings(CorsRegistry registry) {
@@ -66,13 +47,14 @@
    }
    public static void cors(HttpServletResponse response){
        // 跨域设置
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Expose-Headers", Constants.TOKEN_HEADER_NAME);
    @Bean
    public AsyncHandlerInterceptor getAsyncHandlerInterceptor() {
        return new AsyncHandlerInterceptor(){
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                Http.cors(response);
                return true;
            }
        };
    }
}
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/AuthController.java
New file
@@ -0,0 +1,174 @@
package com.vincent.rsf.openApi.controller;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.openApi.entity.constant.Constants;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.entity.AppAuthParam;
import com.vincent.rsf.openApi.security.service.AppAuthService;
import com.vincent.rsf.openApi.security.utils.TokenUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
 * App认证控制器
 *
 * 提供AppId和AppSecret的登录认证功能
 *
 * @author vincent
 * @since 2026-01-05
 */
@RestController
//@RequestMapping("/auth")
@Api(tags = "应用认证管理")
@Slf4j
public class AuthController {
    // 开启模拟数据
    @Value("${foreign.api.data.simulated}")
    public static String SIMULATED_DATA_ENABLE = "1";
    @Resource
    private AppAuthService appAuthService;
    /**
     * 获取App认证Token
     *
     * @param param 应用ID和应用密钥
     * @return 认证Token
     */
    @ApiOperation("获取App认证Token")
    @PostMapping("/getToken")
    public CommonResponse getToken(@RequestBody AppAuthParam param) {
        String appId = param.getAppId();
        String appSecret = param.getAppSecret();
        if (Cools.isEmpty(appId, appSecret)) {
            return CommonResponse.error("AppId和AppSecret不能为空");
        }
        boolean isValid = appAuthService.validateApp(appId, appSecret);
        if (isValid) {
            String token = Constants.TOKEN_PREFIX + TokenUtils.generateToken(appId, appSecret);  //appAuthService.generateAppToken(appId, appSecret);
            return CommonResponse.ok()
                    .setMsg("获取Token成功")
                    .setData(token);
        } else {
            return CommonResponse.error("AppId或AppSecret无效");
        }
    }
//    /**
//     * 验证Token的接口
//     *
//     * @param token 要验证的Token
//     * @return Token验证结果
//     */
//    @PostMapping("/validateToken")
//    public Map<String, Object> validateToken(@RequestHeader(name = "authorization") String token) {
//        log.info("验证Token: {}", token.substring(0, Math.min(10, token.length())) + "...");
//
//        boolean isValid = TokenUtils.validateToken(token);
//
//        Map<String, Object> response = new HashMap<>();
//        response.put("code", "200");
//        response.put("message", isValid ? "Token有效" : "Token无效");
//        response.put("data", Map.of(
//                "valid", isValid,
//                "appId", isValid ? TokenUtils.getAppIdFromToken(token) : null,
//                "userId", isValid ? TokenUtils.getUserIdFromToken(token) : null
//        ));
//        response.put("success", isValid);
//
//        return response;
//    }
//    /**
//     * AppId和AppSecret登录认证
//     *
//     * @param param 认证参数
//     * @return 认证结果
//     */
//    @ApiOperation("AppId和AppSecret登录认证")
//    @PostMapping("/login")
//    public CommonResponse login(@RequestBody AppAuthParam param) {
//        String appId = param.getAppId();
//        String appSecret = param.getAppSecret();
//
//        if (Cools.isEmpty(appId, appSecret)) {
//            return CommonResponse.error("AppId和AppSecret不能为空");
//        }
//
//        boolean isValid = appAuthService.validateApp(appId, appSecret);
//        if (isValid) {
//            // 生成Token
//            String token = appAuthService.generateAppToken(appId, appSecret);
//            return CommonResponse.ok()
//                    .setMsg("登录成功")
//                    .setData(token);
//        } else {
//            return CommonResponse.error("AppId或AppSecret无效");
//        }
//    }
//
//
//
//    /**
//     * 验证App认证
//     *
//     * @param request HTTP请求
//     * @return 验证结果
//     */
//    @ApiOperation("验证App认证")
//    @PostMapping("/validate")
//    public CommonResponse validate(HttpServletRequest request) {
//        String appId = request.getHeader(Constants.HEADER_APP_ID);
//        String appSecret = request.getHeader(Constants.HEADER_APP_SECRET);
//
//        if (Cools.isEmpty(appId, appSecret)) {
//            return CommonResponse.error("缺少AppId或AppSecret");
//        }
//
//        boolean isValid = appAuthService.validateApp(appId, appSecret);
//        if (isValid) {
//            return CommonResponse.ok()
//                    .setMsg("验证成功")
//                    .setData(appAuthService.getAppInfo(appId));
//        } else {
//            return CommonResponse.error("验证失败");
//        }
//    }
//
//    /**
//     * 获取当前认证的App信息
//     *
//     * @param request HTTP请求
//     * @return App信息
//     */
//    @ApiOperation("获取当前认证的App信息")
//    @GetMapping("/info")
//    public CommonResponse getAppInfo(HttpServletRequest request) {
//        String appId = (String) request.getAttribute("APP_ID");
//        if (appId == null) {
//            appId = request.getHeader(Constants.HEADER_APP_ID);
//        }
//
//        if (appId == null) {
//            return CommonResponse.error("未找到AppId");
//        }
//
//        var appInfo = appAuthService.getAppInfo(appId);
//        if (appInfo != null) {
//            return CommonResponse.ok()
//                    .setMsg("获取App信息成功")
//                    .setData(appInfo);
//        } else {
//            return CommonResponse.error("未找到App信息");
//        }
//    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/TaskController.java
New file
@@ -0,0 +1,59 @@
package com.vincent.rsf.openApi.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.entity.phyz.Task;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
import static com.vincent.rsf.openApi.controller.AuthController.SIMULATED_DATA_ENABLE;
import static com.vincent.rsf.openApi.controller.phyz.ERPController.paramsFormat;
@RestController
@Api("任务管理接口")
@Slf4j
public class TaskController {
    @ApiOperation("点对点创建AGV搬运任务")
    @PostMapping("/agv/transTask/add")
    public CommonResponse addAgvTask(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("点对点取消AGV搬运任务")
    @PostMapping("/agv/transTask/cancel")
    public CommonResponse cancelAgvTask(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/WmsErpController.java
@@ -17,7 +17,7 @@
import java.util.Objects;
@RestController
@RequestMapping("/erp")
@RequestMapping("/erp1")
@Api("ERP接口对接")
public class WmsErpController {
@@ -46,7 +46,7 @@
     * @return
     */
    @ApiOperation("单据修改")
    @PostMapping("/order/upadte")
    @PostMapping("/order/update")
    public CommonResponse modifyOrderDtel(@RequestBody ErpOpParams params) {
        if (Objects.isNull(params)) {
            throw new CoolException("参数不能为空!!");
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/WmsRcsController.java
@@ -3,6 +3,7 @@
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.entity.dto.SyncLocsDto;
import com.vincent.rsf.openApi.entity.params.ExMsgCallbackParams;
import com.vincent.rsf.openApi.entity.params.RcsPubTaskParams;
import com.vincent.rsf.openApi.entity.params.SyncRcsLocsParam;
@@ -15,6 +16,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -74,11 +76,12 @@
     */
    @ApiOperation("RCS库位信息同步")
    @PostMapping("/sync/locs")
    public R syncLocsToWms(@RequestBody SyncRcsLocsParam params) {
    public CommonResponse syncLocsToWms(@RequestBody SyncRcsLocsParam params) {
         if (Objects.isNull(params)) {
             return R.error("参数不能为空!!");
             throw new CoolException("参数不能为空!!");
         }
         return R.ok().add(wmsRcsService.syncLocs(params));
         List<SyncLocsDto> result = wmsRcsService.syncLocs(params);
         return CommonResponse.ok(result);
    }
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/ApiAuthExampleController.java
New file
@@ -0,0 +1,96 @@
package com.vincent.rsf.openApi.controller.example;
import com.vincent.rsf.openApi.entity.constant.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
 * API认证示例控制器
 * 演示如何使用统一的认证机制(支持AppId/AppSecret和Token)
 */
@RestController
@RequestMapping("/api/example/auth")
public class ApiAuthExampleController {
    private static final Logger log = LoggerFactory.getLogger(ApiAuthExampleController.class);
    /**
     * 获取受保护的数据 - 支持AppId/AppSecret或Token认证
     *
     * @param request HTTP请求
     * @return 受保护的数据
     */
    @GetMapping("/protected-data")
    public Map<String, Object> getProtectedData(HttpServletRequest request) {
        // 从请求属性中获取认证信息(由AppIdAuthenticationFilter设置)
        String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID);
        String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID);
        log.info("访问受保护接口,AppId: {}, UserId: {}", appId, userId);
        Map<String, Object> response = new HashMap<>();
        response.put("code", "200");
        response.put("message", "访问成功");
        response.put("data", Map.of(
            "appId", appId,
            "userId", userId,
            "protectedInfo", "这是受保护的数据",
            "authType", userId != null ? "Token" : "AppId/AppSecret",
            "timestamp", System.currentTimeMillis()
        ));
        response.put("success", true);
        return response;
    }
    /**
     * 获取认证信息 - 支持AppId/AppSecret或Token认证
     *
     * @param request HTTP请求
     * @return 认证信息
     */
    @GetMapping("/auth-info")
    public Map<String, Object> getAuthInfo(HttpServletRequest request) {
        // 从请求属性中获取认证信息
        String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID);
        String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID);
        log.info("获取认证信息,AppId: {}, UserId: {}", appId, userId);
        Map<String, Object> response = new HashMap<>();
        response.put("code", "200");
        response.put("message", "获取认证信息成功");
        response.put("data", Map.of(
            "appId", appId,
            "userId", userId,
            "authType", userId != null ? "Token" : "AppId/AppSecret",
            "authenticated", appId != null
        ));
        response.put("success", true);
        return response;
    }
    /**
     * 测试接口 - 不需要认证
     *
     * @return 测试数据
     */
    @GetMapping("/public-test")
    public Map<String, Object> getPublicTest() {
        Map<String, Object> response = new HashMap<>();
        response.put("code", "200");
        response.put("message", "公开接口访问成功");
        response.put("data", Map.of(
            "info", "这是一个不需要认证的公开接口",
            "timestamp", System.currentTimeMillis()
        ));
        response.put("success", true);
        return response;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/AppAuthExampleController.java
New file
@@ -0,0 +1,98 @@
package com.vincent.rsf.openApi.controller.example;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.security.utils.AuthUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
 * App认证使用示例控制器
 *
 * 演示如何在控制器中使用AppId认证
 *
 * @author vincent
 * @since 2026-01-05
 */
@RestController
@RequestMapping("/example/auth")
@Api(tags = "App认证使用示例")
public class AppAuthExampleController {
    /**
     * 需要App认证的接口示例
     *
     * @param request HTTP请求
     * @return 响应结果
     */
    @ApiOperation("需要App认证的接口示例")
    @GetMapping("/protected")
    public CommonResponse protectedEndpoint(HttpServletRequest request) {
        // 获取认证的AppId
        String appId = AuthUtils.getAppId(request);
        // 检查是否已认证
        if (appId == null) {
            return CommonResponse.error("未通过App认证");
        }
        return CommonResponse.ok()
                .setMsg("访问成功")
                .setData("认证的AppId: " + appId);
    }
    /**
     * 获取当前认证的App信息
     *
     * @param request HTTP请求
     * @return App信息
     */
    @ApiOperation("获取当前认证的App信息")
    @GetMapping("/app-info")
    public CommonResponse getAppInfo(HttpServletRequest request) {
        String appId = AuthUtils.getAppId(request);
        if (appId == null) {
            return CommonResponse.error("未通过App认证");
        }
        return CommonResponse.ok()
                .setMsg("获取App信息成功")
                .setData("当前AppId: " + appId);
    }
    /**
     * 无需认证的公开接口
     *
     * @return 响应结果
     */
    @ApiOperation("无需认证的公开接口")
    @GetMapping("/public")
    public CommonResponse publicEndpoint() {
        return CommonResponse.ok()
                .setMsg("公开接口访问成功")
                .setData("任何人都可以访问此接口");
    }
    /**
     * 检查认证状态
     *
     * @param request HTTP请求
     * @return 认证状态
     */
    @ApiOperation("检查认证状态")
    @PostMapping("/check-auth")
    public CommonResponse checkAuth(HttpServletRequest request) {
        boolean isAuthenticated = AuthUtils.isAuthenticated(request);
        String appId = AuthUtils.getAppId(request);
        return CommonResponse.ok()
                .setMsg("认证检查完成")
                .setData("isAuthenticated: " + isAuthenticated + ", appId: " + appId);
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/FieldMappingExampleController.java
New file
@@ -0,0 +1,195 @@
package com.vincent.rsf.openApi.controller.example;
import com.alibaba.fastjson.JSONObject;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.utils.ParamsMapUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 接口字段映射使用示例
 *
 * 展示如何在实际业务中使用 FuncMap 进行字段映射
 *
 * @author vincent
 * @since 2026-01-04
 */
@Slf4j
@RestController
@RequestMapping("/example/mapping")
@Api(tags = "字段映射使用示例")
public class FieldMappingExampleController {
    /**
     * 示例1:ERP订单接收 - 字段映射
     *
     * 场景:接收ERP系统的订单数据,字段名与WMS不一致,需要进行映射
     *
     * ERP字段 -> WMS字段:
     * orderNumber -> code
     * orderQty -> qty
     * orderAmount -> anfme
     */
    @ApiOperation("示例:ERP订单接收")
    @PostMapping("/erp/order/receive")
    public CommonResponse erpOrderReceive(@RequestBody JSONObject requestData) {
        log.info("接收到ERP订单数据(映射前):{}", requestData);
        // 执行字段映射
        String appId = "ERP_SYSTEM";
        String funcId = "ORDER_SYNC";
        JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData);
        log.info("字段映射后的数据:{}", mappedData);
        // 这里可以继续调用WMS内部的业务逻辑处理映射后的数据
        // orderService.createOrder(mappedData);
        return CommonResponse.ok()
                .setMsg("订单接收成功")
                .setData(mappedData);
    }
    /**
     * 示例2:物料信息同步 - 字段映射
     *
     * 场景:接收ERP系统的物料数据
     *
     * ERP字段 -> WMS字段:
     * materialCode -> matnr
     * materialName -> maktx
     * materialSpec -> spec
     */
    @ApiOperation("示例:物料信息同步")
    @PostMapping("/erp/material/sync")
    public CommonResponse materialSync(@RequestBody JSONObject requestData) {
        log.info("接收到物料数据(映射前):{}", requestData);
        // 执行字段映射
        String appId = "ERP_SYSTEM";
        String funcId = "MATNR_SYNC";
        JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData);
        log.info("字段映射后的数据:{}", mappedData);
        return CommonResponse.ok()
                .setMsg("物料同步成功")
                .setData(mappedData);
    }
    /**
     * 示例3:WCS任务创建 - 字段映射
     *
     * 场景:接收WCS系统的任务创建请求
     *
     * WCS字段 -> WMS字段:
     * containerCode -> barcode
     * stationCode -> sourceStaNo
     * taskType -> ioType
     */
    @ApiOperation("示例:WCS任务创建")
    @PostMapping("/wcs/task/create")
    public CommonResponse wcsTaskCreate(@RequestBody JSONObject requestData) {
        log.info("接收到WCS任务数据(映射前):{}", requestData);
        // 执行字段映射
        String appId = "WCS_SYSTEM";
        String funcId = "TASK_CREATE";
        JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData);
        log.info("字段映射后的数据:{}", mappedData);
        return CommonResponse.ok()
                .setMsg("任务创建成功")
                .setData(mappedData);
    }
    /**
     * 示例4:批量数据映射
     *
     * 场景:批量接收订单明细,需要对每条明细进行字段映射
     */
    @ApiOperation("示例:批量订单明细映射")
    @PostMapping("/erp/order/batch")
    public CommonResponse batchOrderMapping(@RequestBody JSONObject requestData) {
        log.info("接收到批量订单数据");
        String appId = "ERP_SYSTEM";
        String funcId = "ORDER_SYNC";
        // 映射订单头
        JSONObject orderHeader = requestData.getJSONObject("header");
        JSONObject mappedHeader = ParamsMapUtils.apiMaps(appId, funcId, orderHeader);
        // 映射订单明细列表
        com.alibaba.fastjson.JSONArray items = requestData.getJSONArray("items");
        com.alibaba.fastjson.JSONArray mappedItems = new com.alibaba.fastjson.JSONArray();
        if (items != null) {
            for (int i = 0; i < items.size(); i++) {
                JSONObject item = items.getJSONObject(i);
                JSONObject mappedItem = ParamsMapUtils.apiMaps(appId, funcId, item);
                mappedItems.add(mappedItem);
            }
        }
        // 组装结果
        JSONObject result = new JSONObject();
        result.put("header", mappedHeader);
        result.put("items", mappedItems);
        log.info("批量映射完成,共处理 {} 条明细", mappedItems.size());
        return CommonResponse.ok()
                .setMsg("批量处理成功")
                .setData(result);
    }
    /**
     * 示例5:条件映射
     *
     * 场景:根据不同的应用来源,使用不同的映射规则
     */
    @ApiOperation("示例:条件映射")
    @PostMapping("/dynamic/mapping")
    public CommonResponse dynamicMapping(@RequestBody JSONObject requestData) {
        // 从请求中获取应用标识
        String source = requestData.getString("source");
        String funcId = requestData.getString("function");
        // 移除元数据字段
        requestData.remove("source");
        requestData.remove("function");
        log.info("动态映射 - 来源:{},功能:{}", source, funcId);
        // 根据来源选择不同的映射规则
        String appId;
        switch (source) {
            case "ERP":
                appId = "ERP_SYSTEM";
                break;
            case "WCS":
                appId = "WCS_SYSTEM";
                break;
            case "MES":
                appId = "MES_SYSTEM";
                break;
            default:
                return CommonResponse.error("未知的数据源:" + source);
        }
        // 执行映射
        JSONObject mappedData = ParamsMapUtils.apiMaps(appId, funcId, requestData);
        return CommonResponse.ok()
                .setMsg("动态映射成功")
                .setData(mappedData);
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/JsonReplaceExampleController.java
New file
@@ -0,0 +1,326 @@
package com.vincent.rsf.openApi.controller.example;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.utils.ParamsMapUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
 * JSON属性名递归替换使用示例
 *
 * 演示如何使用 FuncMap 递归遍历并替换JSON所有层级的属性名称
 *
 * @author vincent
 * @since 2026-01-04
 */
@Slf4j
@RestController
@RequestMapping("/example/json-replace")
@Api(tags = "JSON属性递归替换示例")
public class JsonReplaceExampleController {
    /**
     * 示例1:简单对象的属性替换
     *
     * 输入:
     * {
     *   "orderNumber": "PO001",
     *   "orderQty": 100,
     *   "orderAmount": 5000.00
     * }
     *
     * 输出:
     * {
     *   "code": "PO001",
     *   "qty": 100,
     *   "anfme": 5000.00
     * }
     */
    @ApiOperation("示例1:简单对象属性替换")
    @PostMapping("/simple-replace")
    public CommonResponse simpleReplace(@RequestBody JSONObject data) {
        log.info("原始数据:{}", data);
        // 定义映射规则
        Map<String, String> mappingRules = new HashMap<>();
        mappingRules.put("orderNumber", "code");
        mappingRules.put("orderQty", "qty");
        mappingRules.put("orderAmount", "anfme");
        // 执行替换
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules);
        log.info("替换后数据:{}", result);
        return CommonResponse.ok()
                .setMsg("替换成功")
                .setData(result);
    }
    /**
     * 示例2:嵌套对象的深度替换
     *
     * 输入:
     * {
     *   "orderNumber": "PO001",
     *   "customer": {
     *     "customerName": "张三",
     *     "customerPhone": "13800138000",
     *     "address": {
     *       "cityName": "北京",
     *       "streetName": "朝阳路"
     *     }
     *   }
     * }
     *
     * 输出:
     * {
     *   "code": "PO001",
     *   "customer": {
     *     "name": "张三",
     *     "phone": "13800138000",
     *     "address": {
     *       "city": "北京",
     *       "street": "朝阳路"
     *     }
     *   }
     * }
     */
    @ApiOperation("示例2:嵌套对象深度替换")
    @PostMapping("/nested-replace")
    public CommonResponse nestedReplace(@RequestBody JSONObject data) {
        log.info("原始嵌套数据:{}", data);
        // 定义映射规则(适用于所有层级)
        Map<String, String> mappingRules = new HashMap<>();
        mappingRules.put("orderNumber", "code");
        mappingRules.put("customerName", "name");
        mappingRules.put("customerPhone", "phone");
        mappingRules.put("cityName", "city");
        mappingRules.put("streetName", "street");
        // 递归替换所有层级
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules);
        log.info("替换后嵌套数据:{}", result);
        return CommonResponse.ok()
                .setMsg("嵌套替换成功")
                .setData(result);
    }
    /**
     * 示例3:数组对象的批量替换
     *
     * 输入:
     * {
     *   "orderNumber": "PO001",
     *   "items": [
     *     {
     *       "materialCode": "MAT001",
     *       "materialName": "物料A",
     *       "itemQty": 10
     *     },
     *     {
     *       "materialCode": "MAT002",
     *       "materialName": "物料B",
     *       "itemQty": 20
     *     }
     *   ]
     * }
     *
     * 输出:
     * {
     *   "code": "PO001",
     *   "items": [
     *     {
     *       "matnr": "MAT001",
     *       "maktx": "物料A",
     *       "qty": 10
     *     },
     *     {
     *       "matnr": "MAT002",
     *       "maktx": "物料B",
     *       "qty": 20
     *     }
     *   ]
     * }
     */
    @ApiOperation("示例3:数组对象批量替换")
    @PostMapping("/array-replace")
    public CommonResponse arrayReplace(@RequestBody JSONObject data) {
        log.info("原始数组数据:{}", data);
        // 定义映射规则
        Map<String, String> mappingRules = new HashMap<>();
        mappingRules.put("orderNumber", "code");
        mappingRules.put("materialCode", "matnr");
        mappingRules.put("materialName", "maktx");
        mappingRules.put("itemQty", "qty");
        // 递归替换(包括数组中的所有对象)
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules);
        log.info("替换后数组数据:{}", result);
        return CommonResponse.ok()
                .setMsg("数组替换成功")
                .setData(result);
    }
    /**
     * 示例4:复杂结构的完整替换
     *
     * 输入包含:嵌套对象 + 数组 + 多层嵌套
     */
    @ApiOperation("示例4:复杂结构完整替换")
    @PostMapping("/complex-replace")
    public CommonResponse complexReplace(@RequestBody JSONObject data) {
        log.info("原始复杂数据:{}", data);
        // 定义完整的映射规则
        Map<String, String> mappingRules = new HashMap<>();
        // 订单字段
        mappingRules.put("orderNumber", "code");
        mappingRules.put("orderType", "type");
        mappingRules.put("orderQty", "qty");
        mappingRules.put("orderAmount", "anfme");
        mappingRules.put("orderStatus", "exceStatus");
        // 客户字段
        mappingRules.put("customerName", "custName");
        mappingRules.put("customerCode", "custCode");
        // 物料字段
        mappingRules.put("materialCode", "matnr");
        mappingRules.put("materialName", "maktx");
        mappingRules.put("materialSpec", "spec");
        mappingRules.put("materialUnit", "meins");
        // 其他字段
        mappingRules.put("warehouseCode", "whCode");
        mappingRules.put("locationCode", "locCode");
        // 执行递归替换
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, mappingRules);
        log.info("替换后复杂数据:{}", result);
        return CommonResponse.ok()
                .setMsg("复杂替换成功")
                .setData(result);
    }
    /**
     * 示例5:使用appId和funcId自动映射
     *
     * 这个示例展示如何结合数据库配置自动进行字段映射
     */
    @ApiOperation("示例5:使用配置自动映射")
    @PostMapping("/auto-replace")
    public CommonResponse autoReplace(@RequestBody JSONObject request) {
        String appId = request.getString("appId");
        String funcId = request.getString("funcId");
        JSONObject data = request.getJSONObject("data");
        log.info("自动映射 - appId:{}, funcId:{}, 数据:{}", appId, funcId, data);
        // 使用 FuncMap 的 apiMaps 方法,它会自动从数据库加载映射规则并递归替换
        JSONObject result = ParamsMapUtils.apiMaps(appId, funcId, data);
        log.info("自动映射后数据:{}", result);
        return CommonResponse.ok()
                .setMsg("自动映射成功")
                .setData(result);
    }
    /**
     * 示例6:直接替换JSON数组
     */
    @ApiOperation("示例6:直接替换JSON数组")
    @PostMapping("/array-direct-replace")
    public CommonResponse arrayDirectReplace(@RequestBody JSONArray array) {
        log.info("原始JSON数组:{}", array);
        // 定义映射规则
        Map<String, String> mappingRules = new HashMap<>();
        mappingRules.put("materialCode", "matnr");
        mappingRules.put("materialName", "maktx");
        mappingRules.put("stockQty", "qty");
        mappingRules.put("warehouseCode", "whCode");
        // 直接替换数组
        JSONArray result = ParamsMapUtils.replaceJsonArrayKeys(array, mappingRules);
        log.info("替换后JSON数组:{}", result);
        return CommonResponse.ok()
                .setMsg("数组直接替换成功")
                .setData(result);
    }
    /**
     * 测试用例:生成示例数据
     */
    @ApiOperation("生成测试数据")
    @PostMapping("/generate-test-data")
    public CommonResponse generateTestData() {
        // 创建复杂的测试数据
        JSONObject testData = new JSONObject();
        testData.put("orderNumber", "PO20260104001");
        testData.put("orderType", "PURCHASE");
        testData.put("orderQty", 500);
        testData.put("orderAmount", 25000.00);
        testData.put("orderStatus", "PENDING");
        // 客户信息
        JSONObject customer = new JSONObject();
        customer.put("customerCode", "CUST001");
        customer.put("customerName", "北京科技有限公司");
        JSONObject address = new JSONObject();
        address.put("cityName", "北京");
        address.put("districtName", "朝阳区");
        address.put("streetName", "建国路88号");
        customer.put("address", address);
        testData.put("customer", customer);
        // 订单明细
        JSONArray items = new JSONArray();
        for (int i = 1; i <= 3; i++) {
            JSONObject item = new JSONObject();
            item.put("itemNo", i);
            item.put("materialCode", "MAT00" + i);
            item.put("materialName", "测试物料" + i);
            item.put("materialSpec", "规格" + i);
            item.put("materialUnit", "EA");
            item.put("itemQty", 100 + i * 50);
            item.put("itemAmount", 5000.00 + i * 1000);
            // 仓库信息
            JSONObject warehouse = new JSONObject();
            warehouse.put("warehouseCode", "WH0" + i);
            warehouse.put("locationCode", "LOC-A-0" + i);
            item.put("warehouse", warehouse);
            items.add(item);
        }
        testData.put("items", items);
        return CommonResponse.ok()
                .setMsg("测试数据生成成功")
                .setData(testData);
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/example/TokenAuthExampleController.java
New file
@@ -0,0 +1,182 @@
package com.vincent.rsf.openApi.controller.example;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.openApi.entity.constant.Constants;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.entity.AppAuthParam;
import com.vincent.rsf.openApi.security.service.AppAuthService;
import com.vincent.rsf.openApi.security.utils.TokenUtils;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
 * Token认证示例控制器
 * 演示如何使用JWT Token进行接口保护
 */
@RestController
@RequestMapping("/api/example/token")
public class TokenAuthExampleController {
    private static final Logger log = LoggerFactory.getLogger(TokenAuthExampleController.class);
    @Resource
    private AppAuthService appAuthService;
    /**
     * 获取受保护的数据 - 需要有效的Token
     *
     * @param request HTTP请求
     * @return 受保护的数据
     */
    @GetMapping("/protected-data")
    public Map<String, Object> getProtectedData(HttpServletRequest request) {
        // 从请求属性中获取认证信息(由TokenAuthenticationFilter设置)
        String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID);
        String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID);
        log.info("访问受保护接口,AppId: {}, UserId: {}", appId, userId);
        Map<String, Object> response = new HashMap<>();
        response.put("code", "200");
        response.put("message", "访问成功");
        response.put("data", Map.of(
            "appId", appId,
            "userId", userId,
            "protectedInfo", "这是受保护的数据",
            "timestamp", System.currentTimeMillis()
        ));
        response.put("success", true);
        return response;
    }
    /**
     * 获取用户信息 - 需要有效的Token
     *
     * @param request HTTP请求
     * @return 用户信息
     */
    @GetMapping("/user-info")
    public Map<String, Object> getUserInfo(HttpServletRequest request) {
        // 从请求属性中获取认证信息
        String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID);
        String userId = (String) request.getAttribute(Constants.REQUEST_ATTR_USER_ID);
        log.info("获取用户信息,AppId: {}, UserId: {}", appId, userId);
        Map<String, Object> response = new HashMap<>();
        response.put("code", "200");
        response.put("message", "获取用户信息成功");
        response.put("data", Map.of(
            "appId", appId,
            "userId", userId,
            "userName", "用户" + (userId != null ? userId : "未知"),
            "role", "USER",
            "permissions", new String[]{"read", "write"}
        ));
        response.put("success", true);
        return response;
    }
    /**
     * 手动生成Token的示例接口
     * 注意:在实际应用中,这个接口通常需要其他形式的认证
     *
     * @param appId 应用ID
     * @param userId 用户ID
     * @return 包含Token的响应
     */
    @PostMapping("/generate-token")
    public Map<String, Object> generateToken(@RequestParam String appId, @RequestParam(required = false) String userId) {
        log.info("生成Token,AppId: {}, UserId: {}", appId, userId);
        try {
            // 生成Token
            String token = TokenUtils.generateToken(appId, userId);
            Map<String, Object> response = new HashMap<>();
            response.put("code", "200");
            response.put("message", "Token生成成功");
            response.put("data", Map.of(
                "token", token,
                "appId", appId,
                "userId", userId,
                "expiresIn", 24 * 60 * 60 // 24小时过期
            ));
            response.put("success", true);
            return response;
        } catch (Exception e) {
            log.error("生成Token失败", e);
            Map<String, Object> response = new HashMap<>();
            response.put("code", "500");
            response.put("message", "生成Token失败: " + e.getMessage());
            response.put("data", null);
            response.put("success", false);
            return response;
        }
    }
    /**
     * 获取App认证Token
     *
     * @param param 应用ID和应用密钥
     * @return 认证Token
     */
    @ApiOperation("获取App认证Token")
    @PostMapping("/getToken")
    public CommonResponse getToken(@RequestBody AppAuthParam param) {
        String appId = param.getAppId();
        String appSecret = param.getAppSecret();
        if (Cools.isEmpty(appId, appSecret)) {
            return CommonResponse.error("AppId和AppSecret不能为空");
        }
        boolean isValid = appAuthService.validateApp(appId, appSecret);
        if (isValid) {
            String token = appAuthService.generateAppToken(appId, appSecret);
            return CommonResponse.ok()
                    .setMsg("获取Token成功")
                    .setData(token);
        } else {
            return CommonResponse.error("AppId或AppSecret无效");
        }
    }
//    /**
//     * 验证Token的接口
//     *
//     * @param token 要验证的Token
//     * @return Token验证结果
//     */
//    @PostMapping("/validateToken")
//    public Map<String, Object> validateToken(@RequestParam String token) {
//        log.info("验证Token: {}", token.substring(0, Math.min(10, token.length())) + "...");
//
//        boolean isValid = TokenUtils.validateToken(token);
//
//        Map<String, Object> response = new HashMap<>();
//        response.put("code", "200");
//        response.put("message", isValid ? "Token有效" : "Token无效");
//        response.put("data", Map.of(
//                "valid", isValid,
//                "appId", isValid ? TokenUtils.getAppIdFromToken(token) : null,
//                "userId", isValid ? TokenUtils.getUserIdFromToken(token) : null
//        ));
//        response.put("success", isValid);
//
//        return response;
//    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/phyz/ERPController.java
New file
@@ -0,0 +1,284 @@
package com.vincent.rsf.openApi.controller.phyz;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.entity.phyz.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
import static com.vincent.rsf.openApi.controller.AuthController.SIMULATED_DATA_ENABLE;
@RestController
@RequestMapping("/erp")
@Api("银座新工厂(五期)ERP接口")
@Slf4j
public class ERPController {
    @ApiOperation("仓库信息同步")
    @PostMapping("/wareHouse/sync")
    public CommonResponse syncWareHouse(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Warehouse> warehouseList = JSON.parseArray(params.toJSONString(), Warehouse.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("物料信息同步")
    @PostMapping("/mat/sync")
    public CommonResponse syncMaterial(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Material> materialList = JSON.parseArray(params.toJSONString(), Material.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("客户信息同步")
    @PostMapping("/customer/sync")
    public CommonResponse syncCustomer(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Customer> customerList = JSON.parseArray(params.toJSONString(), Customer.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("供应商信息同步")
    @PostMapping("/supplier/sync")
    public CommonResponse syncSupplier(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Supplier> supplierList = JSON.parseArray(params.toJSONString(), Supplier.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("入/出库任务通知单")
    @PostMapping("/order/add")
    public CommonResponse addOrder(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Order> orderList = JSON.parseArray(params.toJSONString(), Order.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("入/出库任务通知单取消")
    @PostMapping("/order/cancel")
    public CommonResponse cancelOrder(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Order> orderList = JSON.parseArray(params.toJSONString(), Order.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("库存查询明细")
    @PostMapping("/inventory/details")
    public CommonResponse queryInventoryDetails(@RequestBody JSONObject params) {
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            String x = "[\n" +
                    "  {\n" +
                    "    \"locId\": \"LOC-A-01-01\",\n" +
                    "    \"wareHouseId\": \"WH001\",\n" +
                    "    \"wareHouseName\": \"原料仓库\",\n" +
                    "    \"palletId\": \"PALLET001\",\n" +
                    "    \"matNr\": \"MAT10001\",\n" +
                    "    \"makTx\": \"钢材Q235\",\n" +
                    "    \"spec\": \"国标GB/T700-2006\",\n" +
                    "    \"anfme\": 10.5,\n" +
                    "    \"unit\": \"吨\",\n" +
                    "    \"status\": \"可用\",\n" +
                    "    \"orderType\": 1,\n" +
                    "    \"orderNo\": \"Order202698921\",\n" +
                    "    \"prepareType\": 1,\n" +
                    "    \"planNo\": \"PLAN202601060001\",\n" +
                    "    \"batch\": \"BATCH20260106001\",\n" +
                    "    \"stockOrgId\": \"ORG001\"\n" +
                    "  },\n" +
                    "  {\n" +
                    "    \"locId\": \"LOC-B-02-03\",\n" +
                    "    \"wareHouseId\": \"WH002\",\n" +
                    "    \"wareHouseName\": \"成品仓库\",\n" +
                    "    \"palletId\": \"PALLET002\",\n" +
                    "    \"matNr\": \"MAT20001\",\n" +
                    "    \"makTx\": \"电机组件\",\n" +
                    "    \"spec\": \"380V 50Hz\",\n" +
                    "    \"anfme\": 50,\n" +
                    "    \"unit\": \"台\",\n" +
                    "    \"status\": \"可用\",\n" +
                    "    \"orderType\": \"1\",\n" +
                    "    \"orderNo\": \"SO202601060001\",\n" +
                    "    \"prepareType\": 1,\n" +
                    "    \"planNo\": \"PLAN202601060002\",\n" +
                    "    \"batch\": \"BATCH20260106002\",\n" +
                    "    \"stockOrgId\": \"ORG001\"\n" +
                    "  }\n" +
                    "]";
            return CommonResponse.ok(JSONArray.parseArray(x, InventoryDetails.class));
        }
        InventoryQueryCondition condition = JSON.parseObject(params.toJSONString(), InventoryQueryCondition.class);
        // 数据处理,转发server
        List<InventoryDetails> inventoryDetails = Lists.newArrayList();
        return new CommonResponse().setCode(200).setData(inventoryDetails);
    }
    @ApiOperation("库存查询汇总")
    @PostMapping("/inventory/summary")
    public CommonResponse queryInventorySummary(@RequestBody JSONObject params) {
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            String s = "{\n" +
                    "  \"code\": 200,\n" +
                    "  \"msg\": \"操作成功\",\n" +
                    "  \"data\": [\n" +
                    "    {\n" +
                    "      \"wareHouseId\": \"WH001\",\n" +
                    "      \"wareHouseName\": \"原料仓库\",\n" +
                    "      \"matNr\": \"MAT10001\",\n" +
                    "      \"makTx\": \"钢材Q235\",\n" +
                    "      \"spec\": \"国标GB/T700-2006\",\n" +
                    "      \"anfme\": 10.5,\n" +
                    "      \"unit\": \"吨\",\n" +
                    "      \"stockOrgId\": \"ORG001\",\n" +
                    "      \"batch\": \"BATCH20260106001\",\n" +
                    "      \"planNo\": \"Plan20260106006\"\n" +
                    "    },\n" +
                    "    {\n" +
                    "      \"wareHouseId\": \"WH001\",\n" +
                    "      \"wareHouseName\": \"原料仓库\",\n" +
                    "      \"matNr\": \"MAT10002\",\n" +
                    "      \"makTx\": \"铝型材6061\",\n" +
                    "      \"spec\": \"国标GB/T3190-2008\",\n" +
                    "      \"anfme\": 20.3,\n" +
                    "      \"unit\": \"吨\",\n" +
                    "      \"stockOrgId\": \"ORG001\",\n" +
                    "      \"batch\": \"BATCH20260106002\",\n" +
                    "      \"planNo\": \"Plan20260106005\"\n" +
                    "    },\n" +
                    "    {\n" +
                    "      \"wareHouseId\": \"WH002\",\n" +
                    "      \"wareHouseName\": \"成品仓库\",\n" +
                    "      \"matNr\": \"MAT30001\",\n" +
                    "      \"makTx\": \"电机成品\",\n" +
                    "      \"spec\": \"380V 50Hz 15KW\",\n" +
                    "      \"anfme\": 100,\n" +
                    "      \"unit\": \"台\",\n" +
                    "      \"stockOrgId\": \"ORG001\",\n" +
                    "      \"batch\": \"BATCH20260106003\",\n" +
                    "      \"planNo\": \"Plan20260106004\"\n" +
                    "    }\n" +
                    "  ]\n" +
                    "}";
            return JSONObject.parseObject(s, CommonResponse.class);
        }
        InventoryQueryCondition condition = JSON.parseObject(params.toJSONString(), InventoryQueryCondition.class);
        // 数据处理,转发server
        List<InventorySummary> inventorySummaries = Lists.newArrayList();
        return new CommonResponse().setCode(200).setData(inventorySummaries);
    }
    @ApiOperation("盘点结果确认")
    @PostMapping("/check/confirm")
    public CommonResponse checkConfirm(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        CheckOrder checkResult = JSON.parseObject(params.toJSONString(), CheckOrder.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    /**
     * 兼容JSONObject和JSONArray格式数据
     *
     * @param data json格式参数
     * @return JSONArray格式数据
     */
    public static JSONArray paramsFormat(Object data) {
        if (Objects.isNull(data)) {
            return new JSONArray();
        }
        try {
            String jsonStr = JSON.toJSONString(data);
            if (jsonStr.startsWith("[")) {
                return JSON.parseArray(jsonStr);
            } else if (jsonStr.startsWith("{")) {
                JSONArray params = new JSONArray();
                params.add(JSON.parseObject(jsonStr));
                return params;
            }
        } catch (Exception e) {
            // 解析失败,返回空数组
            log.error("转换参数为json格式错误", e);
        }
        // 默认返回空数组
        return new JSONArray();
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/phyz/MESController.java
New file
@@ -0,0 +1,94 @@
package com.vincent.rsf.openApi.controller.phyz;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.entity.phyz.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
import static com.vincent.rsf.openApi.controller.AuthController.SIMULATED_DATA_ENABLE;
import static com.vincent.rsf.openApi.controller.phyz.ERPController.paramsFormat;
@RestController
@RequestMapping("/mes")
@Api("银座新工厂(五期)MES接口")
public class MESController {
    @ApiOperation("备料通知")
    @PostMapping("/callMaterial")
    public CommonResponse callMaterial(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("0")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<MatPreparationOrder> orders = JSON.parseArray(params.toJSONString(), MatPreparationOrder.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("简单生产领料、退料AGV任务")
    @PostMapping("/transTask/simpleProduction")
    public CommonResponse simpleProductionTask(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("0")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<SimpleProductionTask> tasks = JSON.parseArray(params.toJSONString(), SimpleProductionTask.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("空托出库")
    @PostMapping("/emptyTray/callOut")
    public CommonResponse callOutEmptyTray(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
    @ApiOperation("空托入库")
    @PostMapping("/emptyTray/backIn")
    public CommonResponse emptyTrayBackIn(@RequestBody Object objParams) {
        if (Objects.isNull(objParams)) {
            throw new CoolException("参数不能为空!!");
        }
        // 返回模拟数据
        if (SIMULATED_DATA_ENABLE.equals("1")) {
            return CommonResponse.ok();
        }
        JSONArray params = paramsFormat(objParams);
        List<Task> tasks = JSON.parseArray(params.toJSONString(), Task.class);
        // 数据处理,转发server
        return CommonResponse.ok();
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/platform/ApiFunctionController.java
New file
@@ -0,0 +1,100 @@
package com.vincent.rsf.openApi.controller.platform;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.openApi.entity.app.ApiFunction;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.service.ApiFunctionService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * ApiFunction管理Controller
 *
 * @author vincent
 * @since 2026-01-04
 */
@RestController
@RequestMapping("/api/function")
@Api(tags = "接口功能管理")
public class ApiFunctionController {
    @Autowired
    private ApiFunctionService apiFunctionService;
    @ApiOperation("分页查询接口功能列表")
    @GetMapping("/page")
    public CommonResponse page(@RequestParam(defaultValue = "1") Integer current,
                                @RequestParam(defaultValue = "10") Integer size) {
        Page<ApiFunction> page = apiFunctionService.page(new Page<>(current, size));
        return CommonResponse.ok().setData(page);
    }
    @ApiOperation("查询所有接口功能")
    @GetMapping("/list")
    public CommonResponse list() {
        List<ApiFunction> list = apiFunctionService.list();
        return CommonResponse.ok().setData(list);
    }
    @ApiOperation("根据ID查询接口功能")
    @GetMapping("/{id}")
    public CommonResponse getById(@PathVariable String id) {
        ApiFunction function = apiFunctionService.getById(id);
        return CommonResponse.ok().setData(function);
    }
    @ApiOperation("新增接口功能")
    @PostMapping
    public CommonResponse save(@RequestBody ApiFunction function) {
        boolean result = apiFunctionService.save(function);
        if (result) {
            apiFunctionService.refreshCache();
            return CommonResponse.ok().setMsg("新增成功");
        }
        return CommonResponse.error("新增失败");
    }
    @ApiOperation("更新接口功能")
    @PutMapping
    public CommonResponse update(@RequestBody ApiFunction function) {
        boolean result = apiFunctionService.updateById(function);
        if (result) {
            apiFunctionService.refreshCache();
            return CommonResponse.ok().setMsg("更新成功");
        }
        return CommonResponse.error("更新失败");
    }
    @ApiOperation("删除接口功能")
    @DeleteMapping("/{id}")
    public CommonResponse delete(@PathVariable String id) {
        boolean result = apiFunctionService.removeById(id);
        if (result) {
            apiFunctionService.refreshCache();
            return CommonResponse.ok().setMsg("删除成功");
        }
        return CommonResponse.error("删除失败");
    }
    @ApiOperation("批量删除接口功能")
    @DeleteMapping("/batch")
    public CommonResponse deleteBatch(@RequestBody List<String> ids) {
        boolean result = apiFunctionService.removeByIds(ids);
        if (result) {
            apiFunctionService.refreshCache();
            return CommonResponse.ok().setMsg("批量删除成功");
        }
        return CommonResponse.error("批量删除失败");
    }
    @ApiOperation("刷新功能缓存")
    @PostMapping("/refresh")
    public CommonResponse refresh() {
        apiFunctionService.refreshCache();
        return CommonResponse.ok().setMsg("缓存刷新成功");
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/platform/ApiMapController.java
New file
@@ -0,0 +1,138 @@
package com.vincent.rsf.openApi.controller.platform;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.openApi.entity.app.ApiMap;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.service.ApiMapService;
import com.vincent.rsf.openApi.utils.ParamsMapUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * ApiMap管理Controller
 *
 * @author vincent
 * @since 2026-01-04
 */
@RestController
@RequestMapping("/api/map")
@Api(tags = "字段映射管理")
public class ApiMapController {
    @Autowired
    private ApiMapService apiMapService;
    @ApiOperation("分页查询字段映射列表")
    @GetMapping("/page")
    public CommonResponse page(@RequestParam(defaultValue = "1") Integer current,
                                @RequestParam(defaultValue = "10") Integer size,
                                @RequestParam(required = false) String appId,
                                @RequestParam(required = false) String funcId) {
        LambdaQueryWrapper<ApiMap> wrapper = new LambdaQueryWrapper<>();
        if (appId != null && !appId.isEmpty()) {
            wrapper.eq(ApiMap::getAppId, appId);
        }
        if (funcId != null && !funcId.isEmpty()) {
            wrapper.eq(ApiMap::getFuncId, funcId);
        }
        Page<ApiMap> page = apiMapService.page(new Page<>(current, size), wrapper);
        return CommonResponse.ok().setData(page);
    }
    @ApiOperation("查询所有字段映射")
    @GetMapping("/list")
    public CommonResponse list(@RequestParam(required = false) String appId,
                                @RequestParam(required = false) String funcId) {
        LambdaQueryWrapper<ApiMap> wrapper = new LambdaQueryWrapper<>();
        if (appId != null && !appId.isEmpty()) {
            wrapper.eq(ApiMap::getAppId, appId);
        }
        if (funcId != null && !funcId.isEmpty()) {
            wrapper.eq(ApiMap::getFuncId, funcId);
        }
        List<ApiMap> list = apiMapService.list(wrapper);
        return CommonResponse.ok().setData(list);
    }
    @ApiOperation("根据ID查询字段映射")
    @GetMapping("/{id}")
    public CommonResponse getById(@PathVariable Integer id) {
        ApiMap map = apiMapService.getById(id);
        return CommonResponse.ok().setData(map);
    }
    @ApiOperation("新增字段映射")
    @PostMapping
    public CommonResponse save(@RequestBody ApiMap map) {
        boolean result = apiMapService.save(map);
        if (result) {
            apiMapService.refreshCache();
            return CommonResponse.ok().setMsg("新增成功");
        }
        return CommonResponse.error("新增失败");
    }
    @ApiOperation("批量新增字段映射")
    @PostMapping("/batch")
    public CommonResponse saveBatch(@RequestBody List<ApiMap> maps) {
        boolean result = apiMapService.saveBatch(maps);
        if (result) {
            apiMapService.refreshCache();
            return CommonResponse.ok().setMsg("批量新增成功");
        }
        return CommonResponse.error("批量新增失败");
    }
    @ApiOperation("更新字段映射")
    @PutMapping
    public CommonResponse update(@RequestBody ApiMap map) {
        boolean result = apiMapService.updateById(map);
        if (result) {
            apiMapService.refreshCache();
            return CommonResponse.ok().setMsg("更新成功");
        }
        return CommonResponse.error("更新失败");
    }
    @ApiOperation("删除字段映射")
    @DeleteMapping("/{id}")
    public CommonResponse delete(@PathVariable Integer id) {
        boolean result = apiMapService.removeById(id);
        if (result) {
            apiMapService.refreshCache();
            return CommonResponse.ok().setMsg("删除成功");
        }
        return CommonResponse.error("删除失败");
    }
    @ApiOperation("批量删除字段映射")
    @DeleteMapping("/batch")
    public CommonResponse deleteBatch(@RequestBody List<Integer> ids) {
        boolean result = apiMapService.removeByIds(ids);
        if (result) {
            apiMapService.refreshCache();
            return CommonResponse.ok().setMsg("批量删除成功");
        }
        return CommonResponse.error("批量删除失败");
    }
    @ApiOperation("刷新映射缓存")
    @PostMapping("/refresh")
    public CommonResponse refresh() {
        apiMapService.refreshCache();
        return CommonResponse.ok().setMsg("缓存刷新成功");
    }
//    @ApiOperation("刷新所有缓存")
//    @PostMapping("/refresh/all")
//    public CommonResponse refreshAll() {
//        new ParamsMapUtils().refreshAll();
//        return CommonResponse.ok().setMsg("所有缓存刷新成功");
//    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/platform/AppController.java
New file
@@ -0,0 +1,100 @@
package com.vincent.rsf.openApi.controller.platform;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.openApi.entity.app.App;
import com.vincent.rsf.openApi.entity.dto.CommonResponse;
import com.vincent.rsf.openApi.service.AppService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * App管理Controller
 *
 * @author vincent
 * @since 2026-01-04
 */
@RestController
@RequestMapping("/api/app")
@Api(tags = "应用管理")
public class AppController {
    @Autowired
    private AppService appService;
    @ApiOperation("分页查询应用列表")
    @GetMapping("/page")
    public CommonResponse page(@RequestParam(defaultValue = "1") Integer current,
                                @RequestParam(defaultValue = "10") Integer size) {
        Page<App> page = appService.page(new Page<>(current, size));
        return CommonResponse.ok().setData(page);
    }
    @ApiOperation("查询所有应用")
    @GetMapping("/list")
    public CommonResponse list() {
        List<App> list = appService.list();
        return CommonResponse.ok().setData(list);
    }
    @ApiOperation("根据ID查询应用")
    @GetMapping("/{id}")
    public CommonResponse getById(@PathVariable String id) {
        App app = appService.getById(id);
        return CommonResponse.ok().setData(app);
    }
    @ApiOperation("新增应用")
    @PostMapping
    public CommonResponse save(@RequestBody App app) {
        boolean result = appService.save(app);
        if (result) {
            appService.refreshCache();
            return CommonResponse.ok().setMsg("新增成功");
        }
        return CommonResponse.error("新增失败");
    }
    @ApiOperation("更新应用")
    @PutMapping
    public CommonResponse update(@RequestBody App app) {
        boolean result = appService.updateById(app);
        if (result) {
            appService.refreshCache();
            return CommonResponse.ok().setMsg("更新成功");
        }
        return CommonResponse.error("更新失败");
    }
    @ApiOperation("删除应用")
    @DeleteMapping("/{id}")
    public CommonResponse delete(@PathVariable String id) {
        boolean result = appService.removeById(id);
        if (result) {
            appService.refreshCache();
            return CommonResponse.ok().setMsg("删除成功");
        }
        return CommonResponse.error("删除失败");
    }
    @ApiOperation("批量删除应用")
    @DeleteMapping("/batch")
    public CommonResponse deleteBatch(@RequestBody List<String> ids) {
        boolean result = appService.removeByIds(ids);
        if (result) {
            appService.refreshCache();
            return CommonResponse.ok().setMsg("批量删除成功");
        }
        return CommonResponse.error("批量删除失败");
    }
    @ApiOperation("刷新应用缓存")
    @PostMapping("/refresh")
    public CommonResponse refresh() {
        appService.refreshCache();
        return CommonResponse.ok().setMsg("缓存刷新成功");
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/AppAuthParam.java
New file
@@ -0,0 +1,26 @@
package com.vincent.rsf.openApi.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
 * App认证参数
 *
 * 用于AppId和AppSecret的认证请求
 *
 * @author vincent
 * @since 2026-01-05
 */
@Data
@Accessors(chain = true)
@ApiModel(value = "AppAuthParam", description = "App认证参数")
public class AppAuthParam {
    @ApiModelProperty(value = "应用ID", required = true)
    private String appId;
    @ApiModelProperty(value = "应用密钥", required = true)
    private String appSecret;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/ApiForeignLog.java
New file
@@ -0,0 +1,136 @@
package com.vincent.rsf.openApi.entity.app;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.vincent.rsf.framework.common.Cools;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
@Data
@TableName("open_api_foreign_log")
public class ApiForeignLog implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    @ApiModelProperty(value= "ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 名称空间
     */
    @ApiModelProperty(value= "名称空间")
    private String namespace;
    /**
     * 接口地址
     */
    @ApiModelProperty(value= "接口地址")
    private String url;
    /**
     * 平台密钥
     */
    @ApiModelProperty(value= "平台密钥")
    private String appkey;
    /**
     * 时间戳
     */
    @ApiModelProperty(value= "时间戳")
    private String timestamp;
    /**
     * 客户端IP
     */
    @ApiModelProperty(value= "客户端IP")
    private String clientIp;
    /**
     * 请求内容
     */
    @ApiModelProperty(value= "请求内容")
    private String request;
    /**
     * 响应内容
     */
    @ApiModelProperty(value= "响应内容")
    private String response;
    /**
     * 消耗时间
     */
    @ApiModelProperty(value= "消耗时间")
    private Integer spendTime;
    /**
     * 异常内容
     */
    @ApiModelProperty(value= "异常内容")
    private String err;
    /**
     * 结果 1: 成功  0: 失败
     */
    @ApiModelProperty(value= "结果 1: 成功  0: 失败  ")
    private Integer result;
    /**
     * 用户
     */
    @ApiModelProperty(value= "用户")
    private Long userId;
    /**
     * 所属机构
     */
    @ApiModelProperty(value= "所属机构")
    private Long tenantId;
    /**
     * 添加时间
     */
    @ApiModelProperty(value= "添加时间")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date createTime;
    /**
     * 备注
     */
    @ApiModelProperty(value= "备注")
    private String memo;
    public String getResult$(){
        if (null == this.result){ return null; }
        switch (this.result){
            case 1:
                return "成功";
            case 0:
                return "失败";
            default:
                return String.valueOf(this.result);
        }
    }
    public String getCreateTime$(){
        if (Cools.isEmpty(this.createTime)){
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/ApiFunction.java
New file
@@ -0,0 +1,48 @@
package com.vincent.rsf.openApi.entity.app;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
@TableName("open_api_function")
public class ApiFunction implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * functionId
     */
    @ApiModelProperty(value= "functionId")
    @TableId(value = "id")
    private String id;
    /**
     * functionName
     */
    @ApiModelProperty(value = "functionName")
    private String name;
    /**
     * functionType,1 WMS接收;2 WMS发送;
     */
    @ApiModelProperty(value = "functionType")
    private Integer type;
    /**
     * functionUrl,对应rsf-server的controller接口相对路径
     */
    @ApiModelProperty(value = "functionUrl")
    private String url;
    /**
     * 租户id
     */
    @ApiModelProperty(value = "租户id")
    private Long tenant_id;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/ApiMap.java
New file
@@ -0,0 +1,78 @@
package com.vincent.rsf.openApi.entity.app;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
@TableName("open_api_attribute_map")
public class ApiMap implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * id
     */
    @ApiModelProperty(value= "id")
    @TableId(value = "id")
    private Integer id;
    /**
     * appId
     */
    @ApiModelProperty(value= "appId")
    private String appId;
    /**
     * func_id
     */
    @ApiModelProperty(value= "func_id")
    private String funcId;
    /**
     * func_type
     */
    @ApiModelProperty(value= "func_type")
    private String funcType;
    /**
     * source_attribute
     */
    @ApiModelProperty(value = "source_attribute")
    private String sourceAttribute;
//    /**
//     * source_type,源属性类型,Integer、String、Double、JSONObject、JSONArray
//     */
//    @ApiModelProperty(value = "source_type")
//    private String sourceType;
    /**
     * target_attribute
     */
    @ApiModelProperty(value = "target_attribute")
    private String targetAttribute;
//    /**
//     * target_type,目标属性类型,Integer、String、Double、JSONObject、JSONArray
//     */
//    @ApiModelProperty(value = "target_type")
//    private String targetType;
    /**
     * 是否启用,0 未启用;1 启用;
     */
    @ApiModelProperty(value = "是否启用,0 未启用;1 启用;")
    private Integer enable;
    /**
     * 租户id
     */
    @ApiModelProperty(value = "租户id")
    private Long tenant_id;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/app/App.java
New file
@@ -0,0 +1,54 @@
package com.vincent.rsf.openApi.entity.app;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
@TableName("open_api_app")
public class App implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * appId
     */
    @ApiModelProperty(value= "appId")
    @TableId(value = "id")
    private String id;
    /**
     * appScrect
     */
    @ApiModelProperty(value = "appScrect")
    private String screct;
    /**
     * appName
     */
    @ApiModelProperty(value = "appName")
    private String name;
    /**
     * appUrl
     */
    @ApiModelProperty(value = "appUrl")
    private String url;
    /**
     * 是否启用,0 未启用;1 启用;
     */
    @ApiModelProperty(value = "是否启用,0 未启用;1 启用;")
    private Integer enable;
    /**
     * 租户id
     */
    @ApiModelProperty(value = "租户id")
    private Long tenant_id;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/constant/Constants.java
@@ -28,7 +28,7 @@
    /**
     * 无权限错误码
     */
    public static final int UNAUTHORIZED_CODE = 403;
    public static final String UNAUTHORIZED_CODE = "403";
    /**
     * 无权限提示信息
@@ -38,7 +38,7 @@
    /**
     * 未认证错误码
     */
    public static final int UNAUTHENTICATED_CODE = 401;
    public static final String UNAUTHENTICATED_CODE = "401";
    /**
     * 未认证提示信息
@@ -131,4 +131,12 @@
     */
    public static final Integer TASK_SORT_MIN_VALUE =  0;
    // AppId认证相关常量
    public static final String HEADER_APP_ID = "appId"; //X-App-Id
    public static final String HEADER_APP_SECRET = "appSecret"; //X-App-Secret
    public static final String HEADER_AUTHORIZATION = "authorization";  //Authorization
    public static final String TOKEN_PREFIX = "Zoneyung";
    public static final String REQUEST_ATTR_APP_ID = "appId"; //request_app_id
    public static final String REQUEST_ATTR_USER_ID = "appSecret"; //request_user_id
    public static final String REQUEST_ATTR_APP_INFO = "APP_INFO";
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/dto/CommonResponse.java
@@ -1,5 +1,6 @@
package com.vincent.rsf.openApi.entity.dto;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -25,4 +26,35 @@
    @ApiModelProperty("响应结果")
    private Object data;
    /**
     * 成功响应
     */
    public static CommonResponse ok() {
        CommonResponse response = new CommonResponse();
        response.setCode(200);
        response.setMsg("操作成功");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("result", "SUCCESS");
        response.setData(jsonObject);
        return response;
    }
    public static CommonResponse ok(Object data) {
        CommonResponse response = new CommonResponse();
        response.setCode(200);
        response.setMsg("操作成功");
        response.setData(data);
        return response;
    }
    /**
     * 失败响应
     */
    public static CommonResponse error(String msg) {
        CommonResponse response = new CommonResponse();
        response.setCode(500);
        response.setMsg(msg);
        return response;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/CheckOrder.java
New file
@@ -0,0 +1,35 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "CheckOrder", description = "盘点单")
public class CheckOrder {
    // 盘点单号,唯一标识
    @NotNull
    @JsonProperty("orderNo")
    private String orderNo;
    // 盘点单名称
    @JsonProperty("orderName")
    private String orderName;
    // 确认时间,秒级时间戳
    private Long checkTime;
    // 单据类型,需ERP确定上报时需要使用哪个类型
    private String orderTypeId;
    // 资产组织
    private String assetOrgId;
    // 盘点方案,需ERP确定上报时使用哪个方案
    private String inventSchemeId;
    // 确认结果
    private List<CheckResult> checkItems;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/CheckResult.java
New file
@@ -0,0 +1,24 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "CheckResult", description = "盘点结果")
public class CheckResult {
    // 仓库编码
    private String wareHouseId;
    // 物料编码
    private String matNr;
    // 库存数量
    private Double qty;
    // 确认后库存数量,实际库存以WMS盘点为准,只保存不做库存数量调整
    private Double checkQty;
    // 是否解冻,0 解冻;1 不解冻;
    private Integer defrost;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Customer.java
New file
@@ -0,0 +1,39 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Customer", description = "客户信息同步")
public class Customer {
    // 客户编码,唯一标识
    @NotNull
    @JsonProperty("customerId")
    private String customerId;
    // 客户名称
    @JsonProperty("customerName")
    private String customerName;
    // 客户昵称
    private String customerNickName;
    // 客户分组,国内,国外
    private String customerGroup;
    // 联系人
    private String contact;
    // 联系电话
    private String telephone;
    // 邮箱
    private String email;
    // 地址
    private String address;
    // 操作类型,1 新增;2 修改;3禁用;4 反禁用;
    @JsonProperty("operateType")
    private Integer operateType;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/InventoryDetails.java
New file
@@ -0,0 +1,47 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "InventoryDetails", description = "库存明细")
public class InventoryDetails {
    // 库位编码
    private String locId;
    // 仓库编码
    private String wareHouseId;
    // 仓库名称
    private String wareHouseName;
    // 托盘码,如果一个托盘上备了2个工单号,则分为2条
    private String palletId;
    // 物料编码
    private String matNr;
    // 物料名称
    private String makTx;
    // 规格
    private String spec;
    // 数量,小数点最长6位
    private Double anfme;
    // 单位
    private String unit;
    // 库存状态,待ERP补充,示例:可用;冻结;
    private String status;
    // 绑定的订单类型,1 出库单;2 入库单;3 备料单;未绑定时为空
    private Integer orderType;
    // 订单号、备料单号,未绑定时为空
    private String orderNo;
    // 备料类型,为备料单时有,1 正常领料;2 生产补料;
    private Integer prepareType;
    // 计划跟踪号
    private String planNo;
    // 批次号
    private String batch;
    // 库存组织
    private String stockOrgId;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/InventoryQueryCondition.java
New file
@@ -0,0 +1,28 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "InventoryQueryCondition", description = "库存查询条件")
public class InventoryQueryCondition {
    // 仓库编码
    private String wareHouseId;
    // 物料编码
    private String matNr;
    // 物料组
    private String matGroup;
    // 库位编码
    private String locId;
    // 订单号/工单号/MES工单号
    private String orderNo;
    // 数计划跟踪号
    private String planNo;
    // 批次号
    private String batch;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/InventorySummary.java
New file
@@ -0,0 +1,34 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "InventorySummary", description = "库存汇总")
public class InventorySummary {
    // 仓库编码
    private String wareHouseId;
    // 仓库名称
    private String wareHouseName;
    // 物料编码
    private String matNr;
    // 物料名称
    private String makTx;
    // 规格
    private String spec;
    // 数量,小数点最长6位
    private Double anfme;
    // 单位
    private String unit;
    // 库存组织
    private String stockOrgId;
    // 批号
    private String batch;
    // 计划跟踪号
    private String planNo;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/MatPreparationOrder.java
New file
@@ -0,0 +1,30 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "MatPreparationOrder", description = "备料单")
public class MatPreparationOrder {
    // 备料单号,唯一标识
    @NotNull
    @JsonProperty("orderNo")
    private String orderNo;
    // 备料单创建时间,秒级时间戳
    @JsonProperty("orderTime")
    private Long orderTime;
    // 备料类型,为备料单时有,1 正常领料;2 生产补料;
    @JsonProperty("prepareType")
    private Integer prepareType;
    // 备料详情
    private List<MatPreparationOrderItem> orderItems;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/MatPreparationOrderItem.java
New file
@@ -0,0 +1,24 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "MatPreparationOrderItem", description = "备料单明细")
public class MatPreparationOrderItem {
    // 物料编码
    @NotNull
    @JsonProperty("matNr")
    private String matNr;
    // 备料数量
    @JsonProperty("anfme")
    private Double anfme;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Material.java
New file
@@ -0,0 +1,55 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Material", description = "物料信息同步")
public class Material {
    // 物料编码,唯一标识
    @NotNull
    @JsonProperty("matNr")
    private String matNr;
    // 物料名称
    @JsonProperty("makTx")
    private String makTx;
    // 物料分组编码
    @JsonProperty("groupId")
    private String groupId;
    // 物料分组
    @JsonProperty("groupName")
    private String groupName;
    // 型号
    private String model;
    // 重量
    private Double weight;
    // 颜色
    private String color;
    // 尺寸
    private String size;
    // 规格
    private String spec;
    // 描述
    private String describe;
    // 单位
    private String unit;
    // 基本单位,银座有双单位,如长度和重量,可能需要转换
    private String baseUnit;
    // 使用组织编码
    private String useOrgId;
    // 使用组织名称
    private String useOrgName;
    // 物料属性,外购等
    private String erpClsID;
    // 操作类型,1 新增;2 修改;3禁用;4 反禁用;
    @JsonProperty("operateType")
    private Integer operateType;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Order.java
New file
@@ -0,0 +1,78 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Order", description = "入/出库通知单")
public class Order {
    // 入/出库订单号,唯一标识
    @NotNull
    @JsonProperty("orderNo")
    private String orderNo;
    // 单据内码,唯一标识
    @JsonProperty("orderInternalCode")
    private String orderInternalCode;
    // 业务类型,示例:
    // 入库:收料通知单(PUR_ReceiveBill)、采购入库单(STK_InStock)、退料申请单(PUR_MRAPP)、采购退料单(PUR_MRB)、
    //   退货通知单(SAL_RETURNNOTICE)、销售退货单(SAL_RETURNSTOCK)、生产退料单(PRD_ReturnMtrl)、生产入库单(PRD_INSTOCK)/生产汇报单(PRD_MORPT)、
    //   其他入库单(STK_MISCELLANEOUS)
    // 出库:发货通知单(SAL_DELIVERYNOTICE)、销售出库单(SAL_OUTSTOCK)、出库申请单(STK_OutStockApply)、生产领料单(PRD_PickMtrl)、
    //   生产补料单(PRD_FeedMtrl)、其他出库单(STK_MisDelivery)
    // 调拨:调拨申请单(STK_TRANSFERAPPLY)、直接调拨单(STK_TransferDirect)
    private String wkType;
    // 订单类型,1 出库单;2 入库单;3 调拨单;
    private String type;
    // 创建日期,时间戳,精确到秒
    private Long createTime;
    // 业务日期,对账使用,时间戳,精确到秒
    private Long businessTime;
    // 库存方向
    private String stockDirect;
    // 订单明细
    private List<OrderItem> orderItems;
    // 出入库接驳站点,出库时将物料出库后运输至该站点,入库时从该站点将物料运回库中
    private String stationId;
    // 客户编码
    private String customerId;
    // 客户名称
    private String customerName;
    // 供应商编码
    private String supplierId;
    // 供应商名称
    private String supplierName;
    // 收料/发货组织
    private String stockOrgId;
    // 收料/发货组织名称
    private String stockOrgName;
    // 采购组织
    private String purchaseOrgId;
    // 采购组织名称
    private String purchaseOrgName;
    // 采购员
    private String purchaseUserId;
    // 采购员名称
    private String purchaseUserName;
    // 生产组织
    private String prdOrgId;
    // 生产组织名称
    private String prdOrgName;
    // 销售组织
    private String saleOrgId;
    // 销售组织名称
    private String saleOrgName;
    // 销售员
    private String saleUserId;
    // 销售员名称
    private String saleUserName;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/OrderItem.java
New file
@@ -0,0 +1,65 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "OrderItem", description = "入/出库通知单明细")
public class OrderItem {
    // 计划跟踪号
    @NotNull
    @JsonProperty("planNo")
    private String planNo;
    // 行内码,唯一标识
    @JsonProperty("lineId")
    private String lineId;
    // 物料编码,唯一标识
    @NotNull
    @JsonProperty("matNr")
    private String matNr;
    // 物料名称
    @JsonProperty("makTx")
    private String makTx;
    // 规格
    private String spec;
    // 型号
    private String model;
    // 数量
    private Double anfme;
    // 批号
    private String batch;
    // 单位
    private String unit;
    // 基本单位
    private String baseUnitId;
    // 计价单位
    private String priceUnitId;
    // 建议目标仓库
    private String palletId;
    // 调出仓
    private String targetWareHouseId;
    // 业务日期,对账使用,时间戳,精确到秒
    private String sourceWareHouseId;
    // 入库类型
    private String inStockType;
    // 货主类型
    private String ownerTypeId;
    // 货主
    private String ownerId;
    // 货主名称
    private String ownerName;
    // 保管者类型
    private String keeperTypeId;
    // 保管者
    private String keeperId;
    // 保管者名称
    private String keeperName;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Pallet.java
New file
@@ -0,0 +1,36 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Pallet", description = "托盘信息")
public class Pallet {
    // 托盘条码
    @NotNull
    @JsonProperty("BarCode")
    private String barCode;
    // 托盘编码
    @JsonProperty("PalletCode")
    private String palletCode;
    // 托盘名称
    @JsonProperty("PalletName")
    private String palletName;
    // 托盘类型编码
    @JsonProperty("PalletTypeCode")
    private String palletTypeCode;
    // 托盘类型
    @JsonProperty("PalletTypeName")
    private String palletTypeName;
    // 创建人
    @JsonProperty("CreatedBy")
    private String createdBy;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/SimpleProductionTask.java
New file
@@ -0,0 +1,14 @@
package com.vincent.rsf.openApi.entity.phyz;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class SimpleProductionTask extends Task {
    // 领料内容
    private String matText;
    // 领料区域,线下创建于MES同步
    private String regionId;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Station.java
New file
@@ -0,0 +1,39 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Station", description = "站点信息")
public class Station {
    // 接驳口编码
    @NotNull
    @JsonProperty("ConnPortCode")
    private String connPortCode;
    // 接驳口名称
    @JsonProperty("ConnPortName")
    private String connPortName;
    // 车间编码
    @JsonProperty("WorkshopCode")
    private String workshopCode;
    // 车间
    @JsonProperty("WorkshopName")
    private String workshopName;
    // 仓库编码
    @JsonProperty("ProductionLineCode")
    private String productionLineCode;
    // 仓库
    @JsonProperty("ProductionLineName")
    private String productionLineName;
    // 创建人
    @JsonProperty("CreatedBy")
    private String createdBy;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Supplier.java
New file
@@ -0,0 +1,39 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Supplier", description = "供应商信息同步")
public class Supplier {
    // 供应商编码,唯一标识
    @NotNull
    @JsonProperty("supplierId")
    private String supplierId;
    // 供应商名称
    @JsonProperty("supplierName")
    private String supplierName;
    // 供应商昵称
    private String supplierNickName;
    // 供应商分组,国内,国外
    private String supplierGroup;
    // 联系人
    private String contact;
    // 联系电话
    private String telephone;
    // 邮箱
    private String email;
    // 地址
    private String address;
    // 操作类型,1 新增;2 修改;3禁用;4 反禁用;
    @JsonProperty("operateType")
    private Integer operateType;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Task.java
New file
@@ -0,0 +1,36 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Task", description = "工作任务")
public class Task {
    // 任务号,唯一标识
    @NotNull
    @JsonProperty("taskNo")
    private String taskNo;
    // 任务名称
    @JsonProperty("taskName")
    private String taskName;
    // 任务类型,1 入库;2 出库;3 转移;
    private Integer taskType;
    // 任务优先级,范围:1-100;数字越大,优先级越高,默认10;
    private Integer initPriority;
    // 起始站点编号
    private String startStationId;
    // 目标站点编号
    private String endStationId;
    // 托盘编号
    private String palletId;
    // 托盘类型,枚举类型:1 托盘;2 料架;3 料箱;等枚举类型待后续对接
    private String palletType;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/TaskResult.java
New file
@@ -0,0 +1,24 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "TaskResult", description = "工作任务结果")
public class TaskResult {
    // 任务号,唯一标识
    @NotNull
    @JsonProperty("TaskNo")
    private String taskNo;
    // 状态
    @JsonProperty("Status")
    private String status;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/entity/phyz/Warehouse.java
New file
@@ -0,0 +1,33 @@
package com.vincent.rsf.openApi.entity.phyz;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Accessors(chain = true)
@ApiModel(value = "Warehouse", description = "仓库信息同步")
public class Warehouse {
    // 仓库编码,唯一标识
    @NotNull
    @JsonProperty("wareHouseId")
    private String wareHouseId;
    // 仓库名称
    @JsonProperty("wareHouseName")
    private String wareHouseName;
    // 仓库位置
    private String address;
    // 使用组织编码
    private String useOrgId;
    // 使用组织名称
    private String useOrgName;
    // 操作类型,1 新增;2 修改;3禁用;4 反禁用;
    @JsonProperty("operateType")
    private Integer operateType;
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/ApiForeignLogMapper.java
New file
@@ -0,0 +1,9 @@
package com.vincent.rsf.openApi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.vincent.rsf.openApi.entity.app.ApiForeignLog;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApiForeignLogMapper extends BaseMapper<ApiForeignLog> {
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/ApiFunctionMapper.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.openApi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.vincent.rsf.openApi.entity.app.ApiFunction;
import org.apache.ibatis.annotations.Mapper;
/**
 * ApiFunction Mapper
 *
 * @author vincent
 * @since 2026-01-04
 */
@Mapper
public interface ApiFunctionMapper extends BaseMapper<ApiFunction> {
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/ApiMapMapper.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.openApi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.vincent.rsf.openApi.entity.app.ApiMap;
import org.apache.ibatis.annotations.Mapper;
/**
 * ApiMap Mapper
 *
 * @author vincent
 * @since 2026-01-04
 */
@Mapper
public interface ApiMapMapper extends BaseMapper<ApiMap> {
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/mapper/AppMapper.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.openApi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.vincent.rsf.openApi.entity.app.App;
import org.apache.ibatis.annotations.Mapper;
/**
 * App Mapper
 *
 * @author vincent
 * @since 2026-01-04
 */
@Mapper
public interface AppMapper extends BaseMapper<App> {
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/filter/AppIdAuthenticationFilter.java
New file
@@ -0,0 +1,121 @@
package com.vincent.rsf.openApi.security.filter;
import com.vincent.rsf.openApi.entity.constant.Constants;
import com.vincent.rsf.openApi.security.service.AppAuthService;
import com.vincent.rsf.openApi.security.utils.TokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.utils.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * AppId和AppSecret认证过滤器
 *
 * 用于验证请求头中的AppId和AppSecret
 *
 * @author vincent
 * @since 2026-01-05
 */
@Slf4j
@Component
@Order(1)
public class AppIdAuthenticationFilter extends OncePerRequestFilter {
    @Resource
    private AppAuthService appAuthService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        // 检查是否为认证请求(如获取token)
        if (isAuthRequest(requestURI)) {
            // 对于认证请求,允许通过
            filterChain.doFilter(request, response);
            return;
        }
        String authHeader = request.getHeader(Constants.HEADER_AUTHORIZATION);
        if (authHeader != null) {
            String token = TokenUtils.extractTokenFromHeader(authHeader);
            if (token != null && TokenUtils.validateTokenTime(token)) {
                // Token时间认证成功,认证AppId和AppSecret
                String tokenAppId = TokenUtils.getAppIdFromToken(token);
                String tokenAppSecret = TokenUtils.getSecretFromToken(token);
                if (StringUtils.isBlank(tokenAppId) || StringUtils.isBlank(tokenAppSecret)
                        || !appAuthService.validateApp(tokenAppId, tokenAppSecret)) {
                    log.warn("Token验证失败");
                    sendErrorResponse(response, Integer.parseInt(Constants.UNAUTHENTICATED_CODE), "认证失败,请提供有效的Token");
                    return;
                } else {
                    request.setAttribute(Constants.REQUEST_ATTR_APP_ID, tokenAppId);
                }
            } else {
                log.warn("Token验证失败或缺失");
                sendErrorResponse(response, Integer.parseInt(Constants.UNAUTHENTICATED_CODE), "认证失败,请提供有效的Token");
                return;
            }
        } else {
            log.warn("缺少Token认证信息");
            sendErrorResponse(response, Integer.parseInt(Constants.UNAUTHENTICATED_CODE), "认证失败,请提供有效的Token");
            return;
        }
        filterChain.doFilter(request, response);
    }
    /**
     * 发送错误响应
     *
     * @param response HTTP响应
     * @param code 错误码
     * @param message 错误消息
     * @throws IOException
     */
    private void sendErrorResponse(HttpServletResponse response, int code, String message) throws IOException {
        response.setStatus(code);
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write("{\"code\": \"" + code + "\", \"msg\": \"" + message + "\", \"data\": null}");
        writer.flush();
    }
    /**
     * 检查是否为认证请求(不需要认证的请求)
     *
     * @param requestURI 请求URI
     * @return 是否为认证请求
     */
    private boolean isAuthRequest(String requestURI) {
        return requestURI.contains("/getToken");
//               || requestURI.contains("/auth/validate") ||
//               requestURI.contains("/auth/login");
    }
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String requestURI = request.getRequestURI();
        // 不过滤认证相关请求和公开接口
        return requestURI.contains("/auth/") ||
               requestURI.contains("/public/") ||
               requestURI.contains("/doc.html") ||
               requestURI.contains("/swagger") ||
               requestURI.contains("/webjars") ||
               requestURI.contains("/v2/api-docs") ||
               requestURI.contains("/v3/api-docs");
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/service/AppAuthService.java
New file
@@ -0,0 +1,112 @@
package com.vincent.rsf.openApi.security.service;
import com.vincent.rsf.openApi.entity.app.App;
import com.vincent.rsf.openApi.service.AppService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
 * App认证服务
 *
 * 提供AppId和AppSecret的验证功能
 *
 * @author vincent
 * @since 2026-01-05
 */
@Slf4j
@Service
public class AppAuthService {
    @Resource
    private AppService appService;
    /**
     * 验证AppId和AppSecret
     *
     * @param appId 应用ID
     * @param appSecret 应用密钥
     * @return 验证结果
     */
    public boolean validateApp(String appId, String appSecret) {
        log.debug("验证AppId: {}, AppSecret: ****", appId);
        if (appId == null || appSecret == null) {
            log.warn("AppId或AppSecret为空");
            return false;
        }
        try {
            // 从数据库查询应用信息
            App app = appService.getById(appId);
            if (app == null) {
                log.warn("未找到应用: {}", appId);
                return false;
            }
            // 检查应用是否启用
            if (app.getEnable() != 1) {
                log.warn("应用未启用: {}", appId);
                return false;
            }
            // 验证密钥
            boolean isValid = appSecret.equals(app.getScrect());
            if (!isValid) {
                log.warn("AppSecret验证失败: AppId={}", appId);
            } else {
                log.info("AppId认证成功: AppId={}", appId);
            }
            return isValid;
        } catch (Exception e) {
            log.error("验证AppId和AppSecret时发生异常", e);
            return false;
        }
    }
    public boolean validateApp(String appId) {
        App app = appService.getById(appId);
        return app != null;
    }
    /**
     * 获取应用信息
     *
     * @param appId 应用ID
     * @return 应用信息
     */
    public App getAppInfo(String appId) {
        if (appId == null) {
            return null;
        }
        try {
            return appService.getById(appId);
        } catch (Exception e) {
            log.error("获取应用信息失败: {}", appId, e);
            return null;
        }
    }
    /**
     * 生成AppToken(可选功能)
     *
     * @param appId 应用ID
     * @param appSecret 应用密钥
     * @return 生成的Token
     */
    public String generateAppToken(String appId, String appSecret) {
        // 这里可以实现基于AppId和AppSecret的Token生成逻辑
        // 例如使用JWT生成Token
        if (validateApp(appId, appSecret)) {
            // 生成Token的逻辑
            long timestamp = System.currentTimeMillis();
            String tokenData = appId + ":" + timestamp;
            // TODO:这里可以使用更安全的加密算法
            return java.util.Base64.getEncoder().encodeToString(tokenData.getBytes());
        }
        return null;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/utils/AuthUtils.java
New file
@@ -0,0 +1,54 @@
package com.vincent.rsf.openApi.security.utils;
import com.vincent.rsf.openApi.entity.constant.Constants;
import com.vincent.rsf.openApi.entity.app.App;
import javax.servlet.http.HttpServletRequest;
/**
 * 认证工具类
 *
 * 提供认证相关的通用功能
 *
 * @author vincent
 * @since 2026-01-05
 */
public class AuthUtils {
    /**
     * 从请求中获取AppId
     *
     * @param request HTTP请求
     * @return AppId
     */
    public static String getAppId(HttpServletRequest request) {
        // 优先从请求属性中获取(认证过滤器设置的)
        String appId = (String) request.getAttribute(Constants.REQUEST_ATTR_APP_ID);
        if (appId != null) {
            return appId;
        }
        // 从请求头获取
        return request.getHeader(Constants.HEADER_APP_ID);
    }
    /**
     * 从请求中获取App信息
     *
     * @param request HTTP请求
     * @return App信息
     */
    public static App getAppInfo(HttpServletRequest request) {
        return (App) request.getAttribute(Constants.REQUEST_ATTR_APP_INFO);
    }
    /**
     * 检查请求是否已通过App认证
     *
     * @param request HTTP请求
     * @return 是否已认证
     */
    public static boolean isAuthenticated(HttpServletRequest request) {
        return getAppId(request) != null;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/security/utils/TokenUtils.java
New file
@@ -0,0 +1,161 @@
package com.vincent.rsf.openApi.security.utils;
import com.vincent.rsf.openApi.entity.app.App;
import com.vincent.rsf.openApi.entity.constant.Constants;
import com.vincent.rsf.openApi.service.AppService;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.apache.tika.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;
/**
 * JWT Token工具类
 * 用于生成和验证JWT Token
 */
public class TokenUtils {
    private static final Logger log = LoggerFactory.getLogger(TokenUtils.class);
    // 使用一个安全的密钥,实际应用中应该从配置文件读取
    private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    // Token过期时间,默认1小时
    private static final long TOKEN_EXPIRATION = 60 * 60 * 1000L; // 24小时
    @Resource
    private AppService appService;
    /**
     * 生成JWT Token
     *
     * @param claims Token中包含的声明信息
     * @return 生成的Token字符串
     */
    public static String generateToken(Map<String, Object> claims) {
        long now = System.currentTimeMillis();
        Date expiration = new Date(now + TOKEN_EXPIRATION);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(expiration)
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
                .compact();
    }
    /**
     * 生成带AppId的Token
     *
     * @param appId 应用ID
     * @param appSecret 应用秘钥
     * @return 生成的Token字符串
     */
    public static String generateToken(String appId, String appSecret) {
        Map<String, Object> claims = Map.of(
            "appId", appId,
            "appSecret", appSecret,
            "created", System.currentTimeMillis()
        );
        return generateToken(claims);
    }
    /**
     * 解析Token获取声明信息
     *
     * @param token Token字符串
     * @return 声明信息
     */
    public static Claims parseToken(String token) {
        try {
            return Jwts.parserBuilder()
                    .setSigningKey(SECRET_KEY)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException e) {
            log.error("解析Token失败: {}", e.getMessage());
            return null;
        }
    }
    /**
     * 验证Token时间是否有效
     *
     * @param token Token字符串
     * @return 时间是否有效
     */
    public static boolean validateTokenTime(String token) {
        try {
            Claims claims = parseToken(token);
            if (claims == null) {
                return false;
            }
            // 检查Token是否过期
            Date expiration = claims.getExpiration();
            return expiration != null && expiration.after(new Date());
        } catch (JwtException e) {
            log.error("验证Token失败: {}", e.getMessage());
            return false;
        }
    }
    /**
     * 从Token中获取AppId
     *
     * @param token Token字符串
     * @return AppId
     */
    public static String getAppIdFromToken(String token) {
        Claims claims = parseToken(token);
        if (claims != null) {
            return (String) claims.get("appId");
        }
        return null;
    }
    /**
     * 从Token中获取appSecret
     *
     * @param token Token字符串
     * @return appSecret
     */
    public static String getSecretFromToken(String token) {
        Claims claims = parseToken(token);
        if (claims != null) {
            return (String) claims.get("appSecret");
        }
        return null;
    }
//    /**
//     * 从Token中获取UserId
//     *
//     * @param token Token字符串
//     * @return UserId
//     */
//    public static String getUserIdFromToken(String token) {
//        Claims claims = parseToken(token);
//        if (claims != null) {
//            return (String) claims.get("userId");
//        }
//        return null;
//    }
    /**
     * 从Authorization头中提取Token
     *
     * @param authHeader Authorization头内容
     * @return Token字符串(不包含Bearer前缀)
     */
    public static String extractTokenFromHeader(String authHeader) {
        if (authHeader != null && authHeader.startsWith(Constants.TOKEN_PREFIX)) {
            return authHeader.substring(Constants.TOKEN_PREFIX.length()).trim();
        }
        return null;
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/ApiForeignLogService.java
New file
@@ -0,0 +1,9 @@
package com.vincent.rsf.openApi.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.openApi.entity.app.ApiForeignLog;
public interface ApiForeignLogService extends IService<ApiForeignLog> {
    void saveAsync(ApiForeignLog log);
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/ApiFunctionService.java
New file
@@ -0,0 +1,18 @@
package com.vincent.rsf.openApi.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.openApi.entity.app.ApiFunction;
/**
 * ApiFunction Service
 *
 * @author vincent
 * @since 2026-01-04
 */
public interface ApiFunctionService extends IService<ApiFunction> {
    /**
     * 刷新功能缓存
     */
    void refreshCache();
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/ApiMapService.java
New file
@@ -0,0 +1,18 @@
package com.vincent.rsf.openApi.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.openApi.entity.app.ApiMap;
/**
 * ApiMap Service
 *
 * @author vincent
 * @since 2026-01-04
 */
public interface ApiMapService extends IService<ApiMap> {
    /**
     * 刷新映射缓存
     */
    void refreshCache();
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/AppService.java
New file
@@ -0,0 +1,18 @@
package com.vincent.rsf.openApi.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.openApi.entity.app.App;
/**
 * App Service
 *
 * @author vincent
 * @since 2026-01-04
 */
public interface AppService extends IService<App> {
    /**
     * 刷新应用缓存
     */
    void refreshCache();
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/ApiForeignLogServiceImpl.java
New file
@@ -0,0 +1,18 @@
package com.vincent.rsf.openApi.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.openApi.entity.app.ApiForeignLog;
import com.vincent.rsf.openApi.mapper.ApiForeignLogMapper;
import com.vincent.rsf.openApi.service.ApiForeignLogService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class ApiForeignLogServiceImpl extends ServiceImpl<ApiForeignLogMapper, ApiForeignLog> implements ApiForeignLogService {
    @Async
    @Override
    public void saveAsync(ApiForeignLog log) {
        baseMapper.insert(log);
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/ApiFunctionServiceImpl.java
New file
@@ -0,0 +1,30 @@
package com.vincent.rsf.openApi.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.openApi.entity.app.ApiFunction;
import com.vincent.rsf.openApi.mapper.ApiFunctionMapper;
import com.vincent.rsf.openApi.service.ApiFunctionService;
import com.vincent.rsf.openApi.utils.ParamsMapUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * ApiFunction Service Implementation
 *
 * @author vincent
 * @since 2026-01-04
 */
@Slf4j
@Service
public class ApiFunctionServiceImpl extends ServiceImpl<ApiFunctionMapper, ApiFunction> implements ApiFunctionService {
    @Override
    public void refreshCache() {
        log.info("开始刷新接口功能缓存...");
        List<ApiFunction> functions = this.list();
        ParamsMapUtils.FUNCTIONS = functions;
        log.info("接口功能缓存刷新完成,共加载 {} 个功能", functions.size());
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/ApiMapServiceImpl.java
New file
@@ -0,0 +1,32 @@
package com.vincent.rsf.openApi.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.openApi.entity.app.ApiMap;
import com.vincent.rsf.openApi.mapper.ApiMapMapper;
import com.vincent.rsf.openApi.service.ApiMapService;
import com.vincent.rsf.openApi.utils.ParamsMapUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * ApiMap Service Implementation
 *
 * @author vincent
 * @since 2026-01-04
 */
@Slf4j
@Service
public class ApiMapServiceImpl extends ServiceImpl<ApiMapMapper, ApiMap> implements ApiMapService {
    @Override
    public void refreshCache() {
        log.info("开始刷新字段映射缓存...");
        List<ApiMap> maps = this.list(new LambdaQueryWrapper<ApiMap>()
                .eq(ApiMap::getEnable, 1));
        ParamsMapUtils.ATTRIBUTE_MAPS = maps;
        log.info("字段映射缓存刷新完成,共加载 {} 条映射规则", maps.size());
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/impl/AppServiceImpl.java
New file
@@ -0,0 +1,32 @@
package com.vincent.rsf.openApi.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vincent.rsf.openApi.entity.app.App;
import com.vincent.rsf.openApi.mapper.AppMapper;
import com.vincent.rsf.openApi.service.AppService;
import com.vincent.rsf.openApi.utils.ParamsMapUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * App Service Implementation
 *
 * @author vincent
 * @since 2026-01-04
 */
@Slf4j
@Service
public class AppServiceImpl extends ServiceImpl<AppMapper, App> implements AppService {
    @Override
    public void refreshCache() {
        log.info("开始刷新应用缓存...");
        List<App> apps = this.list(new LambdaQueryWrapper<App>()
                .eq(App::getEnable, 1));
        ParamsMapUtils.APPS = apps;
        log.info("应用缓存刷新完成,共加载 {} 个应用", apps.size());
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/utils/JsonReplaceTest.java
New file
@@ -0,0 +1,192 @@
package com.vincent.rsf.openApi.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.HashMap;
import java.util.Map;
/**
 * JSON属性名递归替换测试类
 *
 * 演示如何使用 FuncMap 进行JSON属性名的递归替换
 *
 * @author vincent
 * @since 2026-01-04
 */
public class JsonReplaceTest {
    public static void main(String[] args) {
        System.out.println("========== JSON属性名递归替换测试 ==========\n");
        // 测试1:简单对象
        testSimpleObject();
        // 测试2:嵌套对象
        testNestedObject();
        // 测试3:包含数组
        testWithArray();
        // 测试4:复杂结构
        testComplexStructure();
    }
    /**
     * 测试1:简单对象属性替换
     */
    private static void testSimpleObject() {
        System.out.println("【测试1:简单对象】");
        // 构造测试数据
        JSONObject data = new JSONObject();
        data.put("orderNumber", "PO001");
        data.put("orderQty", 100);
        data.put("orderAmount", 5000.00);
        System.out.println("原始数据:" + data.toJSONString());
        // 定义映射规则
        Map<String, String> rules = new HashMap<>();
        rules.put("orderNumber", "code");
        rules.put("orderQty", "qty");
        rules.put("orderAmount", "anfme");
        // 执行替换
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules);
        System.out.println("替换结果:" + result.toJSONString());
        System.out.println();
    }
    /**
     * 测试2:嵌套对象深度替换
     */
    private static void testNestedObject() {
        System.out.println("【测试2:嵌套对象】");
        // 构造嵌套数据
        JSONObject data = new JSONObject();
        data.put("orderNumber", "PO002");
        JSONObject customer = new JSONObject();
        customer.put("customerName", "张三");
        customer.put("customerPhone", "13800138000");
        JSONObject address = new JSONObject();
        address.put("cityName", "北京");
        address.put("streetName", "朝阳路88号");
        customer.put("address", address);
        data.put("customer", customer);
        System.out.println("原始数据:" + data.toJSONString());
        // 定义映射规则(会应用到所有层级)
        Map<String, String> rules = new HashMap<>();
        rules.put("orderNumber", "code");
        rules.put("customerName", "name");
        rules.put("customerPhone", "phone");
        rules.put("cityName", "city");
        rules.put("streetName", "street");
        // 执行递归替换
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules);
        System.out.println("替换结果:" + result.toJSONString());
        System.out.println();
    }
    /**
     * 测试3:包含数组的替换
     */
    private static void testWithArray() {
        System.out.println("【测试3:包含数组】");
        // 构造包含数组的数据
        JSONObject data = new JSONObject();
        data.put("orderNumber", "PO003");
        JSONArray items = new JSONArray();
        for (int i = 1; i <= 3; i++) {
            JSONObject item = new JSONObject();
            item.put("materialCode", "MAT00" + i);
            item.put("materialName", "物料" + i);
            item.put("itemQty", 10 * i);
            items.add(item);
        }
        data.put("items", items);
        System.out.println("原始数据:" + data.toJSONString());
        // 定义映射规则
        Map<String, String> rules = new HashMap<>();
        rules.put("orderNumber", "code");
        rules.put("materialCode", "matnr");
        rules.put("materialName", "maktx");
        rules.put("itemQty", "qty");
        // 执行替换(包括数组中的所有对象)
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules);
        System.out.println("替换结果:" + result.toJSONString());
        System.out.println();
    }
    /**
     * 测试4:复杂结构(嵌套+数组+多层)
     */
    private static void testComplexStructure() {
        System.out.println("【测试4:复杂结构】");
        // 构造复杂结构
        JSONObject data = new JSONObject();
        data.put("orderNumber", "PO004");
        data.put("orderStatus", "PENDING");
        // 客户信息
        JSONObject customer = new JSONObject();
        customer.put("customerCode", "CUST001");
        customer.put("customerName", "北京公司");
        data.put("customer", customer);
        // 订单明细数组
        JSONArray items = new JSONArray();
        for (int i = 1; i <= 2; i++) {
            JSONObject item = new JSONObject();
            item.put("itemNo", i);
            item.put("materialCode", "MAT00" + i);
            item.put("materialName", "物料" + i);
            item.put("itemQty", 50 + i * 10);
            // 仓库信息(嵌套在明细中)
            JSONObject warehouse = new JSONObject();
            warehouse.put("warehouseCode", "WH0" + i);
            warehouse.put("locationCode", "LOC-A-0" + i);
            item.put("warehouse", warehouse);
            items.add(item);
        }
        data.put("items", items);
        System.out.println("原始数据:" + data.toJSONString());
        // 定义完整的映射规则
        Map<String, String> rules = new HashMap<>();
        rules.put("orderNumber", "code");
        rules.put("orderStatus", "exceStatus");
        rules.put("customerCode", "custCode");
        rules.put("customerName", "custName");
        rules.put("materialCode", "matnr");
        rules.put("materialName", "maktx");
        rules.put("itemQty", "qty");
        rules.put("warehouseCode", "whCode");
        rules.put("locationCode", "locCode");
        // 执行递归替换
        JSONObject result = ParamsMapUtils.replaceJsonKeys(data, rules);
        System.out.println("替换结果:" + result.toJSONString());
        System.out.println();
    }
}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/utils/ParamsMapUtils.java
New file
@@ -0,0 +1,273 @@
package com.vincent.rsf.openApi.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.vincent.rsf.openApi.entity.app.ApiFunction;
import com.vincent.rsf.openApi.entity.app.ApiMap;
import com.vincent.rsf.openApi.entity.app.App;
import com.vincent.rsf.openApi.service.ApiMapService;
import com.vincent.rsf.openApi.service.ApiFunctionService;
import com.vincent.rsf.openApi.service.AppService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.utils.StringUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 接口字段动态映射工具类
 * 支持从数据库加载映射配置,并提供字段转换功能
 *
 * @author vincent
 * @since 2026-01-04
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ParamsMapUtils {
    private final AppService appService;
    private final ApiFunctionService functionService;
    private final ApiMapService mapService;
    // 缓存到内存
    public static List<App> APPS = new ArrayList<>();
    public static List<ApiFunction> FUNCTIONS = new ArrayList<>();
    public static List<ApiMap> ATTRIBUTE_MAPS = new ArrayList<>();
    /**
     * 应用完全启动后自动加载配置
     * 使用 ApplicationReadyEvent 确保 Spring 容器完全初始化
     */
    @EventListener(ApplicationReadyEvent.class)
    public void init() {
        log.info("=============== 开始加载接口映射配置 ===============");
        try {
            appService.refreshCache();
            functionService.refreshCache();
            mapService.refreshCache();
            log.info("接口映射配置加载完成 - 应用数:{}, 功能数:{}, 映射规则数:{}",
                    APPS.size(), FUNCTIONS.size(), ATTRIBUTE_MAPS.size());
        } catch (Exception e) {
            log.error("接口映射配置加载失败", e);
        }
    }
    /**
     * 手动刷新所有缓存
     */
    public void refreshAll() {
        log.info("手动刷新所有映射缓存...");
        appService.refreshCache();
        functionService.refreshCache();
        mapService.refreshCache();
    }
    /**
     * 执行字段映射转换
     *
     * @param appId 应用ID
     * @param funcId 功能ID
     * @param params 原始参数
     * @return 转换后的参数
     */
    public static JSONObject apiMaps(String appId, String funcId, JSONObject params) {
        if (params == null || params.isEmpty()) {
            return params;
        }
        // 1、获取映射表
        List<ApiMap> maps = ATTRIBUTE_MAPS.stream()
                .filter(map -> map.getAppId().equals(appId) && map.getFuncId().equals(funcId))
                .toList();
        if (maps.isEmpty()) {
            log.debug("未找到映射配置 - appId:{}, funcId:{}", appId, funcId);
            return params;
        }
        // 2、构建映射规则Map
        Map<String, String> mappingRules = new HashMap<>();
        for (ApiMap map : maps) {
            String sourceAttribute = map.getSourceAttribute();
            String targetAttribute = map.getTargetAttribute();
            if (!StringUtils.isBlank(sourceAttribute) && !StringUtils.isBlank(targetAttribute)) {
                mappingRules.put(sourceAttribute, targetAttribute);
            }
        }
        // 3、递归替换所有层级的属性名称
        JSONObject result = replaceKeysRecursive(params, mappingRules);
        return result;
    }
    /**
     * 递归替换JSON所有层级的属性名称
     * 支持嵌套对象和数组的深度遍历
     *
     * @param json 原始JSON对象
     * @param mappingRules 映射规则 (源字段名 -> 目标字段名)
     * @return 替换后的JSON对象
     */
    public static JSONObject replaceKeysRecursive(JSONObject json, Map<String, String> mappingRules) {
        if (json == null || json.isEmpty()) {
            return json;
        }
        JSONObject result = new JSONObject();
        for (String key : json.keySet()) {
            Object value = json.get(key);
            // 确定新的键名(如果有映射规则则使用映射后的名称)
            String newKey = mappingRules.getOrDefault(key, key);
            if (value instanceof JSONObject) {
                // 递归处理嵌套对象
                JSONObject nestedResult = replaceKeysRecursive((JSONObject) value, mappingRules);
                result.put(newKey, nestedResult);
                log.debug("替换对象字段: {} -> {}", key, newKey);
            } else if (value instanceof JSONArray) {
                // 递归处理数组
                JSONArray arrayResult = replaceKeysInArray((JSONArray) value, mappingRules);
                result.put(newKey, arrayResult);
                log.debug("替换数组字段: {} -> {}", key, newKey);
            } else {
                // 普通值直接赋值
                Object convertedValue = convertValue(value, newKey);
                result.put(newKey, convertedValue);
                if (!key.equals(newKey)) {
                    log.debug("替换字段: {} -> {} (值: {})", key, newKey, convertedValue);
                }
            }
        }
        return result;
    }
    /**
     * 递归处理JSON数组中的所有元素
     *
     * @param array 原始JSON数组
     * @param mappingRules 映射规则
     * @return 处理后的JSON数组
     */
    private static JSONArray replaceKeysInArray(JSONArray array, Map<String, String> mappingRules) {
        if (array == null || array.isEmpty()) {
            return array;
        }
        JSONArray result = new JSONArray();
        for (int i = 0; i < array.size(); i++) {
            Object element = array.get(i);
            if (element instanceof JSONObject) {
                // 数组元素是对象,递归处理
                JSONObject replacedObject = replaceKeysRecursive((JSONObject) element, mappingRules);
                result.add(replacedObject);
            } else if (element instanceof JSONArray) {
                // 数组元素是数组,递归处理
                JSONArray replacedArray = replaceKeysInArray((JSONArray) element, mappingRules);
                result.add(replacedArray);
            } else {
                // 基本类型,直接添加
                result.add(element);
            }
        }
        return result;
    }
    /**
     * 通用的JSON属性名替换方法(对外提供)
     * 可以直接传入映射规则进行替换
     *
     * @param json 原始JSON对象
     * @param mappingRules 映射规则 Map<源字段名, 目标字段名>
     * @return 替换后的JSON对象
     */
    public static JSONObject replaceJsonKeys(JSONObject json, Map<String, String> mappingRules) {
        return replaceKeysRecursive(json, mappingRules);
    }
    /**
     * 通用的JSON数组属性名替换方法(对外提供)
     *
     * @param array 原始JSON数组
     * @param mappingRules 映射规则 Map<源字段名, 目标字段名>
     * @return 替换后的JSON数组
     */
    public static JSONArray replaceJsonArrayKeys(JSONArray array, Map<String, String> mappingRules) {
        return replaceKeysInArray(array, mappingRules);
    }
    /**
     * 值类型转换(可扩展)
     *
     * @param value 原始值
     * @param targetField 目标字段名
     * @return 转换后的值
     */
    private static Object convertValue(Object value, String targetField) {
        if (value == null) {
            return null;
        }
        // 这里可以根据需要添加更多类型转换逻辑
        // 例如:日期格式转换、数字精度转换等
        // 示例:如果字段名包含amount、price等,转换为BigDecimal
        String lowerField = targetField.toLowerCase();
        if ((lowerField.contains("amount") || lowerField.contains("price")
                || lowerField.contains("qty")) && value instanceof String) {
            try {
                return new BigDecimal(value.toString());
            } catch (Exception e) {
                log.warn("数字转换失败: {} -> {}", value, targetField);
                return value;
            }
        }
        return value;
    }
    /**
     * 获取应用信息
     *
     * @param appId 应用ID
     * @return 应用信息
     */
    public static App getApp(String appId) {
        return APPS.stream()
                .filter(app -> app.getId().equals(appId))
                .findFirst()
                .orElse(null);
    }
    /**
     * 获取功能信息
     *
     * @param funcId 功能ID
     * @return 功能信息
     */
    public static ApiFunction getFunction(String funcId) {
        return FUNCTIONS.stream()
                .filter(func -> func.getId().equals(funcId))
                .findFirst()
                .orElse(null);
    }
}
rsf-open-api/src/main/resources/logback-spring.xml
New file
@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被添加到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <!--    <property name="log.path" value="./emp-log"/>-->
    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 配置属性 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!--输出到控制台的appender-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--日志级别过滤器-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!--日志过滤级别-->
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--log输出文件路径-->
    <springProperty scope="context" name="log.path" source="logging.file.path"/>
    <!--日志文件路径属性-->
    <property name="logback.logdir" value="${log.path}"/>
    <!-- level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${logback.logdir}/log_debug.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 指定日志记录器的拆分归档策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${logback.logdir}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--日志级过滤规则-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--日志过滤级别-->
            <level>debug</level>
            <!--超过过滤级别的策略-->
            <onMatch>ACCEPT</onMatch>
            <!--未超过过滤级别的策略-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--  level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${logback.logdir}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 指定日志记录器的拆分归档策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${logback.logdir}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--日志级过滤规则-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--日志过滤级别-->
            <level>info</level>
            <!--超过过滤级别的策略-->
            <onMatch>ACCEPT</onMatch>
            <!--未超过过滤级别的策略-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--  level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${logback.logdir}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 指定日志记录器的拆分归档策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logback.logdir}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--日志级过滤规则-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--日志过滤级别-->
            <level>warn</level>
            <!--超过过滤级别的策略-->
            <onMatch>ACCEPT</onMatch>
            <!--未超过过滤级别的策略-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--  level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${logback.logdir}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!--指定日志记录器的拆分归档策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logback.logdir}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!--日志级过滤规则-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--日志过滤级别-->
            <level>ERROR</level>
            <!--超过过滤级别的策略-->
            <onMatch>ACCEPT</onMatch>
            <!--未超过过滤级别的策略-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <!--<logger name="org.springframework.web" level="info"/>-->
    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
     -->
    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.lg.emp.controller" level="error"/>
    </springProfile>
    <!--root logger 配置    -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">-->
    <!--<root level="info">-->
    <!--<appender-ref ref="CONSOLE" />-->
    <!--<appender-ref ref="DEBUG_FILE" />-->
    <!--<appender-ref ref="INFO_FILE" />-->
    <!--<appender-ref ref="ERROR_FILE" />-->
    <!--<appender-ref ref="WARN_FILE" />-->
    <!--</root>-->
    <!--</springProfile>-->
</configuration>