[Feature] Add Workspace Support (#284)

diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
index 22c0183..7dcb105 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/common/Constants.java
@@ -495,7 +495,7 @@
     public static final String WORKFLOW_RELATION_LIST = "workFlowRelationList";
 
     /** session user */
-    public static final String SESSION_USER = "session.user";
+    public static final String SESSION_USER_CONTEXT = "session.user.context";
 
     public static final String SESSION_ID = "sessionId";
 
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/AsyncConfig.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/AsyncConfig.java
index 8a66521..3652142 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/AsyncConfig.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/config/AsyncConfig.java
@@ -16,8 +16,8 @@
  */
 package org.apache.seatunnel.app.config;
 
-import org.apache.seatunnel.app.dal.entity.User;
 import org.apache.seatunnel.app.security.UserContext;
+import org.apache.seatunnel.app.security.UserContextHolder;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -59,13 +59,13 @@
         @Override
         public Runnable decorate(Runnable runnable) {
             try {
-                User user = UserContext.getUser();
+                UserContext userContext = UserContextHolder.getUserContext();
                 return () -> {
                     try {
-                        UserContext.setUser(user);
+                        UserContextHolder.setUserContext(userContext);
                         runnable.run();
                     } finally {
-                        UserContext.clear();
+                        UserContextHolder.clear();
                     }
                 };
             } catch (Exception e) {
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java
index 6dfe70c..85da2ac 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java
@@ -48,7 +48,6 @@
 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.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -57,7 +56,6 @@
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
-import springfox.documentation.annotations.ApiIgnore;
 
 import javax.annotation.Resource;
 
@@ -67,8 +65,6 @@
 import java.util.Optional;
 import java.util.stream.Collectors;
 
-import static org.apache.seatunnel.app.common.Constants.SESSION_USER;
-
 @RestController
 @RequestMapping("/seatunnel/api/v1/datasource")
 public class SeatunnelDatasourceController extends BaseController {
@@ -278,7 +274,6 @@
     })
     @GetMapping("/support-datasources")
     Result<Map<Integer, List<DataSourcePluginInfo>>> getSupportDatasources(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
             @RequestParam("showVirtualDataSource") Boolean showVirtualDataSource,
             @RequestParam(value = "source", required = false) String source) {
         Map<Integer, List<DataSourcePluginInfo>> allDatasources =
@@ -301,22 +296,17 @@
     }
 
     @GetMapping("/dynamic-form")
-    Result<String> getDynamicForm(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
-            @RequestParam("pluginName") String pluginName) {
+    Result<String> getDynamicForm(@RequestParam("pluginName") String pluginName) {
         return Result.success(datasourceService.getDynamicForm(pluginName));
     }
 
     @GetMapping("/databases")
-    Result<List<String>> getDatabases(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
-            @RequestParam("datasourceName") String datasourceName) {
+    Result<List<String>> getDatabases(@RequestParam("datasourceName") String datasourceName) {
         return Result.success(datasourceService.queryDatabaseByDatasourceName(datasourceName));
     }
 
     @GetMapping("/tables")
     Result<List<String>> getTableNames(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
             @RequestParam("datasourceName") String datasourceName,
             @RequestParam("databaseName") String databaseName,
             @RequestParam("filterName") String filterName,
@@ -327,7 +317,6 @@
 
     @GetMapping("/schema")
     Result<List<TableField>> getTableFields(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
             @RequestParam("datasourceId") String datasourceId,
             @RequestParam(value = "databaseName", required = false) String databaseName,
             @RequestParam("tableName") String tableName) {
@@ -367,9 +356,7 @@
     }
 
     @GetMapping("/all-tables")
-    Result<List<DatabaseTables>> getTables(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
-            @RequestParam("datasourceId") String datasourceId) {
+    Result<List<DatabaseTables>> getTables(@RequestParam("datasourceId") String datasourceId) {
         DatasourceDetailRes res = datasourceService.queryDatasourceDetailById(datasourceId);
         List<DatabaseTables> tables = new ArrayList<>();
         List<String> databases =
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/VirtualTableController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/VirtualTableController.java
index 2870e57..09d8667 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/VirtualTableController.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/VirtualTableController.java
@@ -34,7 +34,6 @@
 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.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
@@ -43,7 +42,6 @@
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
-import springfox.documentation.annotations.ApiIgnore;
 
 import javax.annotation.Resource;
 
@@ -51,8 +49,6 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import static org.apache.seatunnel.app.common.Constants.SESSION_USER;
-
 @RestController
 @RequestMapping("/seatunnel/api/v1/virtual_table")
 public class VirtualTableController extends BaseController {
@@ -103,9 +99,7 @@
                 dataType = "VirtualTableReq")
     })
     @GetMapping("/check/valid")
-    Result<Boolean> checkVirtualTableValid(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
-            @RequestBody VirtualTableReq virtualTableReq) {
+    Result<Boolean> checkVirtualTableValid(@RequestBody VirtualTableReq virtualTableReq) {
         return Result.success(virtualTableService.checkVirtualTableValid(virtualTableReq));
     }
 
@@ -139,9 +133,7 @@
                 dataType = "String")
     })
     @GetMapping("/{id}")
-    Result<VirtualTableDetailRes> queryVirtualTable(
-            @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser,
-            @PathVariable("id") String id) {
+    Result<VirtualTableDetailRes> queryVirtualTable(@PathVariable("id") String id) {
         // rsp add plugin name
         return Result.success(virtualTableService.queryVirtualTable(id));
     }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/WorkspaceController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/WorkspaceController.java
new file mode 100644
index 0000000..6eb4e93
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/WorkspaceController.java
@@ -0,0 +1,69 @@
+/*
+ * 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.seatunnel.app.controller;
+
+import org.apache.seatunnel.app.common.Result;
+import org.apache.seatunnel.app.dal.entity.Workspace;
+import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq;
+import org.apache.seatunnel.app.service.WorkspaceService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+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.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/seatunnel/api/v1/workspace")
+public class WorkspaceController {
+
+    @Autowired private WorkspaceService workspaceService;
+
+    @PostMapping("/create")
+    public Result<Long> createWorkspace(@RequestBody WorkspaceReq workspaceReq) {
+        return Result.success(workspaceService.createWorkspace(workspaceReq));
+    }
+
+    @PutMapping("/update/{id}")
+    public Result<Boolean> updateWorkspace(
+            @PathVariable Long id, @RequestBody WorkspaceReq workspaceReq) {
+        return Result.success(workspaceService.updateWorkspace(id, workspaceReq));
+    }
+
+    @DeleteMapping("/delete/{id}")
+    public Result<Boolean> deleteWorkspace(@PathVariable Long id) {
+        return Result.success(workspaceService.deleteWorkspace(id));
+    }
+
+    @GetMapping("/list")
+    public Result<List<Workspace>> getAllWorkspaces() {
+        return Result.success(workspaceService.getAllWorkspaces());
+    }
+
+    @GetMapping("/list/{id}")
+    public Result<Workspace> getWorkspace(@PathVariable Long id) {
+        Workspace workspaceById = workspaceService.getWorkspace(id);
+        return Result.success(workspaceById);
+    }
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java
index be5dd16..a8dafe7 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IUserDao.java
@@ -51,7 +51,7 @@
 
     void disableToken(int userId);
 
-    UserLoginLog getLastLoginLog(Integer userId);
+    UserLoginLog getLastLoginLog(Integer userId, Long workspaceId);
 
     /**
      * query enabled users
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IWorkspaceDao.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IWorkspaceDao.java
new file mode 100644
index 0000000..5458ea3
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/IWorkspaceDao.java
@@ -0,0 +1,36 @@
+/*
+ * 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.seatunnel.app.dal.dao;
+
+import org.apache.seatunnel.app.dal.entity.Workspace;
+
+import java.util.List;
+
+public interface IWorkspaceDao {
+    void insertWorkspace(Workspace workspace);
+
+    Workspace selectWorkspaceById(Long id);
+
+    Workspace selectWorkspaceByName(String workspaceName);
+
+    boolean updateWorkspaceById(Workspace workspace);
+
+    boolean deleteWorkspaceById(Long id);
+
+    List<Workspace> selectAllWorkspaces();
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/DatasourceDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/DatasourceDaoImpl.java
index fc5c760..e6c4180 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/DatasourceDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/DatasourceDaoImpl.java
@@ -31,6 +31,8 @@
 
 import java.util.List;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Repository
 public class DatasourceDaoImpl implements IDatasourceDao {
 
@@ -43,23 +45,37 @@
 
     @Override
     public Datasource selectDatasourceById(Long id) {
-        return datasourceMapper.selectById(id);
+        return datasourceMapper.selectOne(
+                new QueryWrapper<Datasource>()
+                        .eq("id", id)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public boolean deleteDatasourceById(Long id) {
-        return datasourceMapper.deleteById(id) > 0;
+        return datasourceMapper.delete(
+                        new QueryWrapper<Datasource>()
+                                .eq("id", id)
+                                .eq("workspace_id", getCurrentWorkspaceId()))
+                > 0;
     }
 
     @Override
     public Datasource queryDatasourceByName(String name) {
         return datasourceMapper.selectOne(
-                new QueryWrapper<Datasource>().eq("datasource_name", name));
+                new QueryWrapper<Datasource>()
+                        .eq("datasource_name", name)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public boolean updateDatasourceById(Datasource datasource) {
-        return datasourceMapper.updateById(datasource) > 0;
+        return datasourceMapper.update(
+                        datasource,
+                        new QueryWrapper<Datasource>()
+                                .eq("id", datasource.getId())
+                                .eq("workspace_id", getCurrentWorkspaceId()))
+                > 0;
     }
 
     @Override
@@ -67,12 +83,14 @@
         QueryWrapper<Datasource> queryWrapper = new QueryWrapper<>();
         queryWrapper.eq("datasource_name", dataSourceName);
         queryWrapper.ne("id", dataSourceId);
-        return datasourceMapper.selectList(queryWrapper).size() <= 0;
+        queryWrapper.eq("workspace_id", getCurrentWorkspaceId());
+        return datasourceMapper.selectList(queryWrapper).isEmpty();
     }
 
     @Override
     public IPage<Datasource> selectDatasourcePage(Page<Datasource> page) {
-        return datasourceMapper.selectPage(page, new QueryWrapper<Datasource>());
+        return datasourceMapper.selectPage(
+                page, new QueryWrapper<Datasource>().eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
@@ -83,7 +101,10 @@
             String pluginName) {
 
         QueryWrapper<Datasource> datasourceQueryWrapper = new QueryWrapper<>();
-        datasourceQueryWrapper.in("id", availableDatasourceIds);
+        if (availableDatasourceIds != null) {
+            datasourceQueryWrapper.in("id", availableDatasourceIds);
+        }
+        datasourceQueryWrapper.eq("workspace_id", getCurrentWorkspaceId());
         if (searchVal != null
                 && !searchVal.isEmpty()
                 && pluginName != null
@@ -107,7 +128,12 @@
 
     @Override
     public String queryDatasourceNameById(Long id) {
-        return datasourceMapper.selectById(id).getDatasourceName();
+        return datasourceMapper
+                .selectOne(
+                        new QueryWrapper<Datasource>()
+                                .eq("id", id)
+                                .eq("workspace_id", getCurrentWorkspaceId()))
+                .getDatasourceName();
     }
 
     @Override
@@ -115,28 +141,37 @@
         return datasourceMapper.selectList(
                 new QueryWrapper<Datasource>()
                         .eq("plugin_name", pluginName)
-                        .eq("plugin_version", pluginVersion));
+                        .eq("plugin_version", pluginVersion)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public List<Datasource> selectDatasourceByIds(List<Long> ids) {
-        return datasourceMapper.selectBatchIds(ids);
+        return datasourceMapper.selectList(
+                new QueryWrapper<Datasource>()
+                        .in("id", ids)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public List<Datasource> queryAll() {
-        return datasourceMapper.selectList(new QueryWrapper<>());
+        return datasourceMapper.selectList(
+                new QueryWrapper<Datasource>().eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public List<Datasource> selectByIds(List<Long> ids) {
-        return datasourceMapper.selectBatchIds(ids);
+        return datasourceMapper.selectList(
+                new QueryWrapper<Datasource>()
+                        .in("id", ids)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public List<Datasource> selectDatasourceByUserId(int userId) {
-        QueryWrapper<Datasource> queryWrapper = new QueryWrapper<>();
-        queryWrapper.eq("create_user_id", userId);
-        return datasourceMapper.selectList(queryWrapper);
+        return datasourceMapper.selectList(
+                new QueryWrapper<Datasource>()
+                        .eq("create_user_id", userId)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobDefinitionDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobDefinitionDaoImpl.java
index 1563b89..cad87ac 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobDefinitionDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobDefinitionDaoImpl.java
@@ -36,6 +36,8 @@
 
 import java.util.List;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Repository
 public class JobDefinitionDaoImpl implements IJobDefinitionDao {
 
@@ -43,17 +45,25 @@
 
     @Override
     public void add(JobDefinition job) {
+        job.setWorkspaceId(getCurrentWorkspaceId());
         jobMapper.insert(job);
     }
 
     @Override
     public JobDefinition getJob(long id) {
-        return jobMapper.selectById(id);
+        return jobMapper.selectOne(
+                Wrappers.<JobDefinition>lambdaQuery()
+                        .eq(JobDefinition::getId, id)
+                        .eq(JobDefinition::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public void updateJob(JobDefinition jobDefinition) {
-        jobMapper.updateById(jobDefinition);
+        jobMapper.update(
+                jobDefinition,
+                Wrappers.<JobDefinition>lambdaUpdate()
+                        .eq(JobDefinition::getId, jobDefinition.getId())
+                        .eq(JobDefinition::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
@@ -62,11 +72,15 @@
         IPage<JobDefinitionRes> jobDefinitionIPage;
         if (StringUtils.isEmpty(jobMode)) {
             jobDefinitionIPage =
-                    jobMapper.queryJobListPaging(new Page<>(pageNo, pageSize), searchName);
+                    jobMapper.queryJobListPaging(
+                            new Page<>(pageNo, pageSize), searchName, getCurrentWorkspaceId());
         } else {
             jobDefinitionIPage =
                     jobMapper.queryJobListPagingWithJobMode(
-                            new Page<>(pageNo, pageSize), searchName, jobMode);
+                            new Page<>(pageNo, pageSize),
+                            searchName,
+                            jobMode,
+                            getCurrentWorkspaceId());
         }
         PageInfo<JobDefinitionRes> jobs = new PageInfo<>();
         jobs.setData(jobDefinitionIPage.getRecords());
@@ -78,16 +92,19 @@
 
     @Override
     public List<JobDefinition> getJobList(@NonNull String name) {
-        return jobMapper.queryJobList(name);
+        return jobMapper.queryJobList(name, getCurrentWorkspaceId());
     }
 
     @Override
     public JobDefinition getJobByName(@NonNull String name) {
-        return jobMapper.queryJob(name);
+        return jobMapper.queryJob(name, getCurrentWorkspaceId());
     }
 
+    @Override
     public void delete(long id) {
         jobMapper.delete(
-                Wrappers.lambdaQuery(new JobDefinition()).and(i -> i.eq(JobDefinition::getId, id)));
+                Wrappers.<JobDefinition>lambdaQuery()
+                        .eq(JobDefinition::getId, id)
+                        .eq(JobDefinition::getWorkspaceId, getCurrentWorkspaceId()));
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceDaoImpl.java
index 5ece725..5e9d049 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceDaoImpl.java
@@ -21,6 +21,7 @@
 import org.apache.seatunnel.app.dal.entity.JobInstance;
 import org.apache.seatunnel.app.dal.mapper.JobInstanceMapper;
 import org.apache.seatunnel.app.domain.dto.job.SeaTunnelJobInstanceDto;
+import org.apache.seatunnel.app.utils.ServletUtils;
 import org.apache.seatunnel.common.constants.JobMode;
 
 import org.springframework.stereotype.Repository;
@@ -31,7 +32,6 @@
 
 import javax.annotation.Resource;
 
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -39,25 +39,35 @@
 public class JobInstanceDaoImpl implements IJobInstanceDao {
     @Resource private JobInstanceMapper jobInstanceMapper;
 
+    private Long getWorkspaceId() {
+        return ServletUtils.getCurrentWorkspaceId();
+    }
+
     @Override
     public JobInstance getJobInstance(@NonNull Long jobInstanceId) {
-        return jobInstanceMapper.selectById(jobInstanceId);
+        return jobInstanceMapper.selectOne(
+                new LambdaQueryWrapper<JobInstance>()
+                        .eq(JobInstance::getId, jobInstanceId)
+                        .eq(JobInstance::getWorkspaceId, getWorkspaceId()));
     }
 
     @Override
     public JobInstance getJobInstanceByEngineId(@NonNull Long jobEngineId) {
         return jobInstanceMapper.selectOne(
-                new LambdaQueryWrapper<>(new JobInstance())
-                        .eq(JobInstance::getJobEngineId, jobEngineId));
+                new LambdaQueryWrapper<JobInstance>()
+                        .eq(JobInstance::getJobEngineId, jobEngineId)
+                        .eq(JobInstance::getWorkspaceId, getWorkspaceId()));
     }
 
     @Override
     public void update(@NonNull JobInstance jobInstance) {
+        jobInstance.setWorkspaceId(getWorkspaceId());
         jobInstanceMapper.updateById(jobInstance);
     }
 
     @Override
     public void insert(@NonNull JobInstance jobInstance) {
+        jobInstance.setWorkspaceId(getWorkspaceId());
         jobInstanceMapper.insert(jobInstance);
     }
 
@@ -74,27 +84,30 @@
             String jobDefineName,
             JobMode jobMode) {
         return jobInstanceMapper.queryJobInstanceListPaging(
-                page, startTime, endTime, jobDefineName, jobMode);
+                page, startTime, endTime, jobDefineName, jobMode, getWorkspaceId());
     }
 
     @Override
     public List<JobInstance> getAllJobInstance(@NonNull List<Long> jobInstanceIdList) {
-        ArrayList<JobInstance> jobInstances = new ArrayList<>();
-        for (long jobInstanceId : jobInstanceIdList) {
-            JobInstance jobInstance = jobInstanceMapper.selectById(jobInstanceId);
-            jobInstances.add(jobInstance);
-        }
-
-        return jobInstances;
+        return jobInstanceMapper.selectList(
+                new LambdaQueryWrapper<JobInstance>()
+                        .in(JobInstance::getId, jobInstanceIdList)
+                        .eq(JobInstance::getWorkspaceId, getWorkspaceId()));
     }
 
     @Override
     public JobInstance getJobExecutionStatus(@NonNull Long jobInstanceId) {
-        return jobInstanceMapper.getJobExecutionStatus(jobInstanceId);
+        return jobInstanceMapper.selectOne(
+                new LambdaQueryWrapper<JobInstance>()
+                        .eq(JobInstance::getId, jobInstanceId)
+                        .eq(JobInstance::getWorkspaceId, getWorkspaceId()));
     }
 
     @Override
     public void deleteById(@NonNull Long jobInstanceId) {
-        jobInstanceMapper.deleteById(jobInstanceId);
+        jobInstanceMapper.delete(
+                new LambdaQueryWrapper<JobInstance>()
+                        .eq(JobInstance::getId, jobInstanceId)
+                        .eq(JobInstance::getWorkspaceId, getWorkspaceId()));
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceHistoryDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceHistoryDaoImpl.java
index 1b440e2..5e5f8fd 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceHistoryDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobInstanceHistoryDaoImpl.java
@@ -27,6 +27,8 @@
 
 import javax.annotation.Resource;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Repository
 public class JobInstanceHistoryDaoImpl implements IJobInstanceHistoryDao {
 
@@ -36,16 +38,19 @@
     public JobInstanceHistory getByInstanceId(Long jobInstanceId) {
         return jobInstanceHistoryMapper.selectOne(
                 Wrappers.lambdaQuery(new JobInstanceHistory())
-                        .eq(JobInstanceHistory::getId, jobInstanceId));
+                        .eq(JobInstanceHistory::getId, jobInstanceId)
+                        .eq(JobInstanceHistory::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public void insert(JobInstanceHistory jobInstanceHistory) {
+        jobInstanceHistory.setWorkspaceId(getCurrentWorkspaceId());
         jobInstanceHistoryMapper.insert(jobInstanceHistory);
     }
 
     @Override
     public void updateJobInstanceHistory(JobInstanceHistory jobInstanceHistory) {
+        jobInstanceHistory.setWorkspaceId(getCurrentWorkspaceId());
         jobInstanceHistoryMapper.updateById(jobInstanceHistory);
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobLineDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobLineDaoImpl.java
index 3be8e97..9b39703 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobLineDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobLineDaoImpl.java
@@ -29,6 +29,8 @@
 
 import java.util.List;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Service
 public class JobLineDaoImpl implements IJobLineDao {
 
@@ -36,17 +38,24 @@
 
     @Override
     public void deleteLinesByVersionId(long jobVersionId) {
-        jobLineMapper.deleteLinesByVersionId(jobVersionId);
+        jobLineMapper.delete(
+                Wrappers.lambdaQuery(new JobLine())
+                        .eq(JobLine::getVersionId, jobVersionId)
+                        .eq(JobLine::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public void insertLines(List<JobLine> lines) {
+        Long workspaceId = getCurrentWorkspaceId();
+        lines.forEach(line -> line.setWorkspaceId(workspaceId));
         jobLineMapper.insertBatchLines(lines);
     }
 
     @Override
     public List<JobLine> getLinesByVersionId(long jobVersionId) {
         return jobLineMapper.selectList(
-                Wrappers.lambdaQuery(new JobLine()).eq(JobLine::getVersionId, jobVersionId));
+                Wrappers.lambdaQuery(new JobLine())
+                        .eq(JobLine::getVersionId, jobVersionId)
+                        .eq(JobLine::getWorkspaceId, getCurrentWorkspaceId()));
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobMetricsDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobMetricsDaoImpl.java
index 11cc40e..8f83789 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobMetricsDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobMetricsDaoImpl.java
@@ -28,6 +28,8 @@
 
 import java.util.List;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Repository
 public class JobMetricsDaoImpl implements IJobMetricsDao {
 
@@ -35,7 +37,7 @@
 
     @Override
     public List<JobMetrics> getByInstanceId(@NonNull Long jobInstanceId) {
-        return jobMetricsMapper.queryJobMetricsByInstanceId(jobInstanceId);
+        return jobMetricsMapper.queryJobMetricsByInstanceId(jobInstanceId, getCurrentWorkspaceId());
     }
 
     @Override
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobTaskDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobTaskDaoImpl.java
index 5ab86f5..028fb7b 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobTaskDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobTaskDaoImpl.java
@@ -29,6 +29,8 @@
 
 import java.util.List;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Repository
 public class JobTaskDaoImpl implements IJobTaskDao {
 
@@ -37,12 +39,15 @@
     @Override
     public List<JobTask> getTasksByVersionId(long jobVersionId) {
         return jobTaskMapper.selectList(
-                Wrappers.lambdaQuery(new JobTask()).eq(JobTask::getVersionId, jobVersionId));
+                Wrappers.lambdaQuery(new JobTask())
+                        .eq(JobTask::getVersionId, jobVersionId)
+                        .eq(JobTask::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public void insertTask(JobTask jobTask) {
         if (jobTask != null) {
+            jobTask.setWorkspaceId(getCurrentWorkspaceId());
             jobTaskMapper.insert(jobTask);
         }
     }
@@ -50,6 +55,7 @@
     @Override
     public void updateTask(JobTask jobTask) {
         if (jobTask != null) {
+            jobTask.setWorkspaceId(getCurrentWorkspaceId());
             jobTaskMapper.updateById(jobTask);
         }
     }
@@ -59,24 +65,35 @@
         return jobTaskMapper.selectOne(
                 Wrappers.lambdaQuery(new JobTask())
                         .eq(JobTask::getVersionId, jobVersionId)
-                        .and(i -> i.eq(JobTask::getPluginId, pluginId)));
+                        .eq(JobTask::getPluginId, pluginId)
+                        .eq(JobTask::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public List<JobTask> getJobTaskByDataSourceId(long datasourceId) {
         return jobTaskMapper.selectList(
-                Wrappers.lambdaQuery(new JobTask()).eq(JobTask::getDataSourceId, datasourceId));
+                Wrappers.lambdaQuery(new JobTask())
+                        .eq(JobTask::getDataSourceId, datasourceId)
+                        .eq(JobTask::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public void updateTasks(List<JobTask> jobTasks) {
-        jobTasks.forEach(jobTaskMapper::updateById);
+        Long workspaceId = getCurrentWorkspaceId();
+        jobTasks.forEach(
+                jobTask -> {
+                    jobTask.setWorkspaceId(workspaceId);
+                    jobTaskMapper.updateById(jobTask);
+                });
     }
 
     @Override
     public void deleteTasks(List<Long> jobTaskIds) {
         if (!jobTaskIds.isEmpty()) {
-            jobTaskMapper.deleteBatchIds(jobTaskIds);
+            jobTaskMapper.delete(
+                    Wrappers.lambdaQuery(new JobTask())
+                            .in(JobTask::getId, jobTaskIds)
+                            .eq(JobTask::getWorkspaceId, getCurrentWorkspaceId()));
         }
     }
 
@@ -85,11 +102,15 @@
         jobTaskMapper.delete(
                 Wrappers.lambdaQuery(new JobTask())
                         .eq(JobTask::getVersionId, jobVersionId)
-                        .and(i -> i.eq(JobTask::getPluginId, pluginId)));
+                        .eq(JobTask::getPluginId, pluginId)
+                        .eq(JobTask::getWorkspaceId, getCurrentWorkspaceId()));
     }
 
     @Override
     public void deleteTaskByVersionId(long id) {
-        jobTaskMapper.delete(Wrappers.lambdaQuery(new JobTask()).eq(JobTask::getVersionId, id));
+        jobTaskMapper.delete(
+                Wrappers.lambdaQuery(new JobTask())
+                        .eq(JobTask::getVersionId, id)
+                        .eq(JobTask::getWorkspaceId, getCurrentWorkspaceId()));
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobVersionDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobVersionDaoImpl.java
index 4c1dc15..bc0c828 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobVersionDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/JobVersionDaoImpl.java
@@ -27,9 +27,9 @@
 
 import javax.annotation.Resource;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
 
 @Repository
 public class JobVersionDaoImpl implements IJobVersionDao {
@@ -38,35 +38,48 @@
 
     @Override
     public void createVersion(JobVersion jobVersion) {
+        jobVersion.setWorkspaceId(getCurrentWorkspaceId());
         jobVersionMapper.insert(jobVersion);
     }
 
     @Override
     public void updateVersion(JobVersion version) {
+        version.setWorkspaceId(getCurrentWorkspaceId());
         jobVersionMapper.updateById(version);
     }
 
     @Override
     public JobVersion getLatestVersion(long jobId) {
-        Map<String, Object> queryMap = new HashMap<>();
-        queryMap.put("job_id", jobId);
-        return jobVersionMapper.selectByMap(queryMap).get(0);
+        return jobVersionMapper.selectOne(
+                new QueryWrapper<JobVersion>()
+                        .eq("job_id", jobId)
+                        .eq("workspace_id", getCurrentWorkspaceId())
+                        .orderByDesc("create_time")
+                        .last("LIMIT 1"));
     }
 
     @Override
     public List<JobVersion> getLatestVersionByJobIds(List<Long> jobIds) {
-        QueryWrapper<JobVersion> wrapper = new QueryWrapper<>();
-        wrapper.in("job_id", jobIds);
-        return jobVersionMapper.selectList(wrapper);
+        return jobVersionMapper.selectList(
+                new QueryWrapper<JobVersion>()
+                        .in("job_id", jobIds)
+                        .eq("workspace_id", getCurrentWorkspaceId())
+                        .orderByDesc("create_time"));
     }
 
     @Override
     public JobVersion getVersionById(long jobVersionId) {
-        return jobVersionMapper.selectById(jobVersionId);
+        return jobVersionMapper.selectOne(
+                new QueryWrapper<JobVersion>()
+                        .eq("id", jobVersionId)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public List<JobVersion> getVersionsByIds(List<Long> jobVersionIds) {
-        return jobVersionMapper.selectBatchIds(jobVersionIds);
+        return jobVersionMapper.selectList(
+                new QueryWrapper<JobVersion>()
+                        .in("id", jobVersionIds)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java
index 3d1afef..9ed4a45 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/UserDaoImpl.java
@@ -37,6 +37,7 @@
 import java.util.Objects;
 
 import static com.google.common.base.Preconditions.checkState;
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
 import static org.apache.seatunnel.server.common.SeatunnelErrorEnum.NO_SUCH_USER;
 import static org.apache.seatunnel.server.common.SeatunnelErrorEnum.USER_ALREADY_EXISTS;
 
@@ -125,6 +126,7 @@
         log.setToken(dto.getToken());
         log.setTokenStatus(dto.getTokenStatus());
         log.setUserId(dto.getUserId());
+        log.setWorkspaceId(dto.getWorkspaceId());
 
         userLoginLogMapper.insert(log);
         return log.getId();
@@ -132,12 +134,13 @@
 
     @Override
     public void disableToken(int userId) {
-        userLoginLogMapper.updateStatus(userId, UserTokenStatusEnum.DISABLE.enable());
+        userLoginLogMapper.updateStatus(
+                userId, UserTokenStatusEnum.DISABLE.enable(), getCurrentWorkspaceId());
     }
 
     @Override
-    public UserLoginLog getLastLoginLog(Integer userId) {
-        return userLoginLogMapper.checkLastTokenEnable(userId);
+    public UserLoginLog getLastLoginLog(Integer userId, Long workspaceId) {
+        return userLoginLogMapper.checkLastTokenEnable(userId, workspaceId);
     }
 
     @Override
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/VirtualTableDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/VirtualTableDaoImpl.java
index 854d38e..80ba310 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/VirtualTableDaoImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/VirtualTableDaoImpl.java
@@ -37,6 +37,8 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
+import static org.apache.seatunnel.app.utils.ServletUtils.getCurrentWorkspaceId;
+
 @Repository
 @Slf4j
 public class VirtualTableDaoImpl implements IVirtualTableDao {
@@ -45,35 +47,46 @@
 
     @Override
     public boolean insertVirtualTable(VirtualTable virtualTable) {
+        virtualTable.setWorkspaceId(getCurrentWorkspaceId());
         return virtualTableMapper.insert(virtualTable) > 0;
     }
 
     @Override
     public boolean updateVirtualTable(VirtualTable virtualTable) {
+        virtualTable.setWorkspaceId(getCurrentWorkspaceId());
         return virtualTableMapper.updateById(virtualTable) > 0;
     }
 
     @Override
     public boolean deleteVirtualTable(Long id) {
-        return virtualTableMapper.deleteById(id) > 0;
+        return virtualTableMapper.delete(
+                        new QueryWrapper<VirtualTable>()
+                                .eq("id", id)
+                                .eq("workspace_id", getCurrentWorkspaceId()))
+                > 0;
     }
 
     @Override
     public VirtualTable selectVirtualTableById(Long id) {
-        return virtualTableMapper.selectById(id);
+        return virtualTableMapper.selectOne(
+                new QueryWrapper<VirtualTable>()
+                        .eq("id", id)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public VirtualTable selectVirtualTableByTableName(String tableName) {
         return virtualTableMapper.selectOne(
-                new QueryWrapper<VirtualTable>().eq("virtual_table_name", tableName));
+                new QueryWrapper<VirtualTable>()
+                        .eq("virtual_table_name", tableName)
+                        .eq("workspace_id", getCurrentWorkspaceId()));
     }
 
     @Override
     public boolean checkVirtualTableNameUnique(
             String virtualTableName, String databaseName, Long tableId) {
         return virtualTableMapper.checkVirtualTableNameUnique(
-                        tableId, databaseName, virtualTableName)
+                        tableId, databaseName, virtualTableName, getCurrentWorkspaceId())
                 <= 0;
     }
 
@@ -84,11 +97,17 @@
                 "======================pluginName:{}, datasourceName:{}",
                 pluginName,
                 datasourceName);
-        if (StringUtils.isBlank(pluginName) && StringUtils.isBlank(datasourceName)) {
-            return virtualTableMapper.selectPage(
-                    page, new QueryWrapper<VirtualTable>().orderByDesc("create_time"));
+        QueryWrapper<VirtualTable> queryWrapper =
+                new QueryWrapper<VirtualTable>()
+                        .eq("workspace_id", getCurrentWorkspaceId())
+                        .orderByDesc("create_time");
+        if (StringUtils.isNotBlank(pluginName)) {
+            queryWrapper.eq("plugin_name", pluginName);
         }
-        return virtualTableMapper.selectVirtualTablePageByParam(page, pluginName, datasourceName);
+        if (StringUtils.isNotBlank(datasourceName)) {
+            queryWrapper.eq("datasource_name", datasourceName);
+        }
+        return virtualTableMapper.selectPage(page, queryWrapper);
     }
 
     @Override
@@ -97,18 +116,19 @@
                 page,
                 new QueryWrapper<VirtualTable>()
                         .eq("datasource_id", datasourceId)
+                        .eq("workspace_id", getCurrentWorkspaceId())
                         .orderByDesc("create_time"));
     }
 
     @Override
     public List<String> getVirtualTableNames(String databaseName, Long datasourceId) {
-
         List<VirtualTable> result =
                 virtualTableMapper.selectList(
                         new QueryWrapper<VirtualTable>()
                                 .select("virtual_table_name")
                                 .eq("datasource_id", datasourceId)
-                                .eq("virtual_database_name", databaseName));
+                                .eq("virtual_database_name", databaseName)
+                                .eq("workspace_id", getCurrentWorkspaceId()));
         if (CollectionUtils.isEmpty(result)) {
             return new ArrayList<>();
         }
@@ -121,7 +141,8 @@
                 virtualTableMapper.selectList(
                         new QueryWrapper<VirtualTable>()
                                 .select("virtual_database_name")
-                                .eq("datasource_id", datasourceId));
+                                .eq("datasource_id", datasourceId)
+                                .eq("workspace_id", getCurrentWorkspaceId()));
         if (CollectionUtils.isEmpty(result)) {
             return new ArrayList<>();
         }
@@ -133,7 +154,9 @@
     @Override
     public boolean checkHasVirtualTable(Long datasourceId) {
         return virtualTableMapper.selectCount(
-                        new QueryWrapper<VirtualTable>().eq("datasource_id", datasourceId))
+                        new QueryWrapper<VirtualTable>()
+                                .eq("datasource_id", datasourceId)
+                                .eq("workspace_id", getCurrentWorkspaceId()))
                 > 0;
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/WorkspaceDaoImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/WorkspaceDaoImpl.java
new file mode 100644
index 0000000..26c21da
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/dao/impl/WorkspaceDaoImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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.seatunnel.app.dal.dao.impl;
+
+import org.apache.seatunnel.app.dal.dao.IWorkspaceDao;
+import org.apache.seatunnel.app.dal.entity.Workspace;
+import org.apache.seatunnel.app.dal.mapper.WorkspaceMapper;
+
+import org.springframework.stereotype.Repository;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+
+import javax.annotation.Resource;
+
+import java.util.List;
+
+@Repository
+public class WorkspaceDaoImpl implements IWorkspaceDao {
+    @Resource private WorkspaceMapper workspaceMapper;
+
+    @Override
+    public void insertWorkspace(Workspace workspace) {
+        workspaceMapper.insert(workspace);
+    }
+
+    @Override
+    public Workspace selectWorkspaceById(Long id) {
+        return workspaceMapper.selectById(id);
+    }
+
+    public Workspace selectWorkspaceByName(String workspaceName) {
+        return workspaceMapper.selectOne(
+                new QueryWrapper<Workspace>().eq("workspace_name", workspaceName));
+    }
+
+    @Override
+    public boolean updateWorkspaceById(Workspace workspace) {
+        return workspaceMapper.updateById(workspace) > 0;
+    }
+
+    @Override
+    public boolean deleteWorkspaceById(Long id) {
+        return workspaceMapper.deleteById(id) > 0;
+    }
+
+    @Override
+    public List<Workspace> selectAllWorkspaces() {
+        return workspaceMapper.selectList(new QueryWrapper<>());
+    }
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Datasource.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Datasource.java
index 3a526da..1a9fb45 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Datasource.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Datasource.java
@@ -64,4 +64,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobDefinition.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobDefinition.java
index 0809907..73f3521 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobDefinition.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobDefinition.java
@@ -57,4 +57,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstance.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstance.java
index 05419a8..cbb4f67 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstance.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstance.java
@@ -80,4 +80,7 @@
 
     @TableField("error_message")
     private String errorMessage;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstanceHistory.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstanceHistory.java
index 761c96a..d5885d4 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstanceHistory.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobInstanceHistory.java
@@ -44,4 +44,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobLine.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobLine.java
index fb6c747..a107f42 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobLine.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobLine.java
@@ -53,4 +53,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobMetrics.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobMetrics.java
index 6706c59..933479c 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobMetrics.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobMetrics.java
@@ -80,4 +80,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobTask.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobTask.java
index 417a201..ec8c369 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobTask.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobTask.java
@@ -81,4 +81,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobVersion.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobVersion.java
index a60629c..3615735 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobVersion.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/JobVersion.java
@@ -69,4 +69,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/UserLoginLog.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/UserLoginLog.java
index 008d46d..4de1e96 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/UserLoginLog.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/UserLoginLog.java
@@ -34,4 +34,6 @@
     private Date createTime;
 
     private Date updateTime;
+
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/VirtualTable.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/VirtualTable.java
index 7afcf72..374c472 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/VirtualTable.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/VirtualTable.java
@@ -67,4 +67,7 @@
 
     @TableField("update_time")
     private Date updateTime;
+
+    @TableField("workspace_id")
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Workspace.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Workspace.java
new file mode 100644
index 0000000..4099660
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/Workspace.java
@@ -0,0 +1,44 @@
+/*
+ * 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.seatunnel.app.dal.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Workspace {
+    private Long id;
+
+    @TableField("workspace_name")
+    private String workspaceName;
+
+    @TableField("description")
+    private String description;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.java
index 2977715..fb013a4 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.java
@@ -37,7 +37,6 @@
             @Param("startTime") Date startTime,
             @Param("endTime") Date endTime,
             @Param("jobDefineName") String jobDefineName,
-            @Param("jobMode") JobMode jobMode);
-
-    JobInstance getJobExecutionStatus(@Param("jobInstanceId") Long jobInstanceId);
+            @Param("jobMode") JobMode jobMode,
+            @Param("workspaceId") Long workspaceId);
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMapper.java
index b84894a..af4c7fa 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMapper.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMapper.java
@@ -30,14 +30,19 @@
 public interface JobMapper extends BaseMapper<JobDefinition> {
 
     IPage<JobDefinitionRes> queryJobListPaging(
-            IPage<JobDefinition> page, @Param("searchName") String searchName);
+            IPage<JobDefinition> page,
+            @Param("searchName") String searchName,
+            @Param("workspaceId") Long workspaceId);
 
     IPage<JobDefinitionRes> queryJobListPagingWithJobMode(
             IPage<JobDefinition> page,
             @Param("searchName") String searchName,
-            @Param("jobMode") String jobMode);
+            @Param("jobMode") String jobMode,
+            @Param("workspaceId") Long workspaceId);
 
-    List<JobDefinition> queryJobList(@Param("searchName") String searchName);
+    List<JobDefinition> queryJobList(
+            @Param("searchName") String searchName, @Param("workspaceId") Long workspaceId);
 
-    JobDefinition queryJob(@Param("searchName") String searchName);
+    JobDefinition queryJob(
+            @Param("searchName") String searchName, @Param("workspaceId") Long workspaceId);
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.java
index 1671c93..4b7bc72 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.java
@@ -25,7 +25,8 @@
 import java.util.List;
 
 public interface JobMetricsMapper extends BaseMapper<JobMetrics> {
-    List<JobMetrics> queryJobMetricsByInstanceId(@Param("jobInstanceId") Long jobInstanceId);
+    List<JobMetrics> queryJobMetricsByInstanceId(
+            @Param("jobInstanceId") Long jobInstanceId, @Param("workspaceId") Long workspaceId);
 
     void insertBatchMetrics(@Param("jobMetrics") List<JobMetrics> jobMetrics);
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.java
index 1641f3d..6b71984 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.java
@@ -26,7 +26,11 @@
 public interface UserLoginLogMapper {
     int insert(UserLoginLog userLoginLog);
 
-    int updateStatus(@Param("userId") int userId, @Param("enable") boolean enable);
+    int updateStatus(
+            @Param("userId") int userId,
+            @Param("enable") boolean enable,
+            @Param("workspaceId") long workspaceId);
 
-    UserLoginLog checkLastTokenEnable(@Param("userId") Integer userId);
+    UserLoginLog checkLastTokenEnable(
+            @Param("userId") Integer userId, @Param("workspaceId") long workspaceId);
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.java
index 5111bc3..bca31b7 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.java
@@ -22,19 +22,12 @@
 import org.apache.ibatis.annotations.Param;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 
 public interface VirtualTableMapper extends BaseMapper<VirtualTable> {
 
-    IPage<VirtualTable> selectPage(IPage<VirtualTable> page);
-
-    IPage<VirtualTable> selectVirtualTablePageByParam(
-            IPage<VirtualTable> page,
-            @Param("pluginName") String pluginName,
-            @Param("datasourceName") String datasourceName);
-
     int checkVirtualTableNameUnique(
             @Param("tableId") Long tableId,
             @Param("virtualDatabaseName") String databaseName,
-            @Param("virtualTableName") String virtualTableName);
+            @Param("virtualTableName") String virtualTableName,
+            @Param("datasourceId") Long datasourceId);
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/WorkspaceMapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/WorkspaceMapper.java
new file mode 100644
index 0000000..552f166
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/mapper/WorkspaceMapper.java
@@ -0,0 +1,32 @@
+/*
+ * 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.seatunnel.app.dal.mapper;
+
+import org.apache.seatunnel.app.dal.entity.Workspace;
+
+import org.apache.ibatis.annotations.Param;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+public interface WorkspaceMapper extends BaseMapper<Workspace> {
+    List<Workspace> getWorkspaceByName(@Param("workspaceName") String workspaceName);
+
+    List<String> getWorkspaceNames(String searchName);
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UserLoginLogDto.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UserLoginLogDto.java
index 55af565..1763af3 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UserLoginLogDto.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/dto/user/UserLoginLogDto.java
@@ -30,4 +30,6 @@
     private String token;
 
     private Boolean tokenStatus;
+
+    private Long workspaceId;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/user/UserLoginReq.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/user/UserLoginReq.java
index fdf2cd3..6c11b29 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/user/UserLoginReq.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/user/UserLoginReq.java
@@ -17,10 +17,15 @@
 
 package org.apache.seatunnel.app.domain.request.user;
 
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class UserLoginReq {
     private String username;
     private String password;
+    private String workspaceName;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/workspace/WorkspaceReq.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/workspace/WorkspaceReq.java
new file mode 100644
index 0000000..334f016
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/workspace/WorkspaceReq.java
@@ -0,0 +1,38 @@
+/*
+ * 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.seatunnel.app.domain.request.workspace;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel(value = "WorkspaceReq", description = "workspace create request")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class WorkspaceReq {
+    @ApiModelProperty(value = "workspace name", required = true, dataType = "String")
+    @NotNull private String workspaceName;
+
+    @ApiModelProperty(value = "workspace description", dataType = "String")
+    private String description;
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java
index d542293..07ccf56 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java
@@ -22,6 +22,7 @@
 import org.apache.seatunnel.app.dal.entity.User;
 import org.apache.seatunnel.app.dal.entity.UserLoginLog;
 import org.apache.seatunnel.app.security.JwtUtils;
+import org.apache.seatunnel.app.security.UserContext;
 
 import org.apache.commons.lang3.StringUtils;
 
@@ -78,7 +79,8 @@
             response.setStatus(HttpStatus.UNAUTHORIZED_401);
             return false;
         }
-        final UserLoginLog userLoginLog = userDaoImpl.getLastLoginLog(userId);
+        long workspaceIdFromToken = ((Number) map.get("workspaceId")).longValue();
+        final UserLoginLog userLoginLog = userDaoImpl.getLastLoginLog(userId, workspaceIdFromToken);
         if (Objects.isNull(userLoginLog) || !userLoginLog.getTokenStatus()) {
             log.info("userLoginLog does not exist");
             response.setStatus(HttpStatus.UNAUTHORIZED_401);
@@ -100,7 +102,13 @@
                 "Setting user to request attributes: userId={}, username={}",
                 user.getId(),
                 user.getUsername());
-        request.setAttribute(Constants.SESSION_USER, user);
+
+        UserContext userContext = new UserContext();
+        userContext.setUser(user);
+        userContext.setWorkspaceId(workspaceIdFromToken);
+        userContext.setWorkspaceName((String) map.get("workspaceName"));
+        request.setAttribute(Constants.SESSION_USER_CONTEXT, userContext);
+
         request.setAttribute("userId", userId);
         return true;
     }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/UserContextInterceptor.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/UserContextInterceptor.java
index e8477cc..be42dbb 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/UserContextInterceptor.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/UserContextInterceptor.java
@@ -16,8 +16,8 @@
  */
 package org.apache.seatunnel.app.interceptor;
 
-import org.apache.seatunnel.app.dal.entity.User;
 import org.apache.seatunnel.app.security.UserContext;
+import org.apache.seatunnel.app.security.UserContextHolder;
 
 import org.springframework.web.servlet.HandlerInterceptor;
 
@@ -26,7 +26,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import static org.apache.seatunnel.app.common.Constants.SESSION_USER;
+import static org.apache.seatunnel.app.common.Constants.SESSION_USER_CONTEXT;
 
 /**
  * Interceptor for managing user context in web requests. This interceptor sets up and cleans up the
@@ -48,12 +48,12 @@
     @Override
     public boolean preHandle(
             HttpServletRequest request, HttpServletResponse response, Object handler) {
-        User user = (User) request.getAttribute(SESSION_USER);
-        if (user != null) {
-            log.debug("Setting user context for user: {}", user.getUsername());
-            UserContext.setUser(user);
+        UserContext userContext = (UserContext) request.getAttribute(SESSION_USER_CONTEXT);
+        if (userContext != null) {
+            log.debug("Setting user context for user: {}", userContext.getUser().getUsername());
+            UserContextHolder.setUserContext(userContext);
         } else {
-            log.warn("No user found in request attributes");
+            log.warn("No user context found in request attributes");
         }
         return true;
     }
@@ -74,6 +74,6 @@
             Object handler,
             Exception ex) {
         log.debug("Clearing user context");
-        UserContext.clear();
+        UserContextHolder.clear();
     }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java
index 7e42fab..8897779 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContext.java
@@ -18,22 +18,15 @@
 
 import org.apache.seatunnel.app.dal.entity.User;
 
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
 public class UserContext {
-    private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
-
-    public static void setUser(User user) {
-        userHolder.set(user);
-    }
-
-    public static User getUser() {
-        User user = userHolder.get();
-        if (user == null) {
-            throw new RuntimeException("User context not found");
-        }
-        return user;
-    }
-
-    public static void clear() {
-        userHolder.remove();
-    }
+    User user;
+    Long workspaceId;
+    String workspaceName;
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java
new file mode 100644
index 0000000..bd6eb5c
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/security/UserContextHolder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.seatunnel.app.security;
+
+import org.apache.seatunnel.app.dal.entity.User;
+
+public class UserContextHolder {
+    private static final ThreadLocal<UserContext> userContextHolder = new ThreadLocal<>();
+
+    public static void setUserContext(UserContext userContext) {
+        userContextHolder.set(userContext);
+    }
+
+    public static User getUser() {
+        UserContext userContext = getUserContext();
+        return userContext.getUser();
+    }
+
+    public static UserContext getUserContext() {
+        UserContext userContext = userContextHolder.get();
+        if (userContext == null) {
+            throw new RuntimeException("User context not found");
+        }
+        return userContext;
+    }
+
+    public static void clear() {
+        userContextHolder.remove();
+    }
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java
new file mode 100644
index 0000000..7b413f1
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/WorkspaceService.java
@@ -0,0 +1,43 @@
+/*
+ * 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.seatunnel.app.service;
+
+import org.apache.seatunnel.app.dal.entity.Workspace;
+import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq;
+
+import java.util.List;
+
+public interface WorkspaceService {
+    Long createWorkspace(WorkspaceReq workspaceCreateReq);
+
+    Workspace getWorkspace(String workspaceName);
+
+    Workspace getWorkspace(Long id);
+
+    boolean updateWorkspace(Long id, WorkspaceReq workspaceReq);
+
+    boolean deleteWorkspace(Long id);
+
+    List<Workspace> getAllWorkspaces();
+
+    Workspace getDefaultWorkspace();
+
+    Long getWorkspaceIdOrDefault(Long workspaceId);
+
+    Long getWorkspaceIdOrCurrent(String workspaceName);
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
index ce9cbc6..2a0d790 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/DatasourceServiceImpl.java
@@ -131,6 +131,7 @@
                         .datasourceConfig(datasourceConfigStr)
                         .createTime(new Date())
                         .updateTime(new Date())
+                        .workspaceId(ServletUtils.getCurrentWorkspaceId())
                         .build();
         boolean success = datasourceDao.insertDatasource(datasource);
         if (success) {
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobMetricsServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobMetricsServiceImpl.java
index ade203b..0d9bad0 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobMetricsServiceImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobMetricsServiceImpl.java
@@ -547,6 +547,7 @@
                         metrics.setJobInstanceId(jobInstance.getId());
                         metrics.setCreateUserId(userId);
                         metrics.setUpdateUserId(userId);
+                        metrics.setWorkspaceId(jobInstance.getWorkspaceId());
                     });
 
             if (!jobMetricsFromEngine.isEmpty()) {
@@ -674,6 +675,7 @@
             jobMetrics.setJobInstanceId(jobInstance.getId());
             jobMetrics.setCreateUserId(userId);
             jobMetrics.setUpdateUserId(userId);
+            jobMetrics.setWorkspaceId(ServletUtils.getCurrentWorkspaceId());
             list.add(jobMetrics);
         }
         if (!list.isEmpty()) {
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
index e99ff4d..8a2620f 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/JobServiceImpl.java
@@ -52,7 +52,7 @@
 import java.util.stream.Stream;
 
 @Service
-public class JobServiceImpl implements IJobService {
+public class JobServiceImpl extends SeatunnelBaseServiceImpl implements IJobService {
 
     @Resource private IJobDefinitionService jobService;
     @Resource private IJobTaskService jobTaskService;
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java
index fb39373..138ab4b 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/UserServiceImpl.java
@@ -22,6 +22,7 @@
 import org.apache.seatunnel.app.config.SeatunnelAuthenticationProvidersConfig;
 import org.apache.seatunnel.app.dal.dao.IUserDao;
 import org.apache.seatunnel.app.dal.entity.User;
+import org.apache.seatunnel.app.dal.entity.Workspace;
 import org.apache.seatunnel.app.domain.dto.user.ListUserDto;
 import org.apache.seatunnel.app.domain.dto.user.UpdateUserDto;
 import org.apache.seatunnel.app.domain.dto.user.UserLoginLogDto;
@@ -38,6 +39,7 @@
 import org.apache.seatunnel.app.security.authentication.strategy.impl.LDAPAuthenticationStrategy;
 import org.apache.seatunnel.app.service.IRoleService;
 import org.apache.seatunnel.app.service.IUserService;
+import org.apache.seatunnel.app.service.WorkspaceService;
 import org.apache.seatunnel.app.utils.PasswordUtils;
 import org.apache.seatunnel.app.utils.ServletUtils;
 import org.apache.seatunnel.server.common.PageData;
@@ -60,11 +62,13 @@
 import java.util.stream.Collectors;
 
 @Component
-public class UserServiceImpl implements IUserService {
+public class UserServiceImpl extends SeatunnelBaseServiceImpl implements IUserService {
     @Resource private IUserDao userDaoImpl;
 
     @Resource private IRoleService roleServiceImpl;
 
+    @Resource private WorkspaceService workspaceService;
+
     @Resource private JwtUtils jwtUtils;
 
     @Value("${user.default.passwordSalt:seatunnel}")
@@ -184,7 +188,19 @@
         IAuthenticationStrategy strategy = strategies.get(authType);
         User user = strategy.authenticate(req);
         UserSimpleInfoRes translate = translate(user);
-        final String token = jwtUtils.genToken(translate.toMap());
+        Workspace workspace;
+        if (StringUtils.isNotEmpty(req.getWorkspaceName())
+                && !req.getWorkspaceName().equals("default")) {
+            workspace = workspaceService.getWorkspace(req.getWorkspaceName());
+        } else {
+            // get user default workspace
+            workspace = workspaceService.getDefaultWorkspace();
+        }
+
+        Map<String, Object> map = translate.toMap();
+        map.put("workspaceName", workspace.getWorkspaceName());
+        map.put("workspaceId", workspace.getId());
+        final String token = jwtUtils.genToken(map);
         translate.setToken(token);
 
         final UserLoginLogDto logDto =
@@ -192,6 +208,7 @@
                         .token(token)
                         .tokenStatus(UserTokenStatusEnum.ENABLE.enable())
                         .userId(user.getId())
+                        .workspaceId(workspace.getId())
                         .build();
         userDaoImpl.insertLoginLog(logDto);
         return translate;
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java
new file mode 100644
index 0000000..2700c7d
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/WorkspaceServiceImpl.java
@@ -0,0 +1,157 @@
+/*
+ * 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.seatunnel.app.service.impl;
+
+import org.apache.seatunnel.app.dal.dao.IWorkspaceDao;
+import org.apache.seatunnel.app.dal.entity.Workspace;
+import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq;
+import org.apache.seatunnel.app.service.WorkspaceService;
+import org.apache.seatunnel.app.utils.ServletUtils;
+import org.apache.seatunnel.server.common.CodeGenerateUtils;
+import org.apache.seatunnel.server.common.ParamValidationException;
+import org.apache.seatunnel.server.common.SeatunnelErrorEnum;
+import org.apache.seatunnel.server.common.SeatunnelException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class WorkspaceServiceImpl extends SeatunnelBaseServiceImpl implements WorkspaceService {
+    @Autowired private IWorkspaceDao workspaceDao;
+
+    @Override
+    public Long createWorkspace(WorkspaceReq workspaceReq) {
+        validateWorkspaceParam(workspaceReq);
+        Workspace workspaceByName =
+                workspaceDao.selectWorkspaceByName(workspaceReq.getWorkspaceName());
+        if (workspaceByName != null) {
+            throw new SeatunnelException(
+                    SeatunnelErrorEnum.RESOURCE_ALREADY_EXISTS,
+                    "Workspace with name " + workspaceReq.getWorkspaceName() + " already exists.");
+        }
+        Workspace workspace = new Workspace();
+        long id = CodeGenerateUtils.getInstance().genCode();
+        workspace.setId(id);
+        workspace.setWorkspaceName(workspaceReq.getWorkspaceName());
+        workspace.setDescription(workspaceReq.getDescription());
+        workspaceDao.insertWorkspace(workspace);
+        return id;
+    }
+
+    private void validateWorkspaceParam(WorkspaceReq workspaceReq) {
+        if (StringUtils.isEmpty(workspaceReq.getWorkspaceName())) {
+            throw new ParamValidationException(
+                    SeatunnelErrorEnum.PARAM_CAN_NOT_BE_NULL, "workspace");
+        }
+    }
+
+    @Override
+    public Workspace getWorkspace(Long id) {
+        Workspace workspace = workspaceDao.selectWorkspaceById(id);
+        if (null == workspace) {
+            throw new SeatunnelException(
+                    SeatunnelErrorEnum.RESOURCE_NOT_FOUND,
+                    "Workspace with id " + id + " not found.");
+        }
+        return workspace;
+    }
+
+    @Override
+    public Workspace getWorkspace(String workspaceName) {
+        Workspace workspace = workspaceDao.selectWorkspaceByName(workspaceName);
+        if (null == workspace) {
+            throw new SeatunnelException(
+                    SeatunnelErrorEnum.RESOURCE_NOT_FOUND,
+                    "Workspace with name " + workspaceName + " not found.");
+        }
+        return workspace;
+    }
+
+    @Override
+    public boolean updateWorkspace(Long id, WorkspaceReq workspaceReq) {
+        Workspace workspace = workspaceDao.selectWorkspaceById(id);
+        if (workspace == null) {
+            throw new SeatunnelException(
+                    SeatunnelErrorEnum.RESOURCE_NOT_FOUND,
+                    "Workspace with id " + id + " not found.");
+        }
+        validateWorkspaceParam(workspaceReq);
+
+        // Check if the workspace name is being changed and if it already exists in the database
+        if (!workspace.getWorkspaceName().equals(workspaceReq.getWorkspaceName())
+                && workspaceDao.selectWorkspaceByName(workspaceReq.getWorkspaceName()) != null) {
+            throw new SeatunnelException(
+                    SeatunnelErrorEnum.RESOURCE_ALREADY_EXISTS,
+                    "Workspace with name " + workspaceReq.getWorkspaceName() + " already exists.");
+        }
+
+        workspace.setWorkspaceName(workspaceReq.getWorkspaceName());
+        workspace.setDescription(workspaceReq.getDescription());
+        workspace.setUpdateTime(new Date());
+        return workspaceDao.updateWorkspaceById(workspace);
+    }
+
+    @Override
+    public boolean deleteWorkspace(Long id) {
+        Workspace workspace = workspaceDao.selectWorkspaceById(id);
+        if (null != workspace) {
+            return workspaceDao.deleteWorkspaceById(id);
+        }
+        return false;
+    }
+
+    @Override
+    public List<Workspace> getAllWorkspaces() {
+        return workspaceDao.selectAllWorkspaces();
+    }
+
+    @Override
+    public Workspace getDefaultWorkspace() {
+        return getWorkspace("default");
+    }
+
+    @Override
+    public Long getWorkspaceIdOrDefault(Long workspaceId) {
+        if (workspaceId == null || workspaceId == 0 || workspaceId == 1) {
+            return getDefaultWorkspace().getId();
+        } else {
+            // Check if the workspace exists
+            Workspace workspaceById = getWorkspace(workspaceId);
+            if (workspaceById == null) {
+                throw new SeatunnelException(
+                        SeatunnelErrorEnum.RESOURCE_NOT_FOUND,
+                        "Workspace with id " + workspaceId + " not found.");
+            }
+            return workspaceById.getId();
+        }
+    }
+
+    public Long getWorkspaceIdOrCurrent(String workspaceName) {
+        if (StringUtils.isEmpty(workspaceName)) {
+            // get names from current workspace
+            return ServletUtils.getCurrentWorkspaceId();
+        } else {
+            return getWorkspace(workspaceName).getId();
+        }
+    }
+}
diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ServletUtils.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ServletUtils.java
index 5fe0b7a..d696353 100644
--- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ServletUtils.java
+++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/ServletUtils.java
@@ -18,15 +18,19 @@
 package org.apache.seatunnel.app.utils;
 
 import org.apache.seatunnel.app.dal.entity.User;
-import org.apache.seatunnel.app.security.UserContext;
+import org.apache.seatunnel.app.security.UserContextHolder;
 
 public class ServletUtils {
 
     public static User getCurrentUser() {
-        return UserContext.getUser();
+        return UserContextHolder.getUser();
     }
 
     public static Integer getCurrentUserId() {
         return getCurrentUser().getId();
     }
+
+    public static Long getCurrentWorkspaceId() {
+        return UserContextHolder.getUserContext().getWorkspaceId();
+    }
 }
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.xml
index 9e4d661..0978573 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.xml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobInstanceMapper.xml
@@ -25,10 +25,11 @@
         <result column="engine_version" jdbcType="VARCHAR" property="engineVersion"/>
         <result column="job_engine_id" jdbcType="VARCHAR" property="jobEngineId"/>
         <result column="error_message" jdbcType="VARCHAR" property="errorMessage"/>
+        <result column="workspace_id" jdbcType="BIGINT" property="workspaceId"/>
     </resultMap>
     <sql id="Base_Column_List">
         id
-        , `job_define_id`, `job_status`, `job_config`, `engine_name`, `engine_version`, `job_engine_id`,`error_message`
+        , `job_define_id`, `job_status`, `job_config`, `engine_name`, `engine_version`, `job_engine_id`,`error_message`, `workspace_id`
     </sql>
 
     <select id="queryJobInstanceListPaging" resultType="org.apache.seatunnel.app.domain.dto.job.SeaTunnelJobInstanceDto">
@@ -37,6 +38,7 @@
         LEFT JOIN t_st_job_definition jd ON ji.job_define_id = jd.id
         LEFT JOIN `user` cu ON ji.create_user_id = cu.id
         <where>
+            ji.workspace_id = #{workspaceId}
             <if test="startTime != null">
                 AND ji.create_time <![CDATA[ >=]]> #{startTime}
             </if>
@@ -55,9 +57,4 @@
         </where>
         ORDER BY ji.create_time DESC
     </select>
-    <select id="getJobExecutionStatus" resultType="org.apache.seatunnel.app.dal.entity.JobInstance">
-        SELECT `job_status`, `error_message`
-        FROM t_st_job_instance t
-        WHERE t.id = #{jobInstanceId}
-    </select>
 </mapper>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobLineMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobLineMapper.xml
index d6e99a5..a921aba 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobLineMapper.xml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobLineMapper.xml
@@ -24,19 +24,17 @@
     <result column="target_plugin_id" jdbcType="BIGINT" property="targetPluginId"/>
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
+    <result column="workspace_id" jdbcType="BIGINT" property="workspaceId"/>
   </resultMap>
-  <delete id="deleteLinesByVersionId">
-    delete from t_st_job_line where version_id = #{versionId}
-  </delete>
-
   <insert id="insertBatchLines">
-    insert into t_st_job_line (id, version_id, input_plugin_id, target_plugin_id)
+    insert into t_st_job_line (id, version_id, input_plugin_id, target_plugin_id, workspace_id)
     values
     <foreach collection="lines" item="line" separator=",">
       (#{line.id},
       #{line.versionId},
       #{line.inputPluginId},
-      #{line.targetPluginId})
+      #{line.targetPluginId},
+      #{line.workspaceId})
     </foreach>
   </insert>
 </mapper>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml
index a8a2b06..3bd8ac9 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml
@@ -24,18 +24,19 @@
     <result column="update_user_id" jdbcType="INTEGER" property="updateUserId"/>
     <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
     <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
+    <result column="workspace_id" jdbcType="BIGINT" property="workspaceId"/>
   </resultMap>
   <sql id="Base_Column_List">
-    id, `name`, `description`,`job_type`, create_user_id, update_user_id
+    id, `name`, `description`,`job_type`, create_user_id, update_user_id, workspace_id
   </sql>
   <sql id="Query_Job_Column_List">
-    t.id, t.`name`, t.`description`, t.`job_type`, t.create_user_id, t.update_user_id, t.create_time, t.update_time
+    t.id, t.`name`, t.`description`, t.`job_type`, t.create_user_id, t.update_user_id, t.create_time, t.update_time, t.workspace_id
   </sql>
   <insert id="insert" parameterType="org.apache.seatunnel.app.dal.entity.JobDefinition">
     insert into `t_st_job_definition` (<include refid="Base_Column_List"/>)
     values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR},
     #{description,jdbcType=VARCHAR},#{jobType,jdbcType=VARCHAR},
-    #{createUserId,jdbcType=BIGINT}, #{updateUserId,jdbcType=BIGINT})
+    #{createUserId,jdbcType=BIGINT}, #{updateUserId,jdbcType=BIGINT}, #{workspaceId,jdbcType=BIGINT})
   </insert>
   <select id="queryJobListPaging" resultType="org.apache.seatunnel.app.domain.response.job.JobDefinitionRes">
     select
@@ -44,9 +45,10 @@
       uu.username as update_username
     from
       t_st_job_definition t
-      left join user cu on t.create_user_id = cu.id
-      left join user uu on t.update_user_id = uu.id
+      left join `user` cu on t.create_user_id = cu.id
+      left join `user` uu on t.update_user_id = uu.id
     <where>
+      t.workspace_id = #{workspaceId}
       <if test="searchName!=null and searchName!= ''">
         and t.name LIKE concat('%', #{searchName}, '%')
       </if>
@@ -63,9 +65,10 @@
     from
       t_st_job_definition t
       join (select * from t_st_job_version where job_mode = #{jobMode}) v on t.id = v.job_id
-      left join user cu on t.create_user_id = cu.id
-      left join user uu on t.update_user_id = uu.id
+      left join `user` cu on t.create_user_id = cu.id
+      left join `user` uu on t.update_user_id = uu.id
     <where>
+      t.workspace_id = #{workspaceId}
       <if test="searchName!=null and searchName != ''">
         and t.name LIKE concat('%', #{searchName}, '%')
       </if>
@@ -79,6 +82,7 @@
     <include refid="Base_Column_List"/>, create_time, update_time
     from t_st_job_definition
     <where>
+      workspace_id = #{workspaceId}
     <if test="searchName!=null and searchName != ''">
       and name LIKE concat('%', #{searchName}, '%')
     </if>
@@ -91,6 +95,7 @@
     <include refid="Base_Column_List"/>, create_time, update_time
     from t_st_job_definition
     <where>
+      workspace_id = #{workspaceId}
       <if test="searchName!=null and searchName != ''">
         and name LIKE concat('%', #{searchName}, '%')
       </if>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.xml
index 7fdf024..94aaa4b 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.xml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMetricsMapper.xml
@@ -27,10 +27,11 @@
         <result column="read_qps" jdbcType="BIGINT" property="readQps"/>
         <result column="write_qps" jdbcType="BIGINT" property="writeQps"/>
         <result column="status" jdbcType="VARCHAR" property="status"/>
+        <result column="workspace_id" jdbcType="BIGINT" property="workspaceId"/>
     </resultMap>
     <sql id="Base_Column_List">
         id
-        , `job_instance_id`, `pipeline_id`, read_row_count, write_row_count, source_table_names, sink_table_names, read_qps, write_qps, status
+        , `job_instance_id`, `pipeline_id`, read_row_count, write_row_count, source_table_names, sink_table_names, read_qps, write_qps, status, workspace_id
     </sql>
     <insert id="insert" parameterType="org.apache.seatunnel.app.dal.entity.JobMetrics">
         insert into `t_st_job_metrics` (<include refid="Base_Column_List"/>, `create_user_id`, `update_user_id`)
@@ -44,6 +45,7 @@
                 #{read_qps,jdbcType=BIGINT},
                 #{write_qps,jdbcType=BIGINT},
                 #{status,jdbcType=VARCHAR},
+                #{workspaceId,jdbcType=BIGINT},
                 #{createUserId,jdbcType=BIGINT},
                 #{updateUserId,jdbcType=BIGINT})
     </insert>
@@ -52,7 +54,7 @@
         select
         <include refid="Base_Column_List"/>
         from t_st_job_metrics
-        where 1=1
+        where workspace_id = #{workspaceId}
         <if test="jobInstanceId != null and jobInstanceId > 0">
             and job_instance_id = #{jobInstanceId}
         </if>
@@ -72,6 +74,7 @@
             #{metrics.readQps},
             #{metrics.writeQps},
             #{metrics.status},
+            #{metrics.workspaceId},
             #{metrics.createUserId},
             #{metrics.updateUserId})
         </foreach>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.xml
index f8f9444..e932035 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.xml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/UserLoginLogMapper.xml
@@ -25,6 +25,7 @@
         <result property="tokenStatus" column="token_status" jdbcType="BOOLEAN"/>
         <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
         <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
+        <result property="workspaceId" column="workspace_id" jdbcType="BIGINT" />
     </resultMap>
 
     <sql id="Base_Column_List">
@@ -33,25 +34,26 @@
         token,
         token_status,
         create_time,
-        update_time
+        update_time,
+        workspace_id
     </sql>
 
     <insert id="insert" keyColumn="id" keyProperty="id" parameterType="org.apache.seatunnel.app.dal.entity.UserLoginLog"
             useGeneratedKeys="true">
-        insert into `user_login_log` (user_id, token, `token_status`)
-        VALUES (#{userId,jdbcType=INTEGER}, #{token,jdbcType=VARCHAR}, #{tokenStatus,jdbcType=BOOLEAN})
+        insert into `user_login_log` (user_id, token, `token_status`, `workspace_id`)
+        VALUES (#{userId,jdbcType=INTEGER}, #{token,jdbcType=VARCHAR}, #{tokenStatus,jdbcType=BOOLEAN}, #{workspaceId,jdbcType=BIGINT})
     </insert>
 
     <update id="updateStatus">
         update user_login_log
         set token_status = #{enable,jdbcType=BOOLEAN}
-        where user_id = #{userId,jdbcType=INTEGER} and token_status != #{enable}
+        where workspace_id = #{workspaceId} and user_id = #{userId,jdbcType=INTEGER} and token_status != #{enable}
     </update>
     <select id="checkLastTokenEnable" resultMap="BaseResultMap">
         select
         <include refid="Base_Column_List"/>
         from user_login_log
-        where user_id = #{userId,jdbcType=INTEGER}
+        where workspace_id = #{workspaceId} and user_id = #{userId,jdbcType=INTEGER}
         order by id desc
         limit 1
     </select>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.xml
index 29baa8b..201feb1 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.xml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/VirtualTableMapper.xml
@@ -26,36 +26,19 @@
         <result column="update_user_id" jdbcType="INTEGER" property="updateUserId"/>
         <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
         <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
+        <result column="workspace_id" jdbcType="BIGINT" property="workspaceId"/>
     </resultMap>
     <sql id="Base_Column_List">
         id
-        , datasource_id, virtual_database_name, virtual_table_name, table_fields, description, create_user_id, update_user_id, create_time, update_time
+        , datasource_id, virtual_database_name, virtual_table_name, table_fields, description, create_user_id, update_user_id, create_time, update_time, workspace_id
     </sql>
 
     <select id="checkVirtualTableNameUnique" resultType="java.lang.Integer">
         SELECT COUNT(1)
         FROM t_st_virtual_table
-        WHERE id != #{tableId}
+        WHERE workspace_id = #{workspaceId} AND id != #{tableId}
           AND virtual_database_name = #{virtualDatabaseName}
           AND virtual_table_name = #{virtualTableName}
     </select>
-    
-    <select id="selectVirtualTablePageByParam" resultType="org.apache.seatunnel.app.dal.entity.VirtualTable">
-        SELECT
-        a.id
-        , a.datasource_id, a.virtual_database_name, a.virtual_table_name, a.table_fields, a.description, a.create_user_id, a.update_user_id, a.create_time, a.update_time
-        FROM t_st_virtual_table a JOIN t_st_datasource b  on  a.datasource_id = b.id 
-        where 
-        a.datasource_id = b.id
-        <if test="pluginName != null and pluginName != '' ">
-            AND b.plugin_name = #{pluginName}
-        </if>
-        <if test="datasourceName != null and datasourceName != '' ">
-            AND b.datasource_name like concat('%', #{datasourceName}, '%')
-        </if>
 
-        ORDER BY create_time DESC
-    </select>
-
-    
 </mapper>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/WorkspaceMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/WorkspaceMapper.xml
new file mode 100644
index 0000000..6535652
--- /dev/null
+++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/WorkspaceMapper.xml
@@ -0,0 +1,30 @@
+<?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.seatunnel.app.dal.mapper.WorkspaceMapper">
+    <select id="getWorkspaceByName" resultType="org.apache.seatunnel.app.dal.entity.Workspace">
+        select id, workspace_name, description, create_time, update_time
+        from workspace
+        where workspace_name = #{workspaceName}
+        order by update_time desc
+    </select>
+    <select id="getWorkspaceNames" resultType="java.lang.String">
+        select workspace_name
+        from workspace
+        where #{searchName} IS NULL OR workspace_name LIKE concat('%', #{searchName}, '%')
+        order by update_time desc
+    </select>
+</mapper>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_h2.sql b/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_h2.sql
index b4231ce..f02bb23 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_h2.sql
+++ b/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_h2.sql
@@ -70,8 +70,9 @@
                                  update_user_id INT NOT NULL,
                                  create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                  update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                                 workspace_id BIGINT NOT NULL,
                                  PRIMARY KEY (id),
-                                 UNIQUE (datasource_name)
+                                 UNIQUE (datasource_name, workspace_id)
 );
 
 -- Table structure for t_st_job_definition
@@ -85,8 +86,9 @@
                                      update_user_id INT NOT NULL,
                                      create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                      update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                                     workspace_id BIGINT NOT NULL,
                                      PRIMARY KEY (id),
-                                     UNIQUE (name)
+                                     UNIQUE (name, workspace_id)
 );
 
 -- Table structure for t_st_job_instance
@@ -106,6 +108,7 @@
                                    end_time TIMESTAMP(3) DEFAULT NULL,
                                    job_type VARCHAR(50) NOT NULL,
                                    error_message VARCHAR(4096) DEFAULT NULL,
+                                   workspace_id BIGINT NOT NULL,
                                    PRIMARY KEY (id)
 );
 
@@ -116,6 +119,7 @@
                                            dag CLOB NOT NULL,
                                            create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                            update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                                           workspace_id BIGINT NOT NULL,
                                            PRIMARY KEY (id)
 );
 
@@ -128,8 +132,9 @@
                                target_plugin_id VARCHAR(50) NOT NULL,
                                create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                               workspace_id BIGINT NOT NULL,
                                PRIMARY KEY (id),
-                               INDEX job_line_version_index (version_id)
+                               INDEX job_line_version_index (version_id, workspace_id)
 );
 
 -- Table structure for t_st_job_metrics
@@ -150,6 +155,7 @@
                                   update_user_id INT DEFAULT NULL,
                                   create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                   update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                                  workspace_id BIGINT NOT NULL,
                                   PRIMARY KEY (id)
 );
 
@@ -171,8 +177,9 @@
                                type VARCHAR(50) NOT NULL,
                                create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                               workspace_id BIGINT NOT NULL,
                                PRIMARY KEY (id),
-                               INDEX job_task_plugin_id_index (plugin_id)
+                               INDEX job_task_plugin_id_index (plugin_id, workspace_id)
 );
 
 -- Table structure for t_st_job_version
@@ -189,6 +196,7 @@
                                   update_user_id INT NOT NULL,
                                   create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                   update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                                  workspace_id BIGINT NOT NULL,
                                   PRIMARY KEY (id)
 );
 
@@ -206,6 +214,7 @@
                                     update_user_id INT NOT NULL,
                                     create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
                                     update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                                    workspace_id BIGINT NOT NULL,
                                     PRIMARY KEY (id)
 );
 
@@ -226,6 +235,36 @@
 -- Records of user
 INSERT INTO "user" ("username", "password", "status", "type") VALUES ('admin', '7f97da8846fed829bb8d1fd9f8030f3b', 0, 0);
 
+-- ----------------------------
+-- Table structure for user_login_log
+-- ----------------------------
+DROP TABLE IF EXISTS user_login_log;
+CREATE TABLE user_login_log (
+                                id BIGINT NOT NULL AUTO_INCREMENT,
+                                user_id INT NOT NULL,
+                                token CLOB NOT NULL,
+                                token_status TINYINT NOT NULL,
+                                create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(),
+                                update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(),
+                                workspace_id BIGINT NOT NULL,
+                                PRIMARY KEY (id)
+);
+
+-- ----------------------------
+-- Workspace related tables
+-- ----------------------------
+DROP TABLE IF EXISTS "workspace";
+CREATE TABLE workspace (
+                           id BIGINT NOT NULL AUTO_INCREMENT,
+                           workspace_name VARCHAR(255) NOT NULL,
+                           description VARCHAR(255) DEFAULT NULL,
+                           create_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(),
+                           update_time TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(),
+                           PRIMARY KEY (id)
+);
+
+INSERT INTO workspace (workspace_name, description) VALUES ('default', 'default workspace');
+
 -- Records of user_login_log
 -- No equivalent records provided for the user_login_log table in the provided SQL script.
 -- You can insert records into this table using similar INSERT INTO statements.
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_mysql.sql b/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_mysql.sql
index 2262caa..5dd91e4 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_mysql.sql
+++ b/seatunnel-server/seatunnel-app/src/main/resources/script/seatunnel_server_mysql.sql
@@ -68,8 +68,9 @@
   `update_user_id` int(11) NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `t_st_datasource_datasource_name_uindex`(`datasource_name`) USING BTREE
+  UNIQUE INDEX `t_st_datasource_datasource_name_uindex`(`datasource_name`, `workspace_id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
 -- ----------------------------
@@ -85,8 +86,9 @@
   `update_user_id` int(11) NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `name`(`name`) USING BTREE
+  UNIQUE INDEX `name`(`name`, `workspace_id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
 -- ----------------------------
@@ -108,6 +110,7 @@
   `end_time` timestamp(3) NULL DEFAULT NULL,
   `job_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
   `error_message` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
@@ -120,6 +123,7 @@
   `dag` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
@@ -134,8 +138,9 @@
   `target_plugin_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE,
-  INDEX `job_line_version_index`(`version_id`) USING BTREE
+  INDEX `job_line_version_index`(`version_id`, `workspace_id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
 -- ----------------------------
@@ -158,6 +163,7 @@
   `update_user_id` int(20) NULL DEFAULT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
@@ -181,8 +187,9 @@
   `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE,
-  INDEX `job_task_plugin_id_index`(`plugin_id`) USING BTREE
+  INDEX `job_task_plugin_id_index`(`plugin_id`, `workspace_id` ) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
 -- ----------------------------
@@ -201,6 +208,7 @@
   `update_user_id` int(11) NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
@@ -220,6 +228,7 @@
   `update_user_id` int(11) NOT NULL,
   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
@@ -250,6 +259,7 @@
   `token_status` tinyint(1) NOT NULL,
   `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
   `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `workspace_id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 106 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
 
@@ -260,3 +270,17 @@
 INSERT INTO `seatunnel`.`user`(`username`,`password`,`status`,`type`) values ('admin', '7f97da8846fed829bb8d1fd9f8030f3b', 0, 0);
 
 SET FOREIGN_KEY_CHECKS = 1;
+
+-- ----------------------------
+-- Workspace related tables
+-- ----------------------------
+CREATE TABLE `workspace`  (
+                         `id` bigint(20) NOT NULL AUTO_INCREMENT,
+                         `workspace_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+                         `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
+                         `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+                         `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+                         PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
+INSERT INTO `seatunnel`.`workspace`(`workspace_name`,`description`) values ('default', 'default workspace');
diff --git a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/security/AsyncUserContextTest.java b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/security/AsyncUserContextTest.java
index 13ee42f..f5460d6 100644
--- a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/security/AsyncUserContextTest.java
+++ b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/security/AsyncUserContextTest.java
@@ -63,7 +63,7 @@
             taskExecutor.shutdown();
         }
         // Clear any remaining user context
-        UserContext.clear();
+        UserContextHolder.clear();
     }
 
     @Test
@@ -85,7 +85,7 @@
                     "Setting main thread user context: userId={}, username={}",
                     userId,
                     user.getUsername());
-            UserContext.setUser(user);
+            UserContextHolder.setUserContext(getUserContext(user));
 
             CompletableFuture<Void> future =
                     CompletableFuture.runAsync(
@@ -145,6 +145,12 @@
         }
     }
 
+    private UserContext getUserContext(User user) {
+        UserContext userContext = new UserContext();
+        userContext.setUser(user);
+        return userContext;
+    }
+
     @Test
     public void testNestedAsyncCalls() throws Exception {
         log.info("Starting nested async calls test...");
@@ -157,7 +163,7 @@
                 "Setting main thread user context: userId={}, username={}",
                 user.getId(),
                 user.getUsername());
-        UserContext.setUser(user);
+        UserContextHolder.setUserContext(getUserContext(user));
 
         CompletableFuture<Void> future =
                 CompletableFuture.runAsync(
@@ -204,7 +210,7 @@
         // First user
         User user1 = new User();
         user1.setId(1);
-        UserContext.setUser(user1);
+        UserContextHolder.setUserContext(getUserContext(user1));
         log.info("Setting first user context: userId={}", user1.getId());
 
         // Capture current user to avoid context loss during thread switching
@@ -217,7 +223,7 @@
                                         "First user's async task started: threadName={}",
                                         Thread.currentThread().getName());
                                 Thread.sleep(1000);
-                                UserContext.setUser(capturedUser1);
+                                UserContextHolder.setUserContext(getUserContext(capturedUser1));
                                 User currentUser = ServletUtils.getCurrentUser();
                                 log.info(
                                         "First user's async task got user: userId={}",
@@ -227,7 +233,7 @@
                                 log.error("First user's async task failed", e);
                                 throw new RuntimeException(e);
                             } finally {
-                                UserContext.clear();
+                                UserContextHolder.clear();
                                 latch.countDown();
                             }
                         },
@@ -236,7 +242,7 @@
         // Second user
         User user2 = new User();
         user2.setId(2);
-        UserContext.setUser(user2);
+        UserContextHolder.setUserContext(getUserContext(user2));
         log.info("Setting second user context: userId={}", user2.getId());
 
         // Capture current user to avoid context loss during thread switching
@@ -248,7 +254,7 @@
                                 log.info(
                                         "Second user's async task started: threadName={}",
                                         Thread.currentThread().getName());
-                                UserContext.setUser(capturedUser2);
+                                UserContextHolder.setUserContext(getUserContext(capturedUser2));
                                 User currentUser = ServletUtils.getCurrentUser();
                                 log.info(
                                         "Second user's async task got user: userId={}",
@@ -258,7 +264,7 @@
                                 log.error("Second user's async task failed", e);
                                 throw new RuntimeException(e);
                             } finally {
-                                UserContext.clear();
+                                UserContextHolder.clear();
                                 latch.countDown();
                             }
                         },
@@ -274,6 +280,6 @@
         assertEquals(2, result2);
         log.info("User context isolation test completed");
 
-        UserContext.clear();
+        UserContextHolder.clear();
     }
 }
diff --git a/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java b/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java
index 8a232b3..5318b55 100644
--- a/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java
+++ b/seatunnel-server/seatunnel-server-common/src/main/java/org/apache/seatunnel/server/common/SeatunnelErrorEnum.java
@@ -140,7 +140,7 @@
     INVALID_PARAM(60019, "", "param [%s] is invalid. %s"),
     TASK_NAME_ALREADY_EXISTS(60020, "task name already exists", "task [%s] already exists"),
     RESOURCE_NOT_FOUND(404, "", "%s"),
-    ;
+    RESOURCE_ALREADY_EXISTS(60021, "resource already exists", "resource [%s] already exists");
 
     private final int code;
     private final String msg;
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/SeatunnelWebTestingBase.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/SeatunnelWebTestingBase.java
index a006cd3..46df8f7 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/SeatunnelWebTestingBase.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/SeatunnelWebTestingBase.java
@@ -42,14 +42,25 @@
     }
 
     public Result<UserSimpleInfoRes> login(UserLoginReq userLoginReq, String authType) {
+        return login(userLoginReq, authType, false);
+    }
+
+    public Result<UserSimpleInfoRes> login(
+            UserLoginReq userLoginReq, String authType, Boolean setAsCurrentUser) {
         String requestBody = JsonUtils.toJsonString(userLoginReq);
         Map<String, String> headers =
                 authType != null
                         ? Collections.singletonMap("X-Seatunnel-Auth-Type", authType)
                         : null;
         String response = sendRequest(url("user/login"), requestBody, "POST", headers);
-        return JSONTestUtils.parseObject(
-                response, new TypeReference<Result<UserSimpleInfoRes>>() {});
+        Result<UserSimpleInfoRes> userSimpleInfoResResult =
+                JSONTestUtils.parseObject(
+                        response, new TypeReference<Result<UserSimpleInfoRes>>() {});
+        if (setAsCurrentUser) {
+            assert userSimpleInfoResResult != null;
+            TokenProvider.setToken(userSimpleInfoResResult.getData().getToken());
+        }
+        return userSimpleInfoResResult;
     }
 
     protected String url(String path) {
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/TokenProvider.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/TokenProvider.java
index 7f58495..7586ee2 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/TokenProvider.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/common/TokenProvider.java
@@ -35,6 +35,10 @@
         userLoginReq.setUsername("admin");
         userLoginReq.setPassword("admin");
         Result<UserSimpleInfoRes> loginResponse = seatunnelWebTestingBase.login(userLoginReq);
-        TokenProvider.token = loginResponse.getData().getToken();
+        setToken(loginResponse.getData().getToken());
+    }
+
+    public static void setToken(String token) {
+        TokenProvider.token = token;
     }
 }
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobControllerWrapper.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobControllerWrapper.java
index 9a5b64b..04705a1 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobControllerWrapper.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/JobControllerWrapper.java
@@ -18,13 +18,19 @@
 
 import org.apache.seatunnel.app.common.Result;
 import org.apache.seatunnel.app.common.SeatunnelWebTestingBase;
+import org.apache.seatunnel.app.domain.request.job.JobConfig;
 import org.apache.seatunnel.app.domain.request.job.JobCreateReq;
+import org.apache.seatunnel.app.domain.request.job.JobDAG;
+import org.apache.seatunnel.app.domain.request.job.PluginConfig;
+import org.apache.seatunnel.app.domain.response.job.JobConfigRes;
 import org.apache.seatunnel.app.domain.response.job.JobRes;
 import org.apache.seatunnel.app.utils.JSONTestUtils;
 import org.apache.seatunnel.common.utils.JsonUtils;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 
+import java.util.List;
+
 public class JobControllerWrapper extends SeatunnelWebTestingBase {
 
     public Result<Long> createJob(JobCreateReq jobCreateRequest) {
@@ -44,4 +50,26 @@
         String response = sendRequest(urlWithParam("job/get/" + jobVersionId + "?"), null, "GET");
         return JSONTestUtils.parseObject(response, new TypeReference<Result<JobRes>>() {});
     }
+
+    public JobCreateReq convertJobResToJobCreateReq(JobRes jobRes) {
+        JobCreateReq jobCreateReq = new JobCreateReq();
+
+        // Assuming JobRes contains JobConfigRes and List<PluginConfig> and JobDAG
+        JobConfigRes jobConfigRes = jobRes.getJobConfig();
+        List<PluginConfig> pluginConfigs = jobRes.getPluginConfigs();
+        JobDAG jobDAG = jobRes.getJobDAG();
+
+        // Populate JobCreateReq with data from JobRes
+        JobConfig jobConfig = new JobConfig();
+        jobConfig.setName(jobConfigRes.getName());
+        jobConfig.setDescription(jobConfigRes.getDescription());
+        jobConfig.setEnv(jobConfigRes.getEnv());
+        jobConfig.setEngine(jobConfigRes.getEngine());
+
+        jobCreateReq.setJobConfig(jobConfig);
+        jobCreateReq.setPluginConfigs(pluginConfigs);
+        jobCreateReq.setJobDAG(jobDAG);
+
+        return jobCreateReq;
+    }
 }
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/UserControllerWrapper.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/UserControllerWrapper.java
index 711ef67..57e8ded 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/UserControllerWrapper.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/UserControllerWrapper.java
@@ -18,14 +18,20 @@
 
 import org.apache.seatunnel.app.common.Result;
 import org.apache.seatunnel.app.common.SeatunnelWebTestingBase;
+import org.apache.seatunnel.app.common.TokenProvider;
 import org.apache.seatunnel.app.domain.request.user.AddUserReq;
 import org.apache.seatunnel.app.domain.request.user.UpdateUserReq;
+import org.apache.seatunnel.app.domain.request.user.UserLoginReq;
+import org.apache.seatunnel.app.domain.response.PageInfo;
 import org.apache.seatunnel.app.domain.response.user.AddUserRes;
+import org.apache.seatunnel.app.domain.response.user.UserSimpleInfoRes;
 import org.apache.seatunnel.app.utils.JSONTestUtils;
 import org.apache.seatunnel.common.utils.JsonUtils;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 public class UserControllerWrapper extends SeatunnelWebTestingBase {
 
     public Result<AddUserRes> addUser(AddUserReq addUserReq) {
@@ -51,8 +57,56 @@
         return JSONTestUtils.parseObject(response, Result.class);
     }
 
+    public Result<PageInfo<UserSimpleInfoRes>> listUsers(String name) {
+        String response = sendRequest(urlWithParam("user?name=" + name + "&pageNo=1&pageSize=10"));
+        return JSONTestUtils.parseObject(
+                response, new TypeReference<Result<PageInfo<UserSimpleInfoRes>>>() {});
+    }
+
+    public Result<PageInfo<UserSimpleInfoRes>> listUsers() {
+        String response = sendRequest(urlWithParam("user?pageNo=1&pageSize=10000"));
+        return JSONTestUtils.parseObject(
+                response, new TypeReference<Result<PageInfo<UserSimpleInfoRes>>>() {});
+    }
+
+    public Result<Void> disableUser(String userId) {
+        String response = sendRequest(url("user/" + userId + "/disable"), null, "PUT");
+        return JSONTestUtils.parseObject(response, Result.class);
+    }
+
+    public Result<Void> enableUser(String userId) {
+        String response = sendRequest(url("user/" + userId + "/enable"), null, "PATCH");
+        return JSONTestUtils.parseObject(response, Result.class);
+    }
+
+    public Result<AddUserRes> addUser(String userName, String password) {
+        String requestBody = JsonUtils.toJsonString(getAddUserReq(userName, password));
+        String response = sendRequest(url("user"), requestBody, "POST");
+        return JSONTestUtils.parseObject(response, new TypeReference<Result<AddUserRes>>() {});
+    }
+
     public Result<Void> logout() {
         String response = sendRequest(url("user/logout"), null, "PATCH");
-        return JSONTestUtils.parseObject(response, Result.class);
+        Result<Void> logoutResult =
+                JSONTestUtils.parseObject(response, new TypeReference<Result<Void>>() {});
+        assert logoutResult != null;
+        if (logoutResult.isSuccess()) {
+            TokenProvider.setToken(null);
+        }
+        return logoutResult;
+    }
+
+    public void loginAndSetCurrentUser(UserLoginReq userLoginReq) {
+        Result<UserSimpleInfoRes> login = login(userLoginReq, null, true);
+        assertTrue(login.isSuccess());
+    }
+
+    private AddUserReq getAddUserReq(String user, String pass) {
+        AddUserReq addUserReq = new AddUserReq();
+        addUserReq.setUsername(user);
+        addUserReq.setPassword(pass);
+        addUserReq.setStatus((byte) 0);
+        addUserReq.setType((byte) 0);
+        return addUserReq;
     }
 }
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/WorkspaceControllerWrapper.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/WorkspaceControllerWrapper.java
new file mode 100644
index 0000000..be57c97
--- /dev/null
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/controller/WorkspaceControllerWrapper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.seatunnel.app.controller;
+
+import org.apache.seatunnel.app.common.Result;
+import org.apache.seatunnel.app.common.SeatunnelWebTestingBase;
+import org.apache.seatunnel.app.dal.entity.Workspace;
+import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq;
+import org.apache.seatunnel.app.utils.JSONTestUtils;
+import org.apache.seatunnel.common.utils.JsonUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class WorkspaceControllerWrapper extends SeatunnelWebTestingBase {
+
+    public Result<List<Workspace>> getAllWorkspaces() {
+        String response = sendRequest(url("workspace/list"));
+        return JSONTestUtils.parseObject(response, new TypeReference<Result<List<Workspace>>>() {});
+    }
+
+    public Result<Workspace> getWorkspacesById(Long workspaceId) {
+        String response = sendRequest(url("workspace/list/" + workspaceId));
+        return JSONTestUtils.parseObject(response, new TypeReference<Result<Workspace>>() {});
+    }
+
+    public Result<Long> createWorkspace(String workspaceName) {
+        WorkspaceReq workspaceReq = new WorkspaceReq(workspaceName, workspaceName + " description");
+        String requestBody = JsonUtils.toJsonString(workspaceReq);
+        String response = sendRequest(url("workspace/create"), requestBody, "POST");
+        return JSONTestUtils.parseObject(response, new TypeReference<Result<Long>>() {});
+    }
+
+    public void createWorkspaceAndVerify(String workspaceName) {
+        Result<Long> workspaceCreationResult = createWorkspace(workspaceName);
+        assertTrue(workspaceCreationResult.isSuccess());
+        assertTrue(workspaceCreationResult.getData() > 0);
+    }
+
+    public Result<Boolean> updateWorkspace(Long workspaceId, WorkspaceReq workspaceReq) {
+        String requestBody = JsonUtils.toJsonString(workspaceReq);
+        String response = sendRequest(url("workspace/update/" + workspaceId), requestBody, "PUT");
+        return JSONTestUtils.parseObject(response, new TypeReference<Result<Boolean>>() {});
+    }
+
+    public Result<Boolean> deleteWorkspace(Long workspaceId) {
+        String response = sendRequest(url("workspace/delete/" + workspaceId), null, "DELETE");
+        return JSONTestUtils.parseObject(response, new TypeReference<Result<Boolean>>() {});
+    }
+}
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
index d3c0c8a..3fd5c17 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobControllerTest.java
@@ -26,7 +26,6 @@
 import org.apache.seatunnel.app.domain.request.job.JobCreateReq;
 import org.apache.seatunnel.app.domain.request.job.JobDAG;
 import org.apache.seatunnel.app.domain.request.job.PluginConfig;
-import org.apache.seatunnel.app.domain.response.job.JobConfigRes;
 import org.apache.seatunnel.app.domain.response.job.JobRes;
 import org.apache.seatunnel.app.domain.response.metrics.JobPipelineDetailMetricsRes;
 import org.apache.seatunnel.app.utils.JobTestingUtils;
@@ -154,7 +153,7 @@
                 jobCreateReq.getPluginConfigs().get(1).getName(),
                 jobRes.getPluginConfigs().get(1).getName());
 
-        JobCreateReq jobUpdateReq = convertJobResToJobCreateReq(jobRes);
+        JobCreateReq jobUpdateReq = jobControllerWrapper.convertJobResToJobCreateReq(jobRes);
         String jobName2 = "updateJob_single_api2" + uniqueId;
         jobUpdateReq.getJobConfig().setName(jobName2);
         jobUpdateReq.getJobConfig().setDescription(jobName2 + " description");
@@ -209,7 +208,7 @@
         assertTrue(getJobResponse.isSuccess());
         JobRes jobRes = getJobResponse.getData();
 
-        JobCreateReq jobUpdateReq = convertJobResToJobCreateReq(jobRes);
+        JobCreateReq jobUpdateReq = jobControllerWrapper.convertJobResToJobCreateReq(jobRes);
         jobUpdateReq.getPluginConfigs().add(getCopyTransformPlugin());
 
         List<Edge> edges = new ArrayList<>();
@@ -242,7 +241,7 @@
         assertTrue(getJobResponse.isSuccess());
         JobRes jobRes = getJobResponse.getData();
 
-        JobCreateReq jobUpdateReq = convertJobResToJobCreateReq(jobRes);
+        JobCreateReq jobUpdateReq = jobControllerWrapper.convertJobResToJobCreateReq(jobRes);
         jobUpdateReq
                 .getPluginConfigs()
                 .removeIf(pluginConfig -> "transform-replace".equals(pluginConfig.getName()));
@@ -281,28 +280,6 @@
                 .build();
     }
 
-    private JobCreateReq convertJobResToJobCreateReq(JobRes jobRes) {
-        JobCreateReq jobCreateReq = new JobCreateReq();
-
-        // Assuming JobRes contains JobConfigRes and List<PluginConfig> and JobDAG
-        JobConfigRes jobConfigRes = jobRes.getJobConfig();
-        List<PluginConfig> pluginConfigs = jobRes.getPluginConfigs();
-        JobDAG jobDAG = jobRes.getJobDAG();
-
-        // Populate JobCreateReq with data from JobRes
-        JobConfig jobConfig = new JobConfig();
-        jobConfig.setName(jobConfigRes.getName());
-        jobConfig.setDescription(jobConfigRes.getDescription());
-        jobConfig.setEnv(jobConfigRes.getEnv());
-        jobConfig.setEngine(jobConfigRes.getEngine());
-
-        jobCreateReq.setJobConfig(jobConfig);
-        jobCreateReq.setPluginConfigs(pluginConfigs);
-        jobCreateReq.setJobDAG(jobDAG);
-
-        return jobCreateReq;
-    }
-
     @AfterAll
     public static void tearDown() {
         seaTunnelWebCluster.stop();
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobExecutorControllerTest.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobExecutorControllerTest.java
index 63488d1..bfe7464 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobExecutorControllerTest.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/JobExecutorControllerTest.java
@@ -21,14 +21,18 @@
 import org.apache.seatunnel.app.controller.JobControllerWrapper;
 import org.apache.seatunnel.app.controller.JobExecutorControllerWrapper;
 import org.apache.seatunnel.app.controller.SeatunnelDatasourceControllerWrapper;
+import org.apache.seatunnel.app.controller.UserControllerWrapper;
+import org.apache.seatunnel.app.controller.WorkspaceControllerWrapper;
 import org.apache.seatunnel.app.domain.dto.job.SeaTunnelJobInstanceDto;
 import org.apache.seatunnel.app.domain.request.datasource.DatasourceReq;
 import org.apache.seatunnel.app.domain.request.job.JobCreateReq;
 import org.apache.seatunnel.app.domain.request.job.JobExecParam;
 import org.apache.seatunnel.app.domain.request.job.PluginConfig;
+import org.apache.seatunnel.app.domain.request.user.UserLoginReq;
 import org.apache.seatunnel.app.domain.response.executor.JobExecutionStatus;
 import org.apache.seatunnel.app.domain.response.executor.JobExecutorRes;
 import org.apache.seatunnel.app.domain.response.metrics.JobPipelineDetailMetricsRes;
+import org.apache.seatunnel.app.domain.response.user.AddUserRes;
 import org.apache.seatunnel.app.utils.JobTestingUtils;
 import org.apache.seatunnel.engine.core.job.JobStatus;
 
@@ -54,6 +58,8 @@
     private static final String uniqueId = "_" + System.currentTimeMillis();
     private static SeatunnelDatasourceControllerWrapper seatunnelDatasourceControllerWrapper;
     private static JobControllerWrapper jobControllerWrapper;
+    private static WorkspaceControllerWrapper workspaceControllerWrapper;
+    private static UserControllerWrapper userControllerWrapper;
 
     @BeforeAll
     public static void setUp() {
@@ -61,6 +67,8 @@
         jobExecutorControllerWrapper = new JobExecutorControllerWrapper();
         seatunnelDatasourceControllerWrapper = new SeatunnelDatasourceControllerWrapper();
         jobControllerWrapper = new JobControllerWrapper();
+        workspaceControllerWrapper = new WorkspaceControllerWrapper();
+        userControllerWrapper = new UserControllerWrapper();
     }
 
     @Test
@@ -79,6 +87,27 @@
     }
 
     @Test
+    public void executeJobWithWorkspaceLogin() {
+        String userName = "user_exec_job_with_workspace" + uniqueId;
+        String password = "somePass";
+        String workspaceName = "workspace_exec_job_with_workspace" + uniqueId;
+        createWorkspaceAndUser(workspaceName, userName, password);
+        userControllerWrapper.loginAndSetCurrentUser(
+                new UserLoginReq(userName, password, workspaceName));
+        String jobName = "execJob_within_workspace" + uniqueId;
+        long jobVersionId = JobTestingUtils.createJob(jobName);
+        Result<Long> result = jobExecutorControllerWrapper.jobExecutor(jobVersionId);
+        assertTrue(result.isSuccess());
+        assertTrue(result.getData() > 0);
+        Result<List<JobPipelineDetailMetricsRes>> listResult =
+                JobTestingUtils.waitForJobCompletion(result.getData());
+        assertEquals(1, listResult.getData().size());
+        assertEquals(JobStatus.FINISHED, listResult.getData().get(0).getStatus());
+        assertEquals(5, listResult.getData().get(0).getReadRowCount());
+        assertEquals(5, listResult.getData().get(0).getWriteRowCount());
+    }
+
+    @Test
     public void executeJobWithParameters() {
         String jobName = "execJobWithParam" + uniqueId;
         JobCreateReq jobCreateReq =
@@ -301,6 +330,12 @@
         seaTunnelWebCluster.stop();
     }
 
+    private void createWorkspaceAndUser(String workspaceName, String username, String password) {
+        workspaceControllerWrapper.createWorkspaceAndVerify(workspaceName);
+        Result<AddUserRes> result = userControllerWrapper.addUser(username, password);
+        assertTrue(result.isSuccess());
+    }
+
     private String getGenerateJobFile(String jobId) {
         String filePath = "profile/" + jobId + ".conf";
         String jsonContent;
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/UserControllerTest.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/UserControllerTest.java
index 77363cc..535d9f3 100644
--- a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/UserControllerTest.java
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/UserControllerTest.java
@@ -19,6 +19,7 @@
 import org.apache.seatunnel.app.common.Result;
 import org.apache.seatunnel.app.common.SeaTunnelWebCluster;
 import org.apache.seatunnel.app.controller.UserControllerWrapper;
+import org.apache.seatunnel.app.controller.WorkspaceControllerWrapper;
 import org.apache.seatunnel.app.domain.request.user.AddUserReq;
 import org.apache.seatunnel.app.domain.request.user.UpdateUserReq;
 import org.apache.seatunnel.app.domain.request.user.UserLoginReq;
@@ -34,18 +35,19 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class UserControllerTest {
     private static final SeaTunnelWebCluster seaTunnelWebCluster = new SeaTunnelWebCluster();
     private static UserControllerWrapper userControllerWrapper;
+    private static WorkspaceControllerWrapper workspaceControllerWrapper;
     final Supplier<String> uniqueId = () -> "_" + System.nanoTime();
 
     @BeforeAll
     public static void setUp() {
         seaTunnelWebCluster.start();
         userControllerWrapper = new UserControllerWrapper();
+        workspaceControllerWrapper = new WorkspaceControllerWrapper();
     }
 
     @Test
@@ -99,7 +101,6 @@
     public void listUsers_shouldReturnUsers_whenUsersExist() {
         Result<Void> result = userControllerWrapper.listUsers(1, 10);
         assertTrue(result.isSuccess());
-        assertNotNull(result.getData());
     }
 
     @Test
@@ -179,6 +180,35 @@
                 SeatunnelErrorEnum.USERNAME_PASSWORD_NO_MATCHED.getCode(), loginResult.getCode());
     }
 
+    @Test
+    public void loginWithWorkspace() {
+        String user = "userLoginWithWorkspace" + uniqueId.get();
+        String pass = "pass9";
+        String workspace = "workspaceForLogin" + uniqueId.get();
+
+        workspaceControllerWrapper.createWorkspaceAndVerify(workspace);
+        AddUserReq addUserReq = getAddUserReq(user, pass);
+        Result<AddUserRes> result = userControllerWrapper.addUser(addUserReq);
+        assertTrue(result.isSuccess());
+        UserLoginReq userLoginReq = new UserLoginReq();
+        userLoginReq.setUsername(user);
+        userLoginReq.setPassword(pass);
+        userLoginReq.setWorkspaceName(workspace);
+        Result<UserSimpleInfoRes> login = userControllerWrapper.login(userLoginReq);
+        assertTrue(login.isSuccess());
+
+        // login with workspace when workspace does not exist
+        userLoginReq.setWorkspaceName("nonExistentWorkspace");
+        Result<UserSimpleInfoRes> loginResult = userControllerWrapper.login(userLoginReq);
+        assertFalse(loginResult.isSuccess());
+        assertEquals(SeatunnelErrorEnum.RESOURCE_NOT_FOUND.getCode(), loginResult.getCode());
+
+        // login without any workspace, should login with the workspace of the user
+        userLoginReq.setWorkspaceName(null);
+        Result<UserSimpleInfoRes> loginWithoutWorkspace = userControllerWrapper.login(userLoginReq);
+        assertTrue(loginWithoutWorkspace.isSuccess());
+    }
+
     @AfterAll
     public static void tearDown() {
         Result<Void> logout = userControllerWrapper.logout();
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/WorkspaceControllerTest.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/WorkspaceControllerTest.java
new file mode 100644
index 0000000..316615a
--- /dev/null
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/WorkspaceControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.seatunnel.app.test;
+
+import org.apache.seatunnel.app.common.Result;
+import org.apache.seatunnel.app.common.SeaTunnelWebCluster;
+import org.apache.seatunnel.app.controller.WorkspaceControllerWrapper;
+import org.apache.seatunnel.app.dal.entity.Workspace;
+import org.apache.seatunnel.app.domain.request.workspace.WorkspaceReq;
+import org.apache.seatunnel.server.common.SeatunnelErrorEnum;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class WorkspaceControllerTest {
+    private static final SeaTunnelWebCluster seaTunnelWebCluster = new SeaTunnelWebCluster();
+    private static WorkspaceControllerWrapper workspaceControllerWrapper;
+    private static final String uniqueId = "_" + System.currentTimeMillis();
+
+    @BeforeAll
+    public static void setUp() {
+        seaTunnelWebCluster.start();
+        workspaceControllerWrapper = new WorkspaceControllerWrapper();
+    }
+
+    @Test
+    public void testCreateWorkspace() {
+        String workspaceName = "workspace_create" + uniqueId;
+        Long workspaceId = createWorkspace(workspaceName);
+        Workspace workspacesById = getWorkspace(workspaceId);
+        assertEquals(workspaceName, workspacesById.getWorkspaceName());
+        // test duplicated workspace name is not allowed
+        Result<Long> result = workspaceControllerWrapper.createWorkspace(workspaceName);
+        assertFalse(result.isSuccess());
+        assertEquals(SeatunnelErrorEnum.RESOURCE_ALREADY_EXISTS.getCode(), result.getCode());
+
+        // create workspace with empty workspace name
+        result = workspaceControllerWrapper.createWorkspace("");
+        assertFalse(result.isSuccess());
+        assertEquals(SeatunnelErrorEnum.PARAM_CAN_NOT_BE_NULL.getCode(), result.getCode());
+    }
+
+    @Test
+    public void testUpdateWorkspace() {
+        String workspaceName = "workspace_update" + uniqueId;
+        Long workspaceId = createWorkspace(workspaceName);
+
+        // update workspace
+        Workspace workspacesById = getWorkspace(workspaceId);
+        String newName = workspacesById.getWorkspaceName() + "_newName";
+        String newDescription = workspacesById.getDescription() + "_newDescription";
+        WorkspaceReq workspaceReq = new WorkspaceReq(newName, newDescription);
+        Result<Boolean> updateResult =
+                workspaceControllerWrapper.updateWorkspace(workspaceId, workspaceReq);
+        assertTrue(updateResult.isSuccess());
+        assertTrue(updateResult.getData());
+
+        workspacesById = getWorkspace(workspaceId);
+        assertEquals(workspacesById.getWorkspaceName(), newName);
+        assertEquals(workspacesById.getDescription(), newDescription);
+
+        // update workspace with empty workspace name
+        workspaceReq = new WorkspaceReq("", newDescription);
+        updateResult = workspaceControllerWrapper.updateWorkspace(workspaceId, workspaceReq);
+        assertFalse(updateResult.isSuccess());
+        assertEquals(SeatunnelErrorEnum.PARAM_CAN_NOT_BE_NULL.getCode(), updateResult.getCode());
+
+        // update workspace with duplicated workspace name
+        String workspaceName2 = "workspace_update2" + uniqueId;
+        createWorkspace(workspaceName2);
+
+        workspaceReq = new WorkspaceReq(workspaceName2, newDescription);
+        updateResult = workspaceControllerWrapper.updateWorkspace(workspaceId, workspaceReq);
+        assertFalse(updateResult.isSuccess());
+        assertEquals(SeatunnelErrorEnum.RESOURCE_ALREADY_EXISTS.getCode(), updateResult.getCode());
+    }
+
+    private static Long createWorkspace(String workspaceName) {
+        Result<Long> result = workspaceControllerWrapper.createWorkspace(workspaceName);
+        assertTrue(result.isSuccess());
+        assertTrue(result.getData() > 0);
+        return result.getData();
+    }
+
+    @Test
+    public void testDeleteWorkspace() {
+        String workspaceName = "workspace_delete" + uniqueId;
+        Long workspaceId = createWorkspace(workspaceName);
+        Result<Boolean> result = workspaceControllerWrapper.deleteWorkspace(workspaceId);
+        assertTrue(result.isSuccess());
+        assertTrue(result.getData());
+    }
+
+    @Test
+    public void testListWorkspaces() {
+        String workspaceName = "workspace_list_1" + uniqueId;
+        Long workspaceId1 = createWorkspace(workspaceName);
+        String workspaceName2 = "workspace_list_2" + uniqueId;
+        Long workspaceId2 = createWorkspace(workspaceName2);
+
+        Result<Workspace> workspacesById =
+                workspaceControllerWrapper.getWorkspacesById(workspaceId1);
+        assertTrue(workspacesById.isSuccess());
+        assertNotNull(workspacesById.getData());
+        assertEquals(workspacesById.getData().getWorkspaceName(), workspaceName);
+
+        Result<List<Workspace>> allWorkspaces = workspaceControllerWrapper.getAllWorkspaces();
+        assertTrue(allWorkspaces.isSuccess());
+        assertTrue(allWorkspaces.getData().size() > 2 || allWorkspaces.getData().size() == 2);
+
+        List<Long> workspaceIds =
+                allWorkspaces.getData().stream().map(Workspace::getId).collect(Collectors.toList());
+        assertTrue(workspaceIds.contains(workspaceId1));
+        assertTrue(workspaceIds.contains(workspaceId2));
+
+        // get workspace which do not exist, assuming 123456789 does not exist
+        workspacesById = workspaceControllerWrapper.getWorkspacesById(123456789L);
+        assertFalse(workspacesById.isSuccess());
+        assertEquals(SeatunnelErrorEnum.RESOURCE_NOT_FOUND.getCode(), workspacesById.getCode());
+    }
+
+    private Workspace getWorkspace(Long workspaceId) {
+        Result<Workspace> workspacesById =
+                workspaceControllerWrapper.getWorkspacesById(workspaceId);
+        assertTrue(workspacesById.isSuccess());
+        assertNotNull(workspacesById.getData());
+        return workspacesById.getData();
+    }
+
+    @AfterAll
+    public static void tearDown() {
+        seaTunnelWebCluster.stop();
+    }
+}
diff --git a/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/WorkspaceTest.java b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/WorkspaceTest.java
new file mode 100644
index 0000000..addf20c
--- /dev/null
+++ b/seatunnel-web-it/src/test/java/org/apache/seatunnel/app/test/WorkspaceTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.seatunnel.app.test;
+
+import org.apache.seatunnel.app.common.Result;
+import org.apache.seatunnel.app.common.SeaTunnelWebCluster;
+import org.apache.seatunnel.app.controller.JobDefinitionControllerWrapper;
+import org.apache.seatunnel.app.controller.JobExecutorControllerWrapper;
+import org.apache.seatunnel.app.controller.SeatunnelDatasourceControllerWrapper;
+import org.apache.seatunnel.app.controller.TaskInstanceControllerWrapper;
+import org.apache.seatunnel.app.controller.UserControllerWrapper;
+import org.apache.seatunnel.app.controller.WorkspaceControllerWrapper;
+import org.apache.seatunnel.app.domain.dto.job.SeaTunnelJobInstanceDto;
+import org.apache.seatunnel.app.domain.request.connector.BusinessMode;
+import org.apache.seatunnel.app.domain.request.datasource.DatasourceReq;
+import org.apache.seatunnel.app.domain.request.job.JobReq;
+import org.apache.seatunnel.app.domain.request.user.AddUserReq;
+import org.apache.seatunnel.app.domain.request.user.UserLoginReq;
+import org.apache.seatunnel.app.domain.response.user.AddUserRes;
+import org.apache.seatunnel.app.domain.response.user.UserSimpleInfoRes;
+import org.apache.seatunnel.app.utils.JobTestingUtils;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class WorkspaceTest {
+    private static final SeaTunnelWebCluster seaTunnelWebCluster = new SeaTunnelWebCluster();
+    private static final String uniqueId = "_" + System.currentTimeMillis();
+    private static SeatunnelDatasourceControllerWrapper datasourceControllerWrapper;
+    private static WorkspaceControllerWrapper workspaceControllerWrapper;
+    private static UserControllerWrapper userControllerWrapper;
+    private static JobDefinitionControllerWrapper jobDefinitionControllerWrapper;
+    private static JobExecutorControllerWrapper jobExecutorControllerWrapper;
+    private static TaskInstanceControllerWrapper taskInstanceControllerWrapper;
+
+    @BeforeAll
+    public static void setUp() {
+        seaTunnelWebCluster.start();
+        datasourceControllerWrapper = new SeatunnelDatasourceControllerWrapper();
+        workspaceControllerWrapper = new WorkspaceControllerWrapper();
+        userControllerWrapper = new UserControllerWrapper();
+        jobDefinitionControllerWrapper = new JobDefinitionControllerWrapper();
+        jobExecutorControllerWrapper = new JobExecutorControllerWrapper();
+        taskInstanceControllerWrapper = new TaskInstanceControllerWrapper();
+    }
+
+    @Test
+    public void testDatasourceIsolation_WithinWorkspace() {
+        String user1 = "user_namespace_11" + uniqueId;
+        String user2 = "user_namespace_12" + uniqueId;
+        String pass = "somePassword";
+        String workspace1 = "workspace_namespace_11" + uniqueId;
+        String workspace2 = "workspace_namespace_12" + uniqueId;
+
+        createWorkspaceAndUser(workspace1, user1, pass);
+        createWorkspaceAndUser(workspace2, user2, pass);
+
+        login(new UserLoginReq(user1, pass, workspace1));
+        String datasourceName = "datasource_namespace_11" + uniqueId;
+        datasourceControllerWrapper.createFakeSourceDatasource(datasourceName);
+
+        // logout and login with another user with another namespace, should be able to create
+        // datasource with same name
+        userControllerWrapper.logout();
+        login(new UserLoginReq(user2, pass, workspace2));
+        DatasourceReq req = datasourceControllerWrapper.getFakeSourceDatasourceReq(datasourceName);
+        Result<String> dataSourceCreationResult = datasourceControllerWrapper.createDatasource(req);
+        assertTrue(dataSourceCreationResult.isSuccess());
+
+        // if create datasource with same name in same workspace, should fail
+        DatasourceReq req2 = datasourceControllerWrapper.getFakeSourceDatasourceReq(datasourceName);
+        Result<String> dataSourceCreationResult2 =
+                datasourceControllerWrapper.createDatasource(req2);
+        assertFalse(dataSourceCreationResult2.isSuccess());
+    }
+
+    @Test
+    public void testJobDefinitionIsolation_WithinWorkspace() {
+        String user1 = "user_workspace_21" + uniqueId;
+        String user2 = "user_workspace_22" + uniqueId;
+        String pass = "somePassword";
+        String workspace1 = "workspace_21" + uniqueId;
+        String workspace2 = "workspace_22" + uniqueId;
+
+        createWorkspaceAndUser(workspace1, user1, pass);
+        createWorkspaceAndUser(workspace2, user2, pass);
+
+        login(new UserLoginReq(user1, pass, workspace1));
+        String jobName = "job_definition_namespace_21" + uniqueId;
+        JobReq jobReq = createJobReq(jobName);
+        Result<Long> jobDefinition = jobDefinitionControllerWrapper.createJobDefinition(jobReq);
+        assertTrue(jobDefinition.isSuccess());
+
+        // logout and login with another user with another workspace, should be able to create
+        // job definition with same name
+        userControllerWrapper.logout();
+        login(new UserLoginReq(user2, pass, workspace2));
+        jobDefinition = jobDefinitionControllerWrapper.createJobDefinition(jobReq);
+        assertTrue(jobDefinition.isSuccess(), jobDefinition.getMsg());
+
+        // if create job definition with same name in same workspace, should fail
+        jobDefinition = jobDefinitionControllerWrapper.createJobDefinition(jobReq);
+        assertFalse(jobDefinition.isSuccess());
+    }
+
+    @Test
+    public void testJobExecutionIsolation_WithinWorkspace() {
+        String user1 = "user_workspace_31" + uniqueId;
+        String user2 = "user_workspace_32" + uniqueId;
+        String pass = "somePassword";
+        String workspace1 = "workspace_31" + uniqueId;
+        String workspace2 = "workspace_32" + uniqueId;
+
+        createWorkspaceAndUser(workspace1, user1, pass);
+        createWorkspaceAndUser(workspace2, user2, pass);
+
+        login(new UserLoginReq(user1, pass, workspace1));
+        String jobName = "execJob_namespace_31" + uniqueId;
+        long jobVersionId_1 = JobTestingUtils.createJob(jobName);
+        Result<Long> executionResult = jobExecutorControllerWrapper.jobExecutor(jobVersionId_1);
+        assertTrue(executionResult.isSuccess());
+        JobTestingUtils.waitForJobCompletion(executionResult.getData());
+        List<SeaTunnelJobInstanceDto> taskInstanceList =
+                taskInstanceControllerWrapper.getTaskInstanceList(jobName);
+        assertEquals(1, taskInstanceList.size());
+
+        // logout and login with another user with another workspace, should be able to create
+        // job execution with same name
+        userControllerWrapper.logout();
+        login(new UserLoginReq(user2, pass, workspace2));
+        long jobVersionId_2 = JobTestingUtils.createJob(jobName);
+        executionResult = jobExecutorControllerWrapper.jobExecutor(jobVersionId_2);
+        assertTrue(executionResult.isSuccess());
+        JobTestingUtils.waitForJobCompletion(executionResult.getData());
+
+        taskInstanceList = taskInstanceControllerWrapper.getTaskInstanceList(jobName);
+        // should be 1 because second time we have run in separate name space
+        assertEquals(1, taskInstanceList.size());
+    }
+
+    private void createWorkspaceAndUser(String workspaceName, String username, String password) {
+        workspaceControllerWrapper.createWorkspaceAndVerify(workspaceName);
+        Result<AddUserRes> result =
+                userControllerWrapper.addUser(getAddUserReq(username, password));
+        assertTrue(result.isSuccess());
+    }
+
+    private JobReq createJobReq(String jobName) {
+        JobReq jobReq = new JobReq();
+        jobReq.setName(jobName);
+        jobReq.setDescription(jobName + " description");
+        jobReq.setJobType(BusinessMode.DATA_INTEGRATION);
+        return jobReq;
+    }
+
+    private static void login(UserLoginReq userLoginReq) {
+        Result<UserSimpleInfoRes> login = userControllerWrapper.login(userLoginReq, null, true);
+        assertTrue(login.isSuccess(), login.getMsg());
+    }
+
+    private AddUserReq getAddUserReq(String user, String pass) {
+        AddUserReq addUserReq = new AddUserReq();
+        addUserReq.setUsername(user);
+        addUserReq.setPassword(pass);
+        addUserReq.setStatus((byte) 0);
+        addUserReq.setType((byte) 0);
+        return addUserReq;
+    }
+
+    @AfterEach
+    public void cleanup() {
+        userControllerWrapper.logout();
+    }
+
+    @AfterAll
+    public static void tearDown() {
+        seaTunnelWebCluster.stop();
+    }
+}