Browse Source

[Feature-14214][Parameter] Support CRUD of project-level parameters (#14264)

Rick Cheng 1 year ago
parent
commit
1c935d901e

+ 169 - 0
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectParameterController.java

@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.api.controller;
+
+import static org.apache.dolphinscheduler.api.enums.Status.CREATE_PROJECT_PARAMETER_ERROR;
+import static org.apache.dolphinscheduler.api.enums.Status.DELETE_PROJECT_PARAMETER_ERROR;
+import static org.apache.dolphinscheduler.api.enums.Status.QUERY_PROJECT_PARAMETER_ERROR;
+import static org.apache.dolphinscheduler.api.enums.Status.UPDATE_PROJECT_PARAMETER_ERROR;
+
+import org.apache.dolphinscheduler.api.aspect.AccessLogAnnotation;
+import org.apache.dolphinscheduler.api.exceptions.ApiException;
+import org.apache.dolphinscheduler.api.service.ProjectParameterService;
+import org.apache.dolphinscheduler.api.utils.Result;
+import org.apache.dolphinscheduler.common.constants.Constants;
+import org.apache.dolphinscheduler.dao.entity.User;
+import org.apache.dolphinscheduler.plugin.task.api.utils.ParameterUtils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+@Tag(name = "PROJECT_PARAMETER_TAG")
+@RestController
+@RequestMapping("projects/{projectCode}/project-parameter")
+@Slf4j
+public class ProjectParameterController extends BaseController {
+
+    @Autowired
+    private ProjectParameterService projectParameterService;
+
+    @Operation(summary = "createProjectParameter", description = "CREATE_PROJECT_PARAMETER_NOTES")
+    @Parameters({
+            @Parameter(name = "projectParameterName", description = "PROJECT_PARAMETER_NAME", schema = @Schema(implementation = String.class)),
+            @Parameter(name = "projectParameterValue", description = "PROJECT_PARAMETER_VALUE", schema = @Schema(implementation = String.class))
+    })
+    @PostMapping()
+    @ResponseStatus(HttpStatus.CREATED)
+    @ApiException(CREATE_PROJECT_PARAMETER_ERROR)
+    @AccessLogAnnotation(ignoreRequestArgs = "loginUser")
+    public Result createProjectParameter(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                         @Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
+                                         @RequestParam("projectParameterName") String projectParameterName,
+                                         @RequestParam(value = "projectParameterValue") String projectParameterValue) {
+        return projectParameterService.createProjectParameter(loginUser, projectCode, projectParameterName,
+                projectParameterValue);
+    }
+
+    @Operation(summary = "updateProjectParameter", description = "UPDATE_PROJECT_PARAMETER_NOTES")
+    @Parameters({
+            @Parameter(name = "code", description = "PROJECT_PARAMETER_CODE", schema = @Schema(implementation = long.class, example = "123456")),
+            @Parameter(name = "projectParameterName", description = "PROJECT_PARAMETER_NAME", schema = @Schema(implementation = String.class)),
+            @Parameter(name = "projectParameterValue", description = "PROJECT_PARAMETER_VALUE", schema = @Schema(implementation = String.class)),
+    })
+    @PutMapping(value = "/{code}")
+    @ResponseStatus(HttpStatus.OK)
+    @ApiException(UPDATE_PROJECT_PARAMETER_ERROR)
+    @AccessLogAnnotation(ignoreRequestArgs = "loginUser")
+    public Result updateProjectParameter(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                         @Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
+                                         @PathVariable("code") Long code,
+                                         @RequestParam("projectParameterName") String projectParameterName,
+                                         @RequestParam(value = "projectParameterValue") String projectParameterValue) {
+        return projectParameterService.updateProjectParameter(loginUser, projectCode, code, projectParameterName,
+                projectParameterValue);
+    }
+
+    @Operation(summary = "deleteProjectParametersByCode", description = "DELETE_PROJECT_PARAMETER_NOTES")
+    @Parameters({
+            @Parameter(name = "code", description = "PROJECT_PARAMETER_CODE", required = true, schema = @Schema(implementation = String.class))
+    })
+    @PostMapping(value = "/delete")
+    @ResponseStatus(HttpStatus.OK)
+    @ApiException(DELETE_PROJECT_PARAMETER_ERROR)
+    @AccessLogAnnotation(ignoreRequestArgs = "loginUser")
+    public Result deleteProjectParametersByCode(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                                @Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
+                                                @RequestParam("code") long code) {
+
+        return projectParameterService.deleteProjectParametersByCode(loginUser, projectCode, code);
+    }
+
+    @Operation(summary = "batchDeleteProjectParametersByCodes", description = "DELETE_PROJECT_PARAMETER_NOTES")
+    @Parameters({
+            @Parameter(name = "codes", description = "PROJECT_PARAMETER_CODE", required = true, schema = @Schema(implementation = String.class))
+    })
+    @PostMapping(value = "/batch-delete")
+    @ResponseStatus(HttpStatus.OK)
+    @ApiException(DELETE_PROJECT_PARAMETER_ERROR)
+    @AccessLogAnnotation(ignoreRequestArgs = "loginUser")
+    public Result batchDeleteProjectParametersByCodes(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                                      @Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
+                                                      @RequestParam("codes") String codes) {
+
+        return projectParameterService.batchDeleteProjectParametersByCodes(loginUser, projectCode, codes);
+    }
+
+    @Operation(summary = "queryProjectParameterListPaging", description = "QUERY_PROJECT_PARAMETER_LIST_PAGING_NOTES")
+    @Parameters({
+            @Parameter(name = "searchVal", description = "SEARCH_VAL", required = false, schema = @Schema(implementation = String.class)),
+            @Parameter(name = "pageNo", description = "PAGE_NO", required = true, schema = @Schema(implementation = int.class, example = "1")),
+            @Parameter(name = "pageSize", description = "PAGE_SIZE", required = true, schema = @Schema(implementation = int.class, example = "10"))
+    })
+    @GetMapping()
+    @ResponseStatus(HttpStatus.OK)
+    @ApiException(QUERY_PROJECT_PARAMETER_ERROR)
+    @AccessLogAnnotation(ignoreRequestArgs = "loginUser")
+    public Result queryProjectParameterListPaging(
+                                                  @Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                                  @Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
+                                                  @RequestParam(value = "searchVal", required = false) String searchVal,
+                                                  @RequestParam("pageNo") Integer pageNo,
+                                                  @RequestParam("pageSize") Integer pageSize) {
+
+        Result result = checkPageParams(pageNo, pageSize);
+        if (!result.checkResult()) {
+            log.warn("Pagination parameters check failed, pageNo:{}, pageSize:{}", pageNo, pageSize);
+            return result;
+        }
+        searchVal = ParameterUtils.handleEscapes(searchVal);
+        return projectParameterService.queryProjectParameterListPaging(loginUser, projectCode, pageSize, pageNo,
+                searchVal);
+    }
+
+    @Operation(summary = "queryProjectParameterByCode", description = "QUERY_PROJECT_PARAMETER_NOTES")
+    @Parameters({
+            @Parameter(name = "code", description = "PROJECT_PARAMETER_CODE", schema = @Schema(implementation = long.class, example = "123456"))
+    })
+    @GetMapping(value = "/{code}")
+    @ResponseStatus(HttpStatus.OK)
+    @ApiException(QUERY_PROJECT_PARAMETER_ERROR)
+    @AccessLogAnnotation(ignoreRequestArgs = "loginUser")
+    public Result queryProjectParameterByCode(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                              @Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
+                                              @PathVariable("code") long code) {
+        return projectParameterService.queryProjectParameterByCode(loginUser, projectCode, code);
+    }
+
+}

+ 14 - 0
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java

@@ -273,6 +273,20 @@ public enum Status {
 
     TASK_INSTANCE_NOT_DYNAMIC_TASK(10213, "task instance {0} is not dynamic", "任务实例[{0}]不是Dynamic类型"),
 
+    CREATE_PROJECT_PARAMETER_ERROR(10214, "create project parameter error", "创建项目参数错误"),
+
+    UPDATE_PROJECT_PARAMETER_ERROR(10215, "update project parameter error", "更新项目参数错误"),
+
+    DELETE_PROJECT_PARAMETER_ERROR(10216, "delete project parameter error {0}", "删除项目参数错误 {0}"),
+
+    QUERY_PROJECT_PARAMETER_ERROR(10217, "query project parameter error", "查询项目参数错误"),
+
+    PROJECT_PARAMETER_ALREADY_EXISTS(10218, "project parameter {0} already exists", "项目参数[{0}]已存在"),
+
+    PROJECT_PARAMETER_NOT_EXISTS(10219, "project parameter {0} not exists", "项目参数[{0}]不存在"),
+
+    PROJECT_PARAMETER_CODE_EMPTY(10220, "project parameter code empty", "项目参数code为空"),
+
     UDF_FUNCTION_NOT_EXIST(20001, "UDF function not found", "UDF函数不存在"),
     UDF_FUNCTION_EXISTS(20002, "UDF function already exists", "UDF函数已存在"),
     RESOURCE_NOT_EXIST(20004, "resource not exist", "资源不存在"),

+ 39 - 0
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectParameterService.java

@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.api.service;
+
+import org.apache.dolphinscheduler.api.utils.Result;
+import org.apache.dolphinscheduler.dao.entity.User;
+
+public interface ProjectParameterService {
+
+    Result createProjectParameter(User loginUser, long projectCode, String projectParameterName,
+                                  String projectParameterValue);
+
+    Result updateProjectParameter(User loginUser, long projectCode, long code, String projectParameterName,
+                                  String projectParameterValue);
+
+    Result deleteProjectParametersByCode(User loginUser, long projectCode, long code);
+
+    Result batchDeleteProjectParametersByCodes(User loginUser, long projectCode, String codes);
+
+    Result queryProjectParameterListPaging(User loginUser, long projectCode, Integer pageSize, Integer pageNo,
+                                           String searchVal);
+
+    Result queryProjectParameterByCode(User loginUser, long projectCode, long code);
+}

+ 275 - 0
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectParameterServiceImpl.java

@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.api.service.impl;
+
+import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT;
+
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.api.exceptions.ServiceException;
+import org.apache.dolphinscheduler.api.service.ProjectParameterService;
+import org.apache.dolphinscheduler.api.service.ProjectService;
+import org.apache.dolphinscheduler.api.utils.PageInfo;
+import org.apache.dolphinscheduler.api.utils.Result;
+import org.apache.dolphinscheduler.common.constants.Constants;
+import org.apache.dolphinscheduler.common.utils.CodeGenerateUtils;
+import org.apache.dolphinscheduler.dao.entity.Project;
+import org.apache.dolphinscheduler.dao.entity.ProjectParameter;
+import org.apache.dolphinscheduler.dao.entity.User;
+import org.apache.dolphinscheduler.dao.mapper.ProjectMapper;
+import org.apache.dolphinscheduler.dao.mapper.ProjectParameterMapper;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.common.collect.Lists;
+
+@Service
+@Slf4j
+public class ProjectParameterServiceImpl extends BaseServiceImpl implements ProjectParameterService {
+
+    @Autowired
+    private ProjectParameterMapper projectParameterMapper;
+
+    @Autowired
+    private ProjectService projectService;
+
+    @Autowired
+    private ProjectMapper projectMapper;
+
+    @Override
+    @Transactional
+    public Result createProjectParameter(User loginUser, long projectCode, String projectParameterName,
+                                         String projectParameterValue) {
+        Result result = new Result();
+
+        // check if user have write perm for project
+        Project project = projectMapper.queryByCode(projectCode);
+        boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result);
+        if (!hasProjectAndWritePerm) {
+            return result;
+        }
+
+        ProjectParameter projectParameter = projectParameterMapper.queryByName(projectParameterName);
+        if (projectParameter != null) {
+            log.warn("ProjectParameter {} already exists.", projectParameter.getParamName());
+            putMsg(result, Status.PROJECT_PARAMETER_ALREADY_EXISTS, projectParameter.getParamName());
+            return result;
+        }
+
+        Date now = new Date();
+
+        try {
+            projectParameter = ProjectParameter
+                    .builder()
+                    .paramName(projectParameterName)
+                    .paramValue(projectParameterValue)
+                    .code(CodeGenerateUtils.getInstance().genCode())
+                    .projectCode(projectCode)
+                    .userId(loginUser.getId())
+                    .createTime(now)
+                    .updateTime(now)
+                    .build();
+        } catch (CodeGenerateUtils.CodeGenerateException e) {
+            log.error("Generate project parameter code error.", e);
+            putMsg(result, Status.CREATE_PROJECT_PARAMETER_ERROR);
+            return result;
+        }
+
+        if (projectParameterMapper.insert(projectParameter) > 0) {
+            log.info("Project parameter is created and id is :{}", projectParameter.getId());
+            result.setData(projectParameter);
+            putMsg(result, Status.SUCCESS);
+        } else {
+            log.error("Project parameter create error, projectName:{}.", projectParameter.getParamName());
+            putMsg(result, Status.CREATE_PROJECT_PARAMETER_ERROR);
+        }
+        return result;
+    }
+
+    @Override
+    public Result updateProjectParameter(User loginUser, long projectCode, long code, String projectParameterName,
+                                         String projectParameterValue) {
+        Result result = new Result();
+
+        // check if user have write perm for project
+        Project project = projectMapper.queryByCode(projectCode);
+        boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result);
+        if (!hasProjectAndWritePerm) {
+            return result;
+        }
+
+        ProjectParameter projectParameter = projectParameterMapper.queryByCode(code);
+        // check project parameter exists
+        if (projectParameter == null || projectCode != projectParameter.getProjectCode()) {
+            log.error("Project parameter does not exist, code:{}.", code);
+            putMsg(result, Status.PROJECT_PARAMETER_NOT_EXISTS, String.valueOf(code));
+            return result;
+        }
+
+        // check if project parameter name exists
+        ProjectParameter tempProjectParameter = projectParameterMapper.queryByName(projectParameterName);
+        if (tempProjectParameter != null) {
+            log.error("Project parameter name {} already exists", projectParameterName);
+            putMsg(result, Status.PROJECT_PARAMETER_ALREADY_EXISTS, projectParameterName);
+            return result;
+        }
+
+        projectParameter.setParamName(projectParameterName);
+        projectParameter.setParamValue(projectParameterValue);
+
+        if (projectParameterMapper.updateById(projectParameter) > 0) {
+            log.info("Project parameter is updated and id is :{}", projectParameter.getId());
+            result.setData(projectParameter);
+            putMsg(result, Status.SUCCESS);
+        } else {
+            log.error("Project parameter update error, {}.", projectParameterName);
+            putMsg(result, Status.UPDATE_PROJECT_PARAMETER_ERROR);
+        }
+        return result;
+    }
+
+    @Override
+    public Result deleteProjectParametersByCode(User loginUser, long projectCode, long code) {
+        Result result = new Result();
+
+        // check if user have write perm for project
+        Project project = projectMapper.queryByCode(projectCode);
+        boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result);
+        if (!hasProjectAndWritePerm) {
+            return result;
+        }
+
+        ProjectParameter projectParameter = projectParameterMapper.queryByCode(code);
+        // check project parameter exists
+        if (projectParameter == null || projectCode != projectParameter.getProjectCode()) {
+            log.error("Project parameter does not exist, code:{}.", code);
+            putMsg(result, Status.PROJECT_PARAMETER_NOT_EXISTS, String.valueOf(code));
+            return result;
+        }
+
+        // TODO: check project parameter is used by workflow
+
+        if (projectParameterMapper.deleteById(projectParameter.getId()) > 0) {
+            log.info("Project parameter is deleted and id is :{}.", projectParameter.getId());
+            result.setData(Boolean.TRUE);
+            putMsg(result, Status.SUCCESS);
+        } else {
+            log.error("Project parameter delete error, {}.", projectParameter.getParamName());
+            putMsg(result, Status.DELETE_PROJECT_PARAMETER_ERROR);
+        }
+        return result;
+    }
+
+    @Override
+    public Result batchDeleteProjectParametersByCodes(User loginUser, long projectCode, String codes) {
+        Result result = new Result();
+
+        if (StringUtils.isEmpty(codes)) {
+            log.error("Project parameter codes is empty, projectCode is {}.", projectCode);
+            putMsg(result, Status.PROJECT_PARAMETER_CODE_EMPTY);
+            return result;
+        }
+
+        Set<Long> requestCodeSet = Lists.newArrayList(codes.split(Constants.COMMA)).stream().map(Long::parseLong)
+                .collect(Collectors.toSet());
+        List<ProjectParameter> projectParameterList = projectParameterMapper.queryByCodes(requestCodeSet);
+        Set<Long> actualCodeSet =
+                projectParameterList.stream().map(ProjectParameter::getCode).collect(Collectors.toSet());
+        // requestCodeSet - actualCodeSet
+        Set<Long> diffCode =
+                requestCodeSet.stream().filter(code -> !actualCodeSet.contains(code)).collect(Collectors.toSet());
+
+        String diffCodeString = diffCode.stream().map(String::valueOf).collect(Collectors.joining(Constants.COMMA));
+        if (CollectionUtils.isNotEmpty(diffCode)) {
+            log.error("Project parameter does not exist, codes:{}.", diffCodeString);
+            throw new ServiceException(Status.PROJECT_PARAMETER_NOT_EXISTS, diffCodeString);
+        }
+
+        for (ProjectParameter projectParameter : projectParameterList) {
+            try {
+                this.deleteProjectParametersByCode(loginUser, projectCode, projectParameter.getCode());
+            } catch (Exception e) {
+                throw new ServiceException(Status.DELETE_PROJECT_PARAMETER_ERROR, e.getMessage());
+            }
+        }
+
+        putMsg(result, Status.SUCCESS);
+        return result;
+    }
+
+    @Override
+    public Result queryProjectParameterListPaging(User loginUser, long projectCode, Integer pageSize, Integer pageNo,
+                                                  String searchVal) {
+        Result result = new Result();
+
+        Project project = projectMapper.queryByCode(projectCode);
+        boolean hasProjectAndPerm = projectService.hasProjectAndPerm(loginUser, project, result, PROJECT);
+        if (!hasProjectAndPerm) {
+            return result;
+        }
+
+        PageInfo<ProjectParameter> pageInfo = new PageInfo<>(pageNo, pageSize);
+        Page<ProjectParameter> page = new Page<>(pageNo, pageSize);
+
+        IPage<ProjectParameter> iPage =
+                projectParameterMapper.queryProjectParameterListPaging(page, null, searchVal);
+
+        List<ProjectParameter> projectParameterList = iPage.getRecords();
+
+        pageInfo.setTotal((int) iPage.getTotal());
+        pageInfo.setTotalList(projectParameterList);
+        result.setData(pageInfo);
+        putMsg(result, Status.SUCCESS);
+        return result;
+    }
+
+    @Override
+    public Result queryProjectParameterByCode(User loginUser, long projectCode, long code) {
+        Result result = new Result();
+
+        Project project = projectMapper.queryByCode(projectCode);
+        boolean hasProjectAndPerm = projectService.hasProjectAndPerm(loginUser, project, result, PROJECT);
+        if (!hasProjectAndPerm) {
+            return result;
+        }
+
+        ProjectParameter projectParameter = projectParameterMapper.queryByCode(code);
+        if (projectParameter == null || projectCode != projectParameter.getProjectCode()) {
+            log.error("Project parameter does not exist, code:{}.", code);
+            putMsg(result, Status.PROJECT_PARAMETER_NOT_EXISTS, String.valueOf(code));
+            return result;
+        }
+
+        result.setData(projectParameter);
+        putMsg(result, Status.SUCCESS);
+        return result;
+    }
+}

+ 11 - 1
dolphinscheduler-api/src/main/resources/i18n/messages.properties

@@ -437,4 +437,14 @@ QUERY_WORKFLOW_LINEAGE_BY_NAME_NOTES=query workflow lineage by name
 QUERY_WORKFLOW_LINEAGE_BY_CODE_NOTE=query workflow lineage by code list
 QUERY_WORKFLOW_LINEAGE_NOTES=query workflow lineage
 VERIFY_TASK_CAN_DELETE=verify if task can be deleted
-WORKFLOW_TAG_V2=work flow lineage related operation (V2)
+WORKFLOW_TAG_V2=work flow lineage related operation (V2)
+
+PROJECT_PARAMETER_TAG=project parameter related operation
+CREATE_PROJECT_PARAMETER_NOTES=create project parameter
+PROJECT_PARAMETER_NAME=project parameter name
+PROJECT_PARAMETER_VALUE=project parameter value
+UPDATE_PROJECT_PARAMETER_NOTES=update project parameter
+PROJECT_PARAMETER_CODE=project parameter code
+DELETE_PROJECT_PARAMETER_NOTES=delete project parameter
+QUERY_PROJECT_PARAMETER_LIST_PAGING_NOTES=query project parameter list paging
+QUERY_PROJECT_PARAMETER_NOTES=query project parameter

+ 11 - 1
dolphinscheduler-api/src/main/resources/i18n/messages_en_US.properties

@@ -476,4 +476,14 @@ TASK_INSTANCE_STOP=stop task instance
 QUERY_WORKFLOW_LINEAGE_BY_CODE_NOTE=query workflow lineage by code list
 QUERY_WORKFLOW_LINEAGE_NOTES=query workflow lineage
 VERIFY_TASK_CAN_DELETE=verify if task can be deleted
-WORKFLOW_TAG_V2=work flow lineage related operation (V2)
+WORKFLOW_TAG_V2=work flow lineage related operation (V2)
+
+PROJECT_PARAMETER_TAG=project parameter related operation
+CREATE_PROJECT_PARAMETER_NOTES=create project parameter
+PROJECT_PARAMETER_NAME=project parameter name
+PROJECT_PARAMETER_VALUE=project parameter value
+UPDATE_PROJECT_PARAMETER_NOTES=update project parameter
+PROJECT_PARAMETER_CODE=project parameter code
+DELETE_PROJECT_PARAMETER_NOTES=delete project parameter
+QUERY_PROJECT_PARAMETER_LIST_PAGING_NOTES=query project parameter list paging
+QUERY_PROJECT_PARAMETER_NOTES=query project parameter

+ 11 - 1
dolphinscheduler-api/src/main/resources/i18n/messages_zh_CN.properties

@@ -473,4 +473,14 @@ TASK_INSTANCE_STOP=停止任务实例
 QUERY_WORKFLOW_LINEAGE_BY_CODE_NOTE=通过血缘代码查询工作流血缘关系
 QUERY_WORKFLOW_LINEAGE_NOTES=查询工作量血缘关系
 VERIFY_TASK_CAN_DELETE=校验是否可以删除任务
-WORKFLOW_TAG_V2=工作量血缘关系相关操作 (V2)
+WORKFLOW_TAG_V2=工作量血缘关系相关操作 (V2)
+
+PROJECT_PARAMETER_TAG=项目参数相关操作
+CREATE_PROJECT_PARAMETER_NOTES=新增项目参数
+PROJECT_PARAMETER_NAME=项目参数名称
+PROJECT_PARAMETER_VALUE=项目参数值
+UPDATE_PROJECT_PARAMETER_NOTES=更新项目参数
+PROJECT_PARAMETER_CODE=项目参数code
+DELETE_PROJECT_PARAMETER_NOTES=删除项目参数
+QUERY_PROJECT_PARAMETER_LIST_PAGING_NOTES=分页查询项目参数
+QUERY_PROJECT_PARAMETER_NOTES=查询项目参数

+ 59 - 0
dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/ProjectParameter.java

@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.dao.entity;
+
+import java.util.Date;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("t_ds_project_parameter")
+public class ProjectParameter {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("user_id")
+    private Integer userId;
+
+    private long code;
+
+    @TableField("project_code")
+    private long projectCode;
+
+    @TableField("param_name")
+    private String paramName;
+
+    @TableField("param_value")
+    private String paramValue;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 41 - 0
dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ProjectParameterMapper.java

@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.dao.mapper;
+
+import org.apache.dolphinscheduler.dao.entity.ProjectParameter;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+public interface ProjectParameterMapper extends BaseMapper<ProjectParameter> {
+
+    ProjectParameter queryByCode(@Param("code") long code);
+
+    List<ProjectParameter> queryByCodes(@Param("codes") Collection<Long> codes);
+
+    ProjectParameter queryByName(@Param("paramName") String paramName);
+
+    IPage<ProjectParameter> queryProjectParameterListPaging(IPage<ProjectParameter> page,
+                                                            @Param("projectParameterIds") List<Integer> projectParameterIds,
+                                                            @Param("searchName") String searchName);
+}

+ 71 - 0
dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ProjectParameterMapper.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="org.apache.dolphinscheduler.dao.mapper.ProjectParameterMapper">
+    <sql id="baseSql">
+        id, param_name, param_value, code, project_code, user_id, create_time, update_time
+    </sql>
+
+    <select id="queryByCode" resultType="org.apache.dolphinscheduler.dao.entity.ProjectParameter">
+        select
+        <include refid="baseSql"/>
+        from t_ds_project_parameter
+        where code = #{code}
+    </select>
+
+    <select id="queryByCodes" resultType="org.apache.dolphinscheduler.dao.entity.ProjectParameter">
+        select
+        <include refid="baseSql"/>
+        from t_ds_project_parameter
+        where 1 = 1
+        <if test="codes != null and codes.size() != 0">
+            and code in
+            <foreach collection="codes" index="index" item="i" open="(" separator="," close=")">
+                #{i}
+            </foreach>
+        </if>
+    </select>
+
+    <select id="queryByName" resultType="org.apache.dolphinscheduler.dao.entity.ProjectParameter">
+        select
+        <include refid="baseSql"/>
+        from t_ds_project_parameter
+        where param_name = #{paramName}
+    </select>
+
+    <select id="queryProjectParameterListPaging" resultType="org.apache.dolphinscheduler.dao.entity.ProjectParameter">
+        select
+        <include refid="baseSql"/>
+        from t_ds_project_parameter
+        where 1=1
+        <if test="projectParameterIds != null and projectParameterIds.size() > 0">
+            and id  in
+            <foreach item="id" index="index" collection="projectParameterIds" open="(" separator="," close=")">
+                #{id}
+            </foreach>
+        </if>
+        <if test="searchName!=null and searchName != ''">
+            AND (param_name LIKE concat('%', #{searchName}, '%')
+            OR param_value LIKE concat('%', #{searchName}, '%')
+            )
+        </if>
+        order by id desc
+    </select>
+
+</mapper>

+ 23 - 0
dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql

@@ -660,6 +660,29 @@ CREATE TABLE t_ds_project
 -- Records of t_ds_project
 -- ----------------------------
 
+-- ----------------------------
+-- Table structure for t_ds_project_parameter
+-- ----------------------------
+DROP TABLE IF EXISTS t_ds_project_parameter CASCADE;
+CREATE TABLE t_ds_project_parameter
+(
+    id              int(11) NOT NULL AUTO_INCREMENT,
+    param_name      varchar(255) NOT NULL,
+    param_value     varchar(255) NOT NULL,
+    code            bigint(20) NOT NULL,
+    project_code    bigint(20) NOT NULL,
+    user_id         int(11) DEFAULT NULL,
+    create_time     datetime NOT NULL,
+    update_time     datetime     DEFAULT NULL,
+    PRIMARY KEY (id),
+    UNIQUE KEY unique_project_parameter_name (project_code, param_name),
+    UNIQUE KEY unique_project_parameter_code (code)
+);
+
+-- ----------------------------
+-- Records of t_ds_project_parameter
+-- ----------------------------
+
 -- ----------------------------
 -- Table structure for t_ds_queue
 -- ----------------------------

+ 22 - 0
dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql

@@ -664,6 +664,28 @@ CREATE TABLE `t_ds_project` (
 -- Records of t_ds_project
 -- ----------------------------
 
+-- ----------------------------
+-- Table structure for t_ds_project_parameter
+-- ----------------------------
+DROP TABLE IF EXISTS `t_ds_project_parameter`;
+CREATE TABLE `t_ds_project_parameter` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'key',
+  `param_name` varchar(255) NOT NULL COMMENT 'project parameter name',
+  `param_value` varchar(255) NOT NULL COMMENT 'project parameter value',
+  `code` bigint(20) NOT NULL COMMENT 'encoding',
+  `project_code` bigint(20) NOT NULL COMMENT 'project code',
+  `user_id` int(11) DEFAULT NULL COMMENT 'creator id',
+  `create_time` datetime NOT NULL COMMENT 'create time',
+  `update_time` datetime DEFAULT NULL COMMENT 'update time',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `unique_project_parameter_name`(`project_code`, `param_name`),
+  UNIQUE KEY `unique_project_parameter_code`(`code`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE = utf8_bin;
+
+-- ----------------------------
+-- Records of t_ds_project_parameter
+-- ----------------------------
+
 -- ----------------------------
 -- Table structure for t_ds_queue
 -- ----------------------------

+ 20 - 0
dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql

@@ -588,6 +588,26 @@ create index user_id_index on t_ds_project (user_id);
 CREATE UNIQUE INDEX unique_name on t_ds_project (name);
 CREATE UNIQUE INDEX unique_code on t_ds_project (code);
 
+--
+-- Table structure for table t_ds_project_parameter
+--
+
+DROP TABLE IF EXISTS t_ds_project_parameter;
+CREATE TABLE t_ds_project_parameter (
+  id int NOT NULL  ,
+  param_name varchar(255) NOT NULL ,
+  param_value varchar(255) NOT NULL ,
+  code bigint NOT NULL,
+  project_code bigint NOT NULL,
+  user_id int DEFAULT NULL ,
+  create_time timestamp DEFAULT CURRENT_TIMESTAMP ,
+  update_time timestamp DEFAULT CURRENT_TIMESTAMP ,
+  PRIMARY KEY (id)
+) ;
+
+CREATE UNIQUE INDEX unique_project_parameter_name on t_ds_project_parameter (project_code, param_name);
+CREATE UNIQUE INDEX unique_project_parameter_code on t_ds_project_parameter (code);
+
 --
 -- Table structure for table t_ds_queue
 --