原创-低代码平台后端搭建-v1.0

前言

        最近接触了挺多低代码相关的系统,发现了许多对我这个菜鸡来说新奇的架构,抽空研究了一些,决定开一篇我的第一个系列——走进低代码平台后端。

        这个系列会长期更新(指编辑已发布的)和连载(因为本人目前也只是刚看出一点门道,之后会利用业余时间慢慢研究总结),预计会是一个较大的工程,希望能在半年或一年内完整落幕。

        文章中用到的代码都是本人一点一点敲出来的,或用gpt生成,绝不是拿现有项目直接拷贝而得。目前暂时考虑到2.x版本,1.x版本是串行执行,2.x会升级到并行执行。等日后项目完善的时候可能会考虑发布源码压缩包,也可能不会。

        由于创建项目的时候spring官网(https://start.spring.io/)已经不支持Java8和springboot2.x了,虽然可以用其他办法下载,但想着正好与时俱进一波,于是这个项目的jdk版本用的是Java21和springboot3.1.7,后续可能有一些不重要的代码会和java版本有关,比如http相关的代码是用到了11之后的版本。

版本1.0——项目雏形(简单版)

        首先想清楚我们这个低代码平台要做什么?和大多数低代码平台一样,通过用户在前端选择不同的组件,填写对应的参数,得到一个组件流,可以是按钮填写表单的形式,也可以是用“拖拉拽”方式生成的,后者需要前端的参与度会多一些,以及前后端的一些参数约定也会更复杂。点击运行或调用接口运行这个组件流,就可以执行相应的逻辑,而这个组件的运行原理则是由低代码平台控制。

        如果用户需要平台能有更多的组件供选择,或需要组件能迭代升级一些个性化功能,就在后端项目里新加一个类即可,所以这篇1.0版本就是先简单搭建一个可以运行和实现上述功能的系统。

系统分析

        (由于懒就不画图写分析了,以后可能会补上但可能性不大)

  1. 这个系统需要用很低的代价去新增或修改组件,因此组件需要是一个通用的概念、定义;
  2. 为了方便管理(约定传参、统一执行逻辑),组件需要有统一的出参和入参约束;
  3. 因为用户可以随意选择使用哪些组件、修改组件运行的顺序,因此组件的运行接口也需要是一个统一的接口。

        考虑上述3点,第一点的结论是组件就是这个系统的一个抽象对象,可以划分的实体表先不总结(我还没理清楚需要有哪些表,以后补充);第二点得到自定义注解可以很好的完成这个需求;第三点可以得出每个组件类需要继承一个公共的接口或抽象类,由接口去定义执行的方法和出入参。

搭建系统

        经过上述简单分析,可以开始创建项目了,这里我创建了一个名为low-code的项目,层级结构是这样的:

core

在core包下面存放项目的核心代码,其中暂时创建了4个包。

entity

entity包里面存放实体类,相关代码如下:

package com.example.lowcode.core.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Map;

/**
 * @author llxzdmd
 * @version ComponentInfo.java, v 0.1 2024年01月02日 19:02 llxzdmd
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ComponentInfo implements Serializable {
    private Long id;
    private Long flowId;
    private Long startTime;
    private Long endTime;
    private Map<String, Object> inputs;
    private Map<String, Object> outputs;
}
package com.example.lowcode.core.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * @author llxzdmd
 * @version ComponentMetaInfo.java, v 0.1 2024年01月03日 19:49 llxzdmd
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ComponentMetaInfo implements Serializable {
    String name;
    String desc;
    List<Object> inputParam;
    List<Object> outputParam;
    String className;
    Long addTime;
    Long updateTime;
}

framework

framework包下面放构成这个架构的重要代码,目前只有3个类。

package com.example.lowcode.core.framework;

import com.example.lowcode.core.entity.ComponentInfo;

/**
 * @author llxzdmd
 * @version IComponent.java, v 0.1 2024年01月02日 19:00 llxzdmd
 */
public interface ComponentInterface {
    Object execute (ComponentInfo componentInfo) throws Exception;
}
package com.example.lowcode.core.framework;

import com.example.lowcode.core.entity.ComponentInfo;

import java.util.Map;

/**
 * @author llxzdmd
 * @version AbstractComponent.java, v 0.1 2024年01月02日 19:34 llxzdmd
 */
public class AbstractComponent implements ComponentInterface{
    @Override
    public Map<String, Object> execute(ComponentInfo componentInfo) throws Exception {
        return null;
    }
}

 这里创建抽象类的原因是,之后可能会扩展或重载多个执行方法,让组件继承抽象类而不是实现接口,可以避免重写没用的执行方法。

package com.example.lowcode.core.framework;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author llxzdmd
 * @version BeanUtil.java, v 0.1 2024年01月03日 19:45 llxzdmd
 */
@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    public static AbstractComponent getComponent(String beanName) {
        return (AbstractComponent) applicationContext.getBean(beanName);
    }

    @Override
    public void setApplicationContext (ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }
}

model

model下面放一些模型类

package com.example.lowcode.core.model;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * 组件定义
 * 
 * @author llxzdmd
 * @version ComponentDefinition.java, v 0.1 2024年01月02日 17:24 llxzdmd
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ComponentDefinition {
    String name();

    ComponentTypeEnum type();

    String desc();
}
package com.example.lowcode.core.model;

import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

/**
 * 组件类型枚举
 * 
 * @author llxzdmd
 * @version ModelEnum.java, v 0.1 2024年01月02日 17:05 llxzdmd
 */
@Getter
public enum ComponentTypeEnum {
    NONE(0),
    INPUT(1),
    OUTPUT(2),
    DATA_QUERY(3),
    SERVICE_CALL(4),
    FILTER(5),
    ;

    private final int type;

    ComponentTypeEnum(int type){
        this.type = type;
    }

    private static Map<Integer, ComponentTypeEnum> componentTypeMap = null;

    static {
        initComponentTypeMap();
    }

    private static void initComponentTypeMap() {
        componentTypeMap = new HashMap<>();
        for(ComponentTypeEnum typeEnum: ComponentTypeEnum.values()) {
            componentTypeMap.put(typeEnum.getType(), typeEnum);
        }
    }

    public static ComponentTypeEnum transToEnum(int componentType) {
        if(componentTypeMap.get(componentType) != null) {
            return componentTypeMap.get(componentType);
        }
        return NONE;
    }
}
package com.example.lowcode.core.model;

import java.lang.annotation.*;

/**
 * 组件入参定义
 *
 * @author llxzdmd
 * @version InputParamDefinition.java, v 0.1 2024年01月02日 17:32 llxzdmd
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InputParamDefinition {
    Param[] value () default @Param();
}
package com.example.lowcode.core.model;

import java.lang.annotation.*;

/**
 * 组件出参定义
 *
 * @author llxzdmd
 * @version OutputParamDefinition.java, v 0.1 2024年01月02日 17:47 llxzdmd
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OutputParamDefinition {
    Param[] value () default @Param();
}
package com.example.lowcode.core.model;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 组件出入参详细参数
 * 
 * @author llxzdmd
 * @version Param.java, v 0.1 2024年01月02日 17:33 llxzdmd
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {
    String name () default "";
    String desc () default "";
    ParamTypeEnum type () default ParamTypeEnum.STRING;
    boolean required () default false;
}
package com.example.lowcode.core.model;

import lombok.Getter;

import java.util.List;
import java.util.Map;

/**
 * 组件可以处理的参数的类型
 * 
 * @author llxzdmd
 * @version ParamTypeEnum.java, v 0.1 2024年01月02日 17:45 llxzdmd
 */
@Getter
public enum ParamTypeEnum {
    STRING("STRING", String.class),
    JSON("JSON", String.class),
    INT("INT", Integer.class),
    BOOLEAN("BOOLEAN", Boolean.class),
    DOUBLE("DOUBLE", Double.class),
    LIST("LIST", List.class),
    MAP("MAP", Map.class),
    ;

    private final String type;

    private final Class<?> typeClass;

    ParamTypeEnum (String type, Class<?> typeClass) {
        this.type = type;
        this.typeClass = typeClass;
    }

    public static ParamTypeEnum fromString (final String type) {
        for (ParamTypeEnum value : ParamTypeEnum.values()) {
            if (value.getType().equalsIgnoreCase(type)) {
                return value;
            }
        }
        return STRING;
    }
}

service

service类中放一些运行组件的接口方法

package com.example.lowcode.core.service;

import com.example.lowcode.core.entity.ComponentInfo;
import com.example.lowcode.core.entity.ComponentMetaInfo;

import java.util.List;
import java.util.Map;

/**
 * @author llxzdmd
 * @version RunFlowService.java, v 0.1 2024年01月03日 11:19 llxzdmd
 */
public interface RunService {

    Map<String, Object> runFlowV1(List<ComponentMetaInfo> metaInfoList, Map<String, ComponentInfo> inputParams);
}
package com.example.lowcode.core.service;

import com.example.lowcode.core.entity.ComponentInfo;
import com.example.lowcode.core.entity.ComponentMetaInfo;
import com.example.lowcode.core.framework.AbstractComponent;
import com.example.lowcode.core.framework.SpringUtil;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author llxzdmd
 * @version RunServiceImpl.java, v 0.1 2024年01月03日 11:20 llxzdmd
 */
@Service
public class RunServiceImpl implements RunService {

    @Override
    public Map<String, Object> runFlowV1(List<ComponentMetaInfo> metaInfoList, Map<String, ComponentInfo> inputParams) {
        List<Map<String, Object>> resultList = new ArrayList<>();
        metaInfoList.forEach(componentInfo -> {
            final Class<?> aClass;
            try {
                aClass = Class.forName(componentInfo.getClassName());
            } catch (ClassNotFoundException e) {
                System.out.println("组件+" + componentInfo.getClassName() + "不存在");
                throw new RuntimeException(e);
            }
            AbstractComponent abstractComponent = (AbstractComponent) SpringUtil.getBean(aClass);
            try {
                Map<String, Object> result = abstractComponent.execute(inputParams.get(componentInfo.getName()));
                resultList.add(result);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

        return resultList.get(resultList.size() - 1);
    }
}

        runFlowV1方法的大致逻辑是:根据入参的组件信息列表,顺序遍历得到每个组件的类,然后获取到抽象类(或者接口)的对象,执行运行的方法。最后返回最后一个组件的运行结果。

dao

mock

因为时间关系暂时没有创建MySQL表,因此很多实体数据可以用mock的形式创建,仿照是从MySQL中查到的数据。

package com.example.lowcode.dao.mock;

import com.example.lowcode.component.DistinctFilter;
import com.example.lowcode.component.PageFilter;
import com.example.lowcode.core.entity.ComponentMetaInfo;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author llxzdmd
 * @version ComponentMetaInfoDO.java, v 0.1 2024年01月04日 10:08 llxzdmd
 */
public class ComponentMetaInfoDO {

    /**
     * 假设这是ComponentMetaInfo表中存放的全部数据
     *
     * @return
     */
    public static List<ComponentMetaInfo> mockData() {
        List<ComponentMetaInfo> metaInfoList = new ArrayList<>();

        ComponentMetaInfo componentMetaInfo1 = new ComponentMetaInfo();
        componentMetaInfo1.setName("DistinctFilter");
        componentMetaInfo1.setClassName(DistinctFilter.class.getName());

        ComponentMetaInfo componentMetaInfo2 = new ComponentMetaInfo();
        componentMetaInfo2.setName("PageFilter");
        componentMetaInfo2.setClassName(PageFilter.class.getName());
        metaInfoList.add(componentMetaInfo1);
        metaInfoList.add(componentMetaInfo2);
        return metaInfoList;
    }

    /**
     * 假设这个是查找的sql接口
     * 
     * @param componentName
     * @return
     */
    public static List<ComponentMetaInfo> batchQueryByName(Collection<String> componentName) {
        Map<String, ComponentMetaInfo> nameInfoMap =
                mockData().stream().collect(Collectors.toMap(ComponentMetaInfo::getName, e -> e));
        return componentName.stream().map(nameInfoMap::get).collect(Collectors.toList());
    }
}

util

存放一些工具类

package com.example.lowcode.util;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @author llxzdmd
 * @version HttpUtils.java, v 0.1 2024年01月02日 19:45 llxzdmd
 */
public class HttpUtils {

    /**
     * 发送get请求,gpt生成
     *
     * @param url
     * @param headers
     * @return
     * @throws Exception
     */
    public static String sendGetRequest(String url, Map<String, String> headers) throws Exception {
        // 初始化URL对象
        HttpClient client = HttpClient.newHttpClient();

        // 创建HttpRequest
        HttpRequest.Builder builder = HttpRequest.newBuilder();
        builder.uri(URI.create(url));
        headers.forEach(builder::header);
        HttpRequest request = builder.build();

        // 发送请求并获取响应
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        // 返回响应内容
        return response.body();
    }

    /**
     * 发送post请求,gpt生成
     *
     * @param url
     * @param json
     * @param headers
     * @return
     * @throws Exception
     */
    public static String sendPostRequest(String url, String json, Map<String, String> headers) throws Exception {
        // 创建HttpClient实例
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .build();

        // 创建HttpRequest,并设置URL、headers和POST请求的body
        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8));

        // 添加headers到HttpRequest中
        headers.forEach(builder::header);

        // 构建HttpRequest对象
        HttpRequest request = builder.build();

        // 发送POST请求,并获取响应
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        // 返回响应内容
        return response.body();
    }
}

component

在这个包下面创建各种组件。

暂时能想到的组件有:MySQL组件、Redis组件、es组件、Dubbo组件、http组件、去重组件、分页组件等。由于组件是可以随意扩展的,不是这个系统的重点,第一篇就先只创建了3个组件。

package com.example.lowcode.component;

import com.example.lowcode.core.model.*;
import com.example.lowcode.core.entity.ComponentInfo;
import com.example.lowcode.core.framework.*;
import com.google.common.collect.Sets;

import java.util.*;

/**
 * @author llxzdmd
 * @version DistinctFilter.java, v 0.1 2024年01月02日 19:39 llxzdmd
 */
@ComponentDefinition(name = "DistinctFilter", type = ComponentTypeEnum.FILTER, desc = "去重过滤器")
@InputParamDefinition({
        @Param(name = "list", desc = "需要去重的集合", type = ParamTypeEnum.LIST, required = true),
        @Param(name = "params", desc = "对象集合的去重字段", type = ParamTypeEnum.LIST, required = false),
        @Param(name = "paramTypes", desc = "去重字段的类型", type = ParamTypeEnum.LIST, required = false)
})
@OutputParamDefinition({@Param(name = "result", desc = "去重过滤器返回结果", required = true)})
public class DistinctFilter extends AbstractComponent {

    @Override
    public Map<String, Object> execute(ComponentInfo componentInfo) throws Exception {
        // 先随便写,只考虑简单类型
        List list = (List) componentInfo.getInputs().get("list");
        Set set = Sets.newHashSet(list);
        List difference = findListDifference(list, set);
        System.out.println("被去重的元素有:"+difference);

        HashMap<String, Object> result = new HashMap<>();
        result.put("result", set);
        return result;
    }

    public static List findListDifference(List list, Set set) {
        // 创建List的一个副本
        List listCopy = new ArrayList<>(list);
        // 遍历Set,尝试从List副本中移除元素
        for (Object element : set) {
            listCopy.remove(element);
        }
        // 返回剩余的元素,即为多出的元素
        return listCopy;
    }
}
package com.example.lowcode.component;

import com.alibaba.fastjson.JSON;
import com.example.lowcode.core.model.*;
import com.example.lowcode.core.entity.ComponentInfo;
import com.example.lowcode.core.framework.*;
import com.example.lowcode.util.HttpUtils;
import com.google.common.collect.Maps;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author llxzdmd
 * @version HttpClient.java, v 0.1 2024年01月02日 18:54 llxzdmd
 */
@ComponentDefinition(name = "HttpClient", type = ComponentTypeEnum.SERVICE_CALL, desc = "http组件")
@InputParamDefinition({
        @Param(name = "url", desc = "http请求地址", type = ParamTypeEnum.STRING, required = true),
        @Param(name = "method", desc = "http请求方法", type = ParamTypeEnum.STRING, required = true),
        @Param(name = "params", desc = "接口入参", type = ParamTypeEnum.MAP, required = false),
        @Param(name = "headers", desc = "http请求headers", type = ParamTypeEnum.MAP, required = false)
})
@OutputParamDefinition({@Param(name = "result", desc = "http接口返回结果", required = true)})
public class HttpClient extends AbstractComponent {
    @Override
    public Map<String, Object> execute(ComponentInfo componentInfo) throws Exception {
        // 暂时忽略判空
        String url = (String) componentInfo.getInputs().get("url");
        String method = (String) componentInfo.getInputs().get("method");
        Map<String, Object> paramMap = (Map<String, Object>) componentInfo.getInputs().get("params");
        Map<String, String> headerMap = (Map<String, String>) componentInfo.getInputs().get("headers");

        String httpResult;
        if (Objects.equals("post", method)) {
            httpResult = HttpUtils.sendPostRequest(url, JSON.toJSONString(paramMap), headerMap);
        } else {
            httpResult = HttpUtils.sendGetRequest(url, headerMap);
        }

        HashMap<String, Object> result = Maps.newHashMap();
        result.put("result", httpResult);
        return result;
    }

}
package com.example.lowcode.component;

import com.example.lowcode.core.model.*;
import com.example.lowcode.core.entity.ComponentInfo;
import com.example.lowcode.core.framework.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author llxzdmd
 * @version PageFilter.java, v 0.1 2024年01月02日 19:39 llxzdmd
 */
@ComponentDefinition(name = "PageFilter", type = ComponentTypeEnum.FILTER, desc = "分页过滤器")
@InputParamDefinition({
        @Param(name = "list", desc = "需要分页的集合", type = ParamTypeEnum.LIST, required = true),
        @Param(name = "pageNum", desc = "起始在第几页,默认1", type = ParamTypeEnum.INT, required = false),
        @Param(name = "pageSize", desc = "分页大小,默认10", type = ParamTypeEnum.INT, required = false)
})
@OutputParamDefinition({@Param(name = "result", desc = "分页过滤器返回结果", required = true)})
public class PageFilter extends AbstractComponent {

    @Override
    public Map<String, Object> execute(ComponentInfo componentInfo) throws Exception {
        List list = (List) componentInfo.getInputs().get("list");
        int pageNum = componentInfo.getInputs().get("pageNum") == null ?
                1 : (int) componentInfo.getInputs().get("pageNum");
        int pageSize = componentInfo.getInputs().get("pageSize") == null ?
                10 : (int) componentInfo.getInputs().get("pageSize");

        HashMap<String, Object> result = new HashMap<>();
        if (list == null || pageNum <= 0 || pageSize <= 0) {
            result.put("result", Collections.emptyList());
            return result;
        }
        int fromIndex = (pageNum - 1) * pageSize;
        if (fromIndex >= list.size()) {
            // 请求的页码超出了列表的范围,返回空列表
            result.put("result", Collections.emptyList());
            return result;
        }
        int toIndex = Math.min(fromIndex + pageSize, list.size());
        result.put("result", list.subList(fromIndex, toIndex));
        return result;
    }
}

test

service

package com.example.lowcode.service;

import com.example.lowcode.core.entity.ComponentInfo;
import com.example.lowcode.core.entity.ComponentMetaInfo;
import com.example.lowcode.core.service.RunService;
import com.example.lowcode.dao.mock.ComponentMetaInfoDO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author llxzdmd
 * @version RunServiceTest.java, v 0.1 2024年01月03日 11:29 llxzdmd
 */
@SpringBootTest
public class RunServiceTest {

    @Autowired
    private RunService runService;

    @Test
    public void runFlowV1Test() {
        Map<String, ComponentInfo> inputParams = new HashMap<>();

        // 去重过滤器填参数
        ComponentInfo componentInfo1 = new ComponentInfo();
        componentInfo1.setId(1L);
        componentInfo1.setFlowId(1L);
        List<String> stringList = new ArrayList<>();
        stringList.add("a");
        stringList.add("b");
        stringList.add("c");
        stringList.add("d");
        stringList.add("e");
        stringList.add("f");
        stringList.add("g");
        stringList.add("c");
        stringList.add("a");
        Map<String, Object> inputMap1 = new HashMap<>();
        inputMap1.put("list", stringList);
        componentInfo1.setInputs(inputMap1);

        // 分页过滤器填参数
        ComponentInfo componentInfo2 = new ComponentInfo();
        componentInfo2.setId(2L);
        componentInfo2.setFlowId(1L);
        Map<String, Object> inputMap2 = new HashMap<>();
        inputMap2.put("list", stringList);
        inputMap2.put("pageNum", 2);
        inputMap2.put("pageSize", 3);
        componentInfo2.setInputs(inputMap2);

        // 整个流填参数
        inputParams.put("DistinctFilter", componentInfo1);
        inputParams.put("PageFilter", componentInfo2);

        // 根据入参
        List<ComponentMetaInfo> metaInfoList = ComponentMetaInfoDO.batchQueryByName(inputParams.keySet());
        Map<String, Object> result = runService.runFlowV1(metaInfoList, inputParams);
        System.out.println(result);
    }
}

        在这个测试类中,我们依次创建了两个组件:去重组件和分页组件。运行结果如下:

总结

        在这个系统1.0中,我们实现了可以任意扩展组件,让用户随意选择组件和执行顺序的核心逻辑。现在有一个小问题是运行组件流的入参不应该是组件元数据信息list,应当有一个实体表存放每个组件流的信息,比如有哪些组件、组件的顺序等。这个会在有空的时候改掉,然后开始准备看v1.1需要优化些什么。

对于vue-element-admin后端改造,你可以按照以下步骤进行操作: 1. 首先,你需要在服务器上使用Node.js启动vue-element-admin,默认是9258端口。这可以通过在命令行中进入vue-element-admin项目目录,然后运行`npm run dev`命令来实现。 2. 接下来,你需要使用Python的Flask框架来启动后端服务,默认是5000端口。你可以创建一个Flask应用,并在其中定义后端的API接口。 3. 修改vue-element-admin中的代码,将其后端交互功能指向Flask提供的服务。你可以在vue-element-admin项目中的`src/api`目录下找到与后端交互的文件,例如`user.js`。在这些文件中,你可以修改API请求的URL,将其指向Flask后端的对应接口。 4. 如果你需要将vue-element-admin中的模拟数据接口(mock)改为真实后端接口,你可以在vue-element-admin项目中的`src/mock`目录下找到对应的文件,例如`user.js`。在这些文件中,你可以修改接口的URL,将其指向Flask后端的对应接口。 5. 在修改后端交互功能和模拟数据接口后,你可能需要处理跨域访问的问题。由于vue-element-admin默认运行在9258端口,而Flask后端运行在5000端口,你需要在Flask应用中添加跨域访问的配置,以允许vue-element-admin能够跨域请求Flask后端的接口。 总结起来,vue-element-admin后端改造的步骤包括启动vue-element-admin和Flask后端服务、修改前端代码中的后端交互功能和模拟数据接口、处理跨域访问的问题。通过这些步骤,你可以将vue-element-admin与Python的Flask框架进行整合,实现前后端的配合工作。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [vue-element-admin改用真实后端(python flask)数据的方法](https://blog.csdn.net/wangdandan01/article/details/103478357)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [vue-element-plus-admin整合后端实战——实现系统登录、缓存用户数据、实现动态路由](https://blog.csdn.net/seawaving/article/details/129766205)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值