mirror of
https://github.com/PowerJob/PowerJob.git
synced 2025-07-17 00:00:04 +08:00
Merge branch '5.0.0_v2' into 5.0.1_beta
# Conflicts: # others/powerjob-mysql.sql # pom.xml # powerjob-client/pom.xml # powerjob-common/pom.xml # powerjob-official-processors/pom.xml # powerjob-remote/pom.xml # powerjob-remote/powerjob-remote-benchmark/pom.xml # powerjob-remote/powerjob-remote-framework/pom.xml # powerjob-remote/powerjob-remote-impl-akka/pom.xml # powerjob-remote/powerjob-remote-impl-http/pom.xml # powerjob-server/pom.xml # powerjob-server/powerjob-server-common/pom.xml # powerjob-server/powerjob-server-core/pom.xml # powerjob-server/powerjob-server-extension/pom.xml # powerjob-server/powerjob-server-migrate/pom.xml # powerjob-server/powerjob-server-monitor/pom.xml # powerjob-server/powerjob-server-persistence/pom.xml # powerjob-server/powerjob-server-remote/pom.xml # powerjob-server/powerjob-server-starter/pom.xml # powerjob-server/powerjob-server-starter/src/main/resources/static/index.html # powerjob-server/powerjob-server-starter/src/main/resources/static/js/1.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/10.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/11.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/2.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/3.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/4.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/5.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/6.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/7.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/8.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/9.js # powerjob-server/powerjob-server-starter/src/main/resources/static/js/app.js # powerjob-worker-agent/pom.xml # powerjob-worker-samples/pom.xml # powerjob-worker-spring-boot-starter/pom.xml # powerjob-worker/pom.xml
This commit is contained in:
commit
02304fe921
@ -1,6 +1,6 @@
|
||||
/*
|
||||
官方 SQL 仅基于特定版本(MySQL8)导出,不一定兼容其他数据库,也不一定兼容其他版本。此 SQL 仅供参考。
|
||||
如果您的数据库无法使用此 SQL,建议使用 SpringDataJPA 自带的建表能力,先在开发环境直连测试库自动建表,然后自行导出相关的 SQL 即可
|
||||
如果您的数据库无法使用此 SQL,建议使用 SpringDataJPA 自带的建表能力,先在开发环境直连测试库自动建表,然后自行导出相关的 SQL 即可。
|
||||
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
@ -8,13 +8,13 @@
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80300 (8.3.0)
|
||||
Source Host : localhost:3306
|
||||
Source Schema : powerjob4
|
||||
Source Schema : powerjob500
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80300 (8.3.0)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 02/03/2024 18:51:36
|
||||
Date: 17/02/2024 22:20:07
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
@ -27,10 +27,16 @@ DROP TABLE IF EXISTS `app_info`;
|
||||
CREATE TABLE `app_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`app_name` varchar(255) DEFAULT NULL,
|
||||
`creator` bigint DEFAULT NULL,
|
||||
`current_server` varchar(255) DEFAULT NULL,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`modifier` bigint DEFAULT NULL,
|
||||
`namespace_id` bigint DEFAULT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`tags` varchar(255) DEFAULT NULL,
|
||||
`title` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uidx01_app_info` (`app_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@ -89,13 +95,11 @@ CREATE TABLE `instance_info` (
|
||||
DROP TABLE IF EXISTS `job_info`;
|
||||
CREATE TABLE `job_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`advanced_runtime_config` varchar(255) DEFAULT NULL,
|
||||
`alarm_config` varchar(255) DEFAULT NULL,
|
||||
`app_id` bigint DEFAULT NULL,
|
||||
`concurrency` int DEFAULT NULL,
|
||||
`designated_workers` varchar(255) DEFAULT NULL,
|
||||
`dispatch_strategy` int DEFAULT NULL,
|
||||
`dispatch_strategy_config` varchar(255) DEFAULT NULL,
|
||||
`execute_type` int DEFAULT NULL,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
@ -125,6 +129,27 @@ CREATE TABLE `job_info` (
|
||||
KEY `idx01_job_info` (`app_id`,`status`,`time_expression_type`,`next_trigger_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for namespace
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `namespace`;
|
||||
CREATE TABLE `namespace` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(255) DEFAULT NULL,
|
||||
`creator` bigint DEFAULT NULL,
|
||||
`dept` varchar(255) DEFAULT NULL,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`modifier` bigint DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`status` int DEFAULT NULL,
|
||||
`tags` varchar(255) DEFAULT NULL,
|
||||
`token` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uidx01_namespace` (`code`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oms_lock
|
||||
-- ----------------------------
|
||||
@ -138,6 +163,40 @@ CREATE TABLE `oms_lock` (
|
||||
`ownerip` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uidx01_oms_lock` (`lock_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for powerjob_files
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `powerjob_files`;
|
||||
CREATE TABLE `powerjob_files` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`bucket` varchar(255) NOT NULL COMMENT '分桶',
|
||||
`name` varchar(255) NOT NULL COMMENT '文件名称',
|
||||
`version` varchar(255) NOT NULL COMMENT '版本',
|
||||
`meta` varchar(255) DEFAULT NULL COMMENT '元数据',
|
||||
`length` bigint NOT NULL COMMENT '长度',
|
||||
`status` int NOT NULL COMMENT '状态',
|
||||
`data` longblob NOT NULL COMMENT '文件内容',
|
||||
`extra` varchar(255) DEFAULT NULL COMMENT '其他信息',
|
||||
`gmt_create` datetime NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for pwjb_user_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `pwjb_user_info`;
|
||||
CREATE TABLE `pwjb_user_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`username` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uidx01_username` (`username`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
@ -154,24 +213,61 @@ CREATE TABLE `server_info` (
|
||||
KEY `idx01_server_info` (`gmt_modified`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sundry
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sundry`;
|
||||
CREATE TABLE `sundry` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`content` varchar(255) DEFAULT NULL,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`pkey` varchar(255) DEFAULT NULL,
|
||||
`skey` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uidx01_sundry` (`pkey`,`skey`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_info`;
|
||||
CREATE TABLE `user_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`account_type` varchar(255) DEFAULT NULL,
|
||||
`email` varchar(255) DEFAULT NULL,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`nick` varchar(255) DEFAULT NULL,
|
||||
`origin_username` varchar(255) DEFAULT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`phone` varchar(255) DEFAULT NULL,
|
||||
`token_login_verify_info` varchar(255) DEFAULT NULL,
|
||||
`username` varchar(255) DEFAULT NULL,
|
||||
`web_hook` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uidx01_user_info` (`username`),
|
||||
UNIQUE KEY `uidx01_user_name` (`username`),
|
||||
KEY `uidx02_user_info` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_role
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_role`;
|
||||
CREATE TABLE `user_role` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`extra` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`role` int DEFAULT NULL,
|
||||
`scope` int DEFAULT NULL,
|
||||
`target` bigint DEFAULT NULL,
|
||||
`user_id` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uidx01_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for workflow_info
|
||||
|
88
others/sql/upgrade/v4.3.x-v5.0.x.sql
Normal file
88
others/sql/upgrade/v4.3.x-v5.0.x.sql
Normal file
@ -0,0 +1,88 @@
|
||||
-- Upgrade SQL FROM 4.1.x to 4.2.x
|
||||
-- ----------------------------
|
||||
-- Table change for app_info
|
||||
-- ----------------------------
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
ALTER TABLE `app_info` ADD COLUMN `creator` bigint NULL DEFAULT NULL;
|
||||
ALTER TABLE `app_info` ADD COLUMN `extra` varchar(255) NULL DEFAULT NULL;
|
||||
ALTER TABLE `app_info` ADD COLUMN `modifier` bigint NULL DEFAULT NULL;
|
||||
ALTER TABLE `app_info` ADD COLUMN `namespace_id` bigint NULL DEFAULT NULL;
|
||||
ALTER TABLE `app_info` ADD COLUMN `tags` varchar(255) NULL DEFAULT NULL;
|
||||
ALTER TABLE `app_info` ADD COLUMN `title` varchar(255) NULL DEFAULT NULL;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table change for user_info
|
||||
-- ----------------------------
|
||||
ALTER TABLE `user_info` ADD COLUMN `account_type` varchar(255) NULL DEFAULT NULL;
|
||||
ALTER TABLE `user_info` ADD COLUMN `nick` varchar(255) NULL DEFAULT NULL;
|
||||
ALTER TABLE `user_info` ADD COLUMN `origin_username` varchar(255) NULL DEFAULT NULL;
|
||||
ALTER TABLE `user_info` ADD COLUMN `token_login_verify_info` varchar(255) NULL DEFAULT NULL;
|
||||
ALTER TABLE `user_info` ADD UNIQUE INDEX `uidx01_user_name`(`username` ASC) USING BTREE;
|
||||
|
||||
-- ----------------------------
|
||||
-- new table 'namespace'
|
||||
-- ----------------------------
|
||||
CREATE TABLE `namespace` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`code` varchar(255) NULL DEFAULT NULL,
|
||||
`creator` bigint NULL DEFAULT NULL,
|
||||
`dept` varchar(255) NULL DEFAULT NULL,
|
||||
`extra` varchar(255) NULL DEFAULT NULL,
|
||||
`gmt_create` datetime(6) NULL DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) NULL DEFAULT NULL,
|
||||
`modifier` bigint NULL DEFAULT NULL,
|
||||
`name` varchar(255) NULL DEFAULT NULL,
|
||||
`status` int NULL DEFAULT NULL,
|
||||
`tags` varchar(255) NULL DEFAULT NULL,
|
||||
`token` varchar(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uidx01_namespace`(`code` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- new table 'pwjb_user_info'
|
||||
-- ----------------------------
|
||||
CREATE TABLE `pwjb_user_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`extra` varchar(255) NULL DEFAULT NULL,
|
||||
`gmt_create` datetime(6) NULL DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) NULL DEFAULT NULL,
|
||||
`password` varchar(255) NULL DEFAULT NULL,
|
||||
`username` varchar(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uidx01_username`(`username` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- new table 'sundry'
|
||||
-- ----------------------------
|
||||
CREATE TABLE `sundry` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`content` varchar(255) NULL DEFAULT NULL,
|
||||
`extra` varchar(255) NULL DEFAULT NULL,
|
||||
`gmt_create` datetime(6) NULL DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) NULL DEFAULT NULL,
|
||||
`pkey` varchar(255) NULL DEFAULT NULL,
|
||||
`skey` varchar(255) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uidx01_sundry`(`pkey` ASC, `skey` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- new table 'user_role'
|
||||
-- ----------------------------
|
||||
CREATE TABLE `user_role` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`extra` varchar(255) NULL DEFAULT NULL,
|
||||
`gmt_create` datetime(6) NULL DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) NULL DEFAULT NULL,
|
||||
`role` int NULL DEFAULT NULL,
|
||||
`scope` int NULL DEFAULT NULL,
|
||||
`target` bigint NULL DEFAULT NULL,
|
||||
`user_id` bigint NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `uidx01_user_id`(`user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
2
pom.xml
2
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>powerjob</name>
|
||||
<url>http://www.powerjob.tech</url>
|
||||
|
@ -5,18 +5,18 @@
|
||||
<parent>
|
||||
<artifactId>powerjob</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>powerjob-client</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<junit.version>5.9.1</junit.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<powerjob.common.version>4.3.9</powerjob.common.version>
|
||||
<powerjob.common.version>5.0.0-beta2</powerjob.common.version>
|
||||
|
||||
<mvn.shade.plugin.version>3.2.4</mvn.shade.plugin.version>
|
||||
</properties>
|
||||
|
@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<artifactId>powerjob</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>powerjob-common</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
|
@ -30,4 +30,6 @@ public class OmsConstant {
|
||||
|
||||
public static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
|
||||
public static final String JSON_MEDIA_TYPE = "application/json; charset=utf-8";
|
||||
|
||||
public static final String NULL = "null";
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
package tech.powerjob.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* PowerJob 运行时异常
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/5/26
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
public class PowerJobException extends RuntimeException {
|
||||
|
||||
protected String code;
|
||||
|
||||
public PowerJobException() {
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,11 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
/**
|
||||
* The result object returned by the request
|
||||
* <p>
|
||||
* 低版本由于 Jackson 序列化配置问题,导致无法在此对象上新增任何字段了,否则会报错 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "code" (class tech.powerjob.common.response.ObjectResultDTO), not marked as ignorable (3 known properties: "data", "success", "message"])
|
||||
* at [Source: (String)"{"success":true,"code":null,"data":2,"message":null}"; line: 1, column: 28] (through reference chain: tech.powerjob.common.response.ObjectResultDTO["code"])
|
||||
* <p>
|
||||
* 短期内所有的新增字段需求,都通过新对象继承实现
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/3/30
|
||||
@ -17,9 +22,9 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
@ToString
|
||||
public class ResultDTO<T> implements PowerSerializable {
|
||||
|
||||
private boolean success;
|
||||
private T data;
|
||||
private String message;
|
||||
protected boolean success;
|
||||
protected T data;
|
||||
protected String message;
|
||||
|
||||
public static <T> ResultDTO<T> success(T data) {
|
||||
ResultDTO<T> r = new ResultDTO<>();
|
||||
|
@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -147,6 +148,13 @@ public class CommonUtils {
|
||||
return OmsConstant.NONE;
|
||||
}
|
||||
|
||||
public static String formatTime(Date date) {
|
||||
if (date == null) {
|
||||
return OmsConstant.NONE;
|
||||
}
|
||||
return formatTime(date.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字符串,如果是 null 或空则显示 N/A
|
||||
* @param str 字符串
|
||||
|
@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<artifactId>powerjob</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>powerjob-official-processors</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
@ -20,7 +20,7 @@
|
||||
<!-- 不会被打包的部分,scope 只能是 test 或 provide -->
|
||||
<junit.version>5.9.1</junit.version>
|
||||
<logback.version>1.2.13</logback.version>
|
||||
<powerjob.worker.version>4.3.9</powerjob.worker.version>
|
||||
<powerjob.worker.version>5.0.0-beta2</powerjob.worker.version>
|
||||
<h2.db.version>2.2.224</h2.db.version>
|
||||
<mysql.version>8.0.28</mysql.version>
|
||||
<spring.version>5.3.31</spring.version>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-remote</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
|
||||
<logback.version>1.2.13</logback.version>
|
||||
<springboot.version>2.7.18</springboot.version>
|
||||
<powerjob-remote-impl-http.version>4.3.9</powerjob-remote-impl-http.version>
|
||||
<powerjob-remote-impl-akka.version>4.3.9</powerjob-remote-impl-akka.version>
|
||||
<powerjob-remote-impl-http.version>5.0.0-beta2</powerjob-remote-impl-http.version>
|
||||
<powerjob-remote-impl-akka.version>5.0.0-beta2</powerjob-remote-impl-akka.version>
|
||||
|
||||
<gatling.version>3.9.0</gatling.version>
|
||||
<gatling-maven-plugin.version>4.2.9</gatling-maven-plugin.version>
|
||||
|
@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-remote</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<artifactId>powerjob-remote-framework</artifactId>
|
||||
|
||||
<properties>
|
||||
@ -17,7 +17,7 @@
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<powerjob-common.version>4.3.9</powerjob-common.version>
|
||||
<powerjob-common.version>5.0.0-beta2</powerjob-common.version>
|
||||
<reflections.version>0.10.2</reflections.version>
|
||||
|
||||
|
||||
|
@ -5,19 +5,19 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-remote</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>powerjob-remote-impl-akka</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<powerjob-remote-framework.version>4.3.9</powerjob-remote-framework.version>
|
||||
<powerjob-remote-framework.version>5.0.0-beta2</powerjob-remote-framework.version>
|
||||
|
||||
<akka.version>2.6.13</akka.version>
|
||||
</properties>
|
||||
|
@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-remote</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>powerjob-remote-impl-http</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
@ -18,7 +18,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<vertx.version>4.3.7</vertx.version>
|
||||
<powerjob-remote-framework.version>4.3.9</powerjob-remote-framework.version>
|
||||
<powerjob-remote-framework.version>5.0.0-beta2</powerjob-remote-framework.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<artifactId>powerjob</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
@ -22,6 +22,7 @@
|
||||
<module>powerjob-server-migrate</module>
|
||||
<module>powerjob-server-core</module>
|
||||
<module>powerjob-server-monitor</module>
|
||||
<module>powerjob-server-auth</module>
|
||||
</modules>
|
||||
|
||||
|
||||
@ -50,12 +51,12 @@
|
||||
<groovy.version>3.0.10</groovy.version>
|
||||
<cron-utils.version>9.2.1</cron-utils.version>
|
||||
|
||||
<powerjob-common.version>4.3.9</powerjob-common.version>
|
||||
<powerjob-remote-impl-http.version>4.3.9</powerjob-remote-impl-http.version>
|
||||
<powerjob-remote-impl-akka.version>4.3.9</powerjob-remote-impl-akka.version>
|
||||
<powerjob-common.version>5.0.0-beta2</powerjob-common.version>
|
||||
<powerjob-remote-impl-http.version>5.0.0-beta2</powerjob-remote-impl-http.version>
|
||||
<powerjob-remote-impl-akka.version>5.0.0-beta2</powerjob-remote-impl-akka.version>
|
||||
<springdoc-openapi-ui.version>1.6.14</springdoc-openapi-ui.version>
|
||||
<aliyun-sdk-oss.version>3.17.1</aliyun-sdk-oss.version>
|
||||
<aws-java-sdk-s3.version>1.12.665</aws-java-sdk-s3.version>
|
||||
<minio.version>8.5.2</minio.version>
|
||||
<commons-collections4.version>4.4</commons-collections4.version>
|
||||
</properties>
|
||||
|
||||
@ -96,6 +97,11 @@
|
||||
<artifactId>powerjob-server-migrate</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-auth</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-starter</artifactId>
|
||||
@ -114,13 +120,12 @@
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>${aliyun-sdk-oss.version}</version>
|
||||
</dependency>
|
||||
<!-- 存储扩展-Minio/S3,未使用可移除(minio-client 依赖 OKHTTP4.x 版本,强制引入 kotlin 标准库,为了防止引入更多问题放弃) -->
|
||||
<!-- 存储扩展-Minio,未使用可移除 -->
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>${aws-java-sdk-s3.version}</version>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>${minio.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
58
powerjob-server/powerjob-server-auth/pom.xml
Normal file
58
powerjob-server/powerjob-server-auth/pom.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<version>5.0.0-beta2</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>powerjob-server-auth</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<jjwt.version>0.11.5</jjwt.version>
|
||||
<dingtalk.version>1.1.86</dingtalk.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-persistence</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
<version>${dingtalk.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,48 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
/**
|
||||
* LoginUserHolder
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/4/16
|
||||
*/
|
||||
public class LoginUserHolder {
|
||||
|
||||
private static final ThreadLocal<PowerJobUser> TL = new ThreadLocal<>();
|
||||
|
||||
public static PowerJobUser get() {
|
||||
return TL.get();
|
||||
}
|
||||
|
||||
public static void set(PowerJobUser powerJobUser) {
|
||||
TL.set(powerJobUser);
|
||||
}
|
||||
|
||||
public static void clean() {
|
||||
TL.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名
|
||||
* @return 存在则返回常规用户名,否则返回 unknown
|
||||
*/
|
||||
public static String getUserName() {
|
||||
PowerJobUser powerJobUser = get();
|
||||
if (powerJobUser != null) {
|
||||
return powerJobUser.getUsername();
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
* @return 存在则返回,否则返回 null
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
PowerJobUser powerJobUser = get();
|
||||
if (powerJobUser != null) {
|
||||
return powerJobUser.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 权限
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Permission {
|
||||
|
||||
/**
|
||||
* 不需要权限
|
||||
*/
|
||||
NONE(1),
|
||||
/**
|
||||
* 读权限,查看控制台数据
|
||||
*/
|
||||
READ(10),
|
||||
/**
|
||||
* 写权限,新增/修改任务等
|
||||
*/
|
||||
WRITE(20),
|
||||
/**
|
||||
* 运维权限,比如任务的执行
|
||||
*/
|
||||
OPS(30),
|
||||
/**
|
||||
* 超级权限
|
||||
*/
|
||||
SU(100)
|
||||
;
|
||||
|
||||
|
||||
private int v;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* PowerJob 的 登陆用户
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class PowerJobUser implements Serializable {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
/**
|
||||
* webHook
|
||||
*/
|
||||
private String webHook;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
/* ************** 以上为数据库字段 ************** */
|
||||
|
||||
private String jwtToken;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static tech.powerjob.server.auth.Permission.*;
|
||||
|
||||
/**
|
||||
* 角色
|
||||
* PowerJob 采用 RBAC 实现权限,出于实际需求的考虑,不决定采用动态权限模型。因此 RBAC 中的角色和权限均在此处定义。
|
||||
* 如果有自定义诉求,可以修改 Role 的定义
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Role {
|
||||
|
||||
/**
|
||||
* 观察者,默认只读权限
|
||||
*/
|
||||
OBSERVER(10, Sets.newHashSet(READ)),
|
||||
/**
|
||||
* 技术质量,读 + 操作权限
|
||||
*/
|
||||
QA(20, Sets.newHashSet(READ, OPS)),
|
||||
/**
|
||||
* 开发者,读 + 编辑 + 操作权限
|
||||
*/
|
||||
DEVELOPER(30, Sets.newHashSet(READ, WRITE, OPS)),
|
||||
/**
|
||||
* 管理员
|
||||
*/
|
||||
ADMIN(40, Sets.newHashSet(READ, WRITE, OPS, SU))
|
||||
;
|
||||
|
||||
private final int v;
|
||||
|
||||
private final Set<Permission> permissions;
|
||||
|
||||
public static Role of(int vv) {
|
||||
for (Role role : values()) {
|
||||
if (vv == role.v) {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("unknown role: " + vv);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 权限范围
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RoleScope {
|
||||
|
||||
/**
|
||||
* NAMESPACE 权限
|
||||
*/
|
||||
NAMESPACE(1),
|
||||
/**
|
||||
* APP 级别权限
|
||||
*/
|
||||
APP(10),
|
||||
/**
|
||||
* 全局权限
|
||||
*/
|
||||
GLOBAL(666)
|
||||
;
|
||||
|
||||
private final int v;
|
||||
|
||||
public static RoleScope of(int vv) {
|
||||
for (RoleScope rs : values()) {
|
||||
if (vv == rs.v) {
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("unknown RoleScope: " + vv);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package tech.powerjob.server.auth.common;
|
||||
|
||||
/**
|
||||
* 常量
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public class AuthConstants {
|
||||
|
||||
/* ********** 账号体系唯一标识,推荐开发者接入第三方登录体系时也使用4位编码,便于前端统一做样式 ********** */
|
||||
/**
|
||||
* PowerJob自建账号体系
|
||||
*/
|
||||
public static final String ACCOUNT_TYPE_POWER_JOB = "PWJB";
|
||||
/**
|
||||
* 钉钉
|
||||
*/
|
||||
public static final String ACCOUNT_TYPE_DING = "DING";
|
||||
/**
|
||||
* 企业微信(预留,蹲一个 contributor)
|
||||
*/
|
||||
public static final String ACCOUNT_TYPE_WX = "QYWX";
|
||||
/**
|
||||
* 飞书(预留,蹲一个 contributor +1)
|
||||
*/
|
||||
public static final String ACCOUNT_LARK = "LARK";
|
||||
|
||||
public static final String PARAM_KEY_USERNAME = "username";
|
||||
public static final String PARAM_KEY_PASSWORD = "password";
|
||||
/**
|
||||
* 前端参数-密码加密类型,官方版本出于成本未进行前后端传输的对称加密,接入方有需求可自行实现,此处定义加密协议字段
|
||||
*/
|
||||
public static final String PARAM_KEY_ENCRYPTION = "encryption";
|
||||
|
||||
/* ********** 账号体系 ********** */
|
||||
|
||||
/**
|
||||
* JWT key
|
||||
* 前端 header 默认首字母大写,保持一致方便处理
|
||||
*/
|
||||
public static final String JWT_NAME = "Power_jwt";
|
||||
|
||||
/**
|
||||
* 前端跳转到指定页面指令
|
||||
*/
|
||||
public static final String FE_REDIRECT_KEY = "FE-REDIRECT:";
|
||||
|
||||
public static final String TIPS_NO_PERMISSION_TO_SEE = "NO_PERMISSION_TO_SEE";
|
||||
|
||||
public static final Long GLOBAL_ADMIN_TARGET_ID = 1L;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package tech.powerjob.server.auth.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 鉴权错误信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AuthErrorCode {
|
||||
|
||||
USER_NOT_LOGIN("-100", "UserNotLoggedIn"),
|
||||
USER_NOT_EXIST("-101", "UserNotExist"),
|
||||
USER_AUTH_FAILED("-102", "UserAuthFailed"),
|
||||
|
||||
|
||||
NO_PERMISSION("-200", "NoPermission"),
|
||||
|
||||
/**
|
||||
* 无效请求,一般是参数问题
|
||||
*/
|
||||
INVALID_REQUEST("-300", "INVALID_REQUEST"),
|
||||
|
||||
INCORRECT_PASSWORD("-400", "INCORRECT_PASSWORD"),
|
||||
|
||||
INVALID_TOKEN("-401", "INVALID_TOKEN"),
|
||||
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String msg;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package tech.powerjob.server.auth.common;
|
||||
|
||||
import lombok.Getter;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
|
||||
/**
|
||||
* 鉴权相关错误
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Getter
|
||||
public class PowerJobAuthException extends PowerJobException {
|
||||
|
||||
public PowerJobAuthException(AuthErrorCode errorCode) {
|
||||
this(errorCode, null);
|
||||
}
|
||||
|
||||
public PowerJobAuthException(AuthErrorCode errorCode, String extraMsg) {
|
||||
super(extraMsg == null ? errorCode.getMsg() : errorCode.getMsg().concat(":").concat(extraMsg));
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package tech.powerjob.server.auth.common.utils;
|
||||
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* HttpServletUtils
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
public class HttpServletUtils {
|
||||
|
||||
public static String fetchFromHeader(String key, HttpServletRequest httpServletRequest) {
|
||||
// header、cookie 都能获取
|
||||
String v = httpServletRequest.getHeader(key);
|
||||
|
||||
// 解决 window.localStorage.getItem 为 null 的问题
|
||||
if (OmsConstant.NULL.equalsIgnoreCase(v) || "undefined".equalsIgnoreCase(v)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package tech.powerjob.server.auth.interceptor;
|
||||
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* API 权限
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ApiPermission {
|
||||
/**
|
||||
* API 名称
|
||||
* @return 空使用服务.方法名代替
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
RoleScope roleScope() default RoleScope.APP;
|
||||
|
||||
/**
|
||||
* 需要的权限
|
||||
* @return 权限
|
||||
*/
|
||||
Permission requiredPermission() default Permission.SU;
|
||||
|
||||
/**
|
||||
* 固定权限不支持的场景,需要使用动态权限
|
||||
* @return 动态权限
|
||||
*/
|
||||
Class<? extends DynamicPermissionPlugin> dynamicPermissionPlugin() default EmptyPlugin.class;
|
||||
|
||||
/**
|
||||
* 新增场景,需要授权插件执行授权
|
||||
* @return 授权插件
|
||||
*/
|
||||
Class<? extends GrantPermissionPlugin> grandPermissionPlugin() default EmptyPlugin.class;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package tech.powerjob.server.auth.interceptor;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* ApiPermission 切面
|
||||
* 主要用于执行授权插件,完成创建后授权
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class ApiPermissionAspect {
|
||||
|
||||
@Pointcut("@annotation(ApiPermission)")
|
||||
public void apiPermissionPointcut() {
|
||||
// 定义切入点
|
||||
}
|
||||
|
||||
/**
|
||||
* 后置返回
|
||||
* 如果第一个参数为JoinPoint,则第二个参数为返回值的信息
|
||||
* 如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
|
||||
* returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
|
||||
* 参数为Object类型将匹配任何目标返回值
|
||||
* After注解标注的方法会在目标方法执行后运行,无论目标方法是正常完成还是抛出异常。它相当于finally块,因为它总是执行,所以适用于释放资源等清理活动。@After注解不能访问目标方法的返回值。
|
||||
* AfterReturning注解标注的方法仅在目标方法成功执行后(即正常返回)运行。它可以访问目标方法的返回值。使用@AfterReturning可以在方法正常返回后执行一些逻辑,比如对返回值进行处理或验证。
|
||||
*/
|
||||
@AfterReturning(value = "apiPermissionPointcut()", returning = "result")
|
||||
public void doAfterReturningAdvice1(JoinPoint joinPoint, Object result) {
|
||||
|
||||
// 入参
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
// 获取目标方法
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
|
||||
ApiPermission annotationAnno = AnnotationUtils.getAnnotation(method, ApiPermission.class);
|
||||
|
||||
assert annotationAnno != null;
|
||||
|
||||
Class<? extends GrantPermissionPlugin> grandPermissionPluginClz = annotationAnno.grandPermissionPlugin();
|
||||
|
||||
try {
|
||||
GrantPermissionPlugin grandPermissionPlugin = grandPermissionPluginClz.getDeclaredConstructor().newInstance();
|
||||
grandPermissionPlugin.grant(args, result, method, joinPoint.getTarget());
|
||||
} catch (Exception e) {
|
||||
log.error("[ApiPermissionAspect] process ApiPermission grant failed", e);
|
||||
ExceptionUtils.rethrow(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package tech.powerjob.server.auth.interceptor;
|
||||
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 动态权限
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
public interface DynamicPermissionPlugin {
|
||||
Permission calculate(HttpServletRequest request, Object handler);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package tech.powerjob.server.auth.interceptor;
|
||||
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 空
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
public class EmptyPlugin implements DynamicPermissionPlugin, GrantPermissionPlugin {
|
||||
@Override
|
||||
public Permission calculate(HttpServletRequest request, Object handler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grant(Object[] args, Object result, Method method, Object originBean) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package tech.powerjob.server.auth.interceptor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 授予权限插件
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public interface GrantPermissionPlugin {
|
||||
|
||||
/**
|
||||
* 授权
|
||||
* @param args 入参
|
||||
* @param result 响应
|
||||
* @param method 被调用方法
|
||||
* @param originBean 原始对象
|
||||
*/
|
||||
void grant(Object[] args, Object result, Method method, Object originBean);
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package tech.powerjob.server.auth.interceptor;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import tech.powerjob.common.exception.ImpossibleException;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.server.auth.LoginUserHolder;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthErrorCode;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.common.utils.HttpServletUtils;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
|
||||
import tech.powerjob.server.common.Loggers;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* login auth and permission check
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/25
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PowerJobAuthInterceptor implements HandlerInterceptor {
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
@Resource
|
||||
private PowerJobPermissionService powerJobPermissionService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {
|
||||
if (!(handler instanceof HandlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
final Method method = handlerMethod.getMethod();
|
||||
final ApiPermission apiPermissionAnno = method.getAnnotation(ApiPermission.class);
|
||||
|
||||
// 无注解代表不需要权限,无需登陆直接访问
|
||||
if (apiPermissionAnno == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 尝试直接解析登陆
|
||||
final Optional<PowerJobUser> loginUserOpt = powerJobLoginService.ifLogin(request);
|
||||
|
||||
// 未登录直接报错,返回固定状态码,前端拦截后跳转到登录页
|
||||
if (!loginUserOpt.isPresent()) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
|
||||
}
|
||||
|
||||
// 登陆用户进行权限校验
|
||||
final PowerJobUser powerJobUser = loginUserOpt.get();
|
||||
|
||||
// 写入上下文
|
||||
LoginUserHolder.set(powerJobUser);
|
||||
|
||||
Permission requiredPermission = parsePermission(request, handler, apiPermissionAnno);
|
||||
RoleScope roleScope = apiPermissionAnno.roleScope();
|
||||
Long targetId = null;
|
||||
|
||||
if (RoleScope.NAMESPACE.equals(roleScope)) {
|
||||
|
||||
final String namespaceIdStr = HttpServletUtils.fetchFromHeader("NamespaceId", request);
|
||||
if (StringUtils.isNotEmpty(namespaceIdStr)) {
|
||||
targetId = Long.valueOf(namespaceIdStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (RoleScope.APP.equals(roleScope)) {
|
||||
final String appIdStr = HttpServletUtils.fetchFromHeader("AppId", request);
|
||||
if (StringUtils.isNotEmpty(appIdStr)) {
|
||||
targetId = Long.valueOf(appIdStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final boolean hasPermission = powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, targetId, requiredPermission);
|
||||
if (hasPermission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String resourceName = parseResourceName(apiPermissionAnno, handlerMethod);
|
||||
Loggers.WEB.info("[PowerJobAuthInterceptor] user[{}] has no permission to access: {}", powerJobUser.getUsername(), resourceName);
|
||||
|
||||
throw new PowerJobException("Permission denied!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) throws Exception {
|
||||
LoginUserHolder.clean();
|
||||
}
|
||||
|
||||
private static String parseResourceName(ApiPermission apiPermission, HandlerMethod handlerMethod) {
|
||||
final String name = apiPermission.name();
|
||||
if (StringUtils.isNotEmpty(name)) {
|
||||
return name;
|
||||
}
|
||||
try {
|
||||
final String clzName = handlerMethod.getBean().getClass().getSimpleName();
|
||||
final String methodName = handlerMethod.getMethod().getName();
|
||||
return String.format("%s_%s", clzName, methodName);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
private static Permission parsePermission(HttpServletRequest request, Object handler, ApiPermission apiPermission) {
|
||||
Class<? extends DynamicPermissionPlugin> dynamicPermissionPlugin = apiPermission.dynamicPermissionPlugin();
|
||||
if (EmptyPlugin.class.equals(dynamicPermissionPlugin)) {
|
||||
return apiPermission.requiredPermission();
|
||||
}
|
||||
try {
|
||||
DynamicPermissionPlugin dynamicPermission = dynamicPermissionPlugin.getDeclaredConstructor().newInstance();
|
||||
return dynamicPermission.calculate(request, handler);
|
||||
} catch (Throwable t) {
|
||||
log.error("[PowerJobAuthService] process dynamicPermissionPlugin failed!", t);
|
||||
ExceptionUtils.rethrow(t);
|
||||
}
|
||||
throw new ImpossibleException();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package tech.powerjob.server.auth.jwt;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT 服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
public interface JwtService {
|
||||
|
||||
String build(Map<String, Object> body, String extraSk);
|
||||
|
||||
Map<String, Object> parse(String jwt, String extraSk);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package tech.powerjob.server.auth.jwt;
|
||||
|
||||
/**
|
||||
* JWT 安全性的核心
|
||||
* 对安全性有要求的接入方,可以自行重新该方法,自定义自己的安全 token 生成策略
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
public interface SecretProvider {
|
||||
|
||||
String fetchSecretKey();
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package tech.powerjob.server.auth.jwt.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.powerjob.server.auth.jwt.SecretProvider;
|
||||
import tech.powerjob.server.common.utils.DigestUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* PowerJob 默认实现
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DefaultSecretProvider implements SecretProvider {
|
||||
|
||||
@Resource
|
||||
private Environment environment;
|
||||
|
||||
private static final String PROPERTY_KEY = "spring.datasource.core.jdbc-url";
|
||||
|
||||
@Override
|
||||
public String fetchSecretKey() {
|
||||
|
||||
// 考虑到大部分用户都是开箱即用,此处还是提供一个相对安全的默认实现。JDBC URL 部署时必会改,skey 不固定,更安全
|
||||
try {
|
||||
String propertyValue = environment.getProperty(PROPERTY_KEY);
|
||||
if (StringUtils.isNotEmpty(propertyValue)) {
|
||||
String md5 = DigestUtils.md5(propertyValue);
|
||||
|
||||
log.debug("[DefaultSecretProvider] propertyValue: {} ==> md5: {}", propertyValue, md5);
|
||||
|
||||
if (StringUtils.isNotEmpty(md5)) {
|
||||
return md5;
|
||||
}
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return "ZQQZJ";
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package tech.powerjob.server.auth.jwt.impl;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.server.auth.jwt.JwtService;
|
||||
import tech.powerjob.server.auth.jwt.SecretProvider;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* JWT 默认实现
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Service
|
||||
public class JwtServiceImpl implements JwtService {
|
||||
|
||||
@Resource
|
||||
private SecretProvider secretProvider;
|
||||
|
||||
/**
|
||||
* JWT 客户端过期时间
|
||||
*/
|
||||
@Value("${oms.auth.security.jwt.expire-seconds:604800}")
|
||||
private int jwtExpireTime;
|
||||
|
||||
/**
|
||||
* <a href="https://music.163.com/#/song?id=167975">GoodSong</a>
|
||||
*/
|
||||
private static final String BASE_SECURITY =
|
||||
"CengMengXiangZhangJianZouTianYa" +
|
||||
"KanYiKanShiJieDeFanHua" +
|
||||
"NianShaoDeXinZongYouXieQingKuang" +
|
||||
"RuJinWoSiHaiWeiJia"
|
||||
;
|
||||
|
||||
@Override
|
||||
public String build(Map<String, Object> body, String extraSk) {
|
||||
|
||||
final String secret = fetchSk(extraSk);
|
||||
return innerBuild(secret, jwtExpireTime, body);
|
||||
}
|
||||
|
||||
static String innerBuild(String secret, int expireSeconds, Map<String, Object> body) {
|
||||
JwtBuilder jwtBuilder = Jwts.builder()
|
||||
.setHeaderParam("typ", "JWT")
|
||||
.addClaims(body)
|
||||
.setSubject("PowerJob")
|
||||
.setExpiration(new Date(System.currentTimeMillis() + 1000L * expireSeconds))
|
||||
.setId(UUID.randomUUID().toString())
|
||||
.signWith(genSecretKey(secret), SignatureAlgorithm.HS256);
|
||||
return jwtBuilder.compact();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> parse(String jwt, String extraSk) {
|
||||
return innerParse(fetchSk(extraSk), jwt);
|
||||
}
|
||||
|
||||
private String fetchSk(String extraSk) {
|
||||
if (StringUtils.isEmpty(extraSk)) {
|
||||
return secretProvider.fetchSecretKey();
|
||||
}
|
||||
return secretProvider.fetchSecretKey().concat(extraSk);
|
||||
}
|
||||
|
||||
static Map<String, Object> innerParse(String secret, String jwtStr) {
|
||||
final Jws<Claims> claimsJws = Jwts.parserBuilder()
|
||||
.setSigningKey(genSecretKey(secret))
|
||||
.build()
|
||||
.parseClaimsJws(jwtStr);
|
||||
Map<String, Object> ret = Maps.newHashMap();
|
||||
ret.putAll(claimsJws.getBody());
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Key genSecretKey(String secret) {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(BASE_SECURITY.concat(secret));
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录类型描述
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class LoginTypeInfo implements Serializable {
|
||||
|
||||
/**
|
||||
* 登录类型,唯一标识
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* 描述名称,前端展示用
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 展示用的 ICON
|
||||
*/
|
||||
private String iconUrl;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 第三方登录请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ThirdPartyLoginRequest {
|
||||
|
||||
/**
|
||||
* 原始参数,给第三方登录方式一个服务端和前端交互的数据通道。PowerJob 本身不感知其中的内容
|
||||
*/
|
||||
private String originParams;
|
||||
|
||||
private transient HttpServletRequest httpServletRequest;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 第三方登录服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
public interface ThirdPartyLoginService {
|
||||
|
||||
/**
|
||||
* 登陆服务的类型
|
||||
* @return 登陆服务类型,比如 PowerJob / DingTalk
|
||||
*/
|
||||
LoginTypeInfo loginType();
|
||||
|
||||
/**
|
||||
* 生成登陆的重定向 URL
|
||||
* @param httpServletRequest http请求
|
||||
* @return 重定向地址
|
||||
*/
|
||||
String generateLoginUrl(HttpServletRequest httpServletRequest);
|
||||
|
||||
/**
|
||||
* 执行第三方登录
|
||||
* @param loginRequest 上下文
|
||||
* @return 登录地址
|
||||
*/
|
||||
ThirdPartyUser login(ThirdPartyLoginRequest loginRequest);
|
||||
|
||||
/**
|
||||
* JWT 登录的回调校验
|
||||
* @param username 用户名称
|
||||
* @param tokenLoginVerifyInfo 二次校验信息
|
||||
* @return 是否通过
|
||||
*/
|
||||
default boolean tokenLoginVerify(String username, TokenLoginVerifyInfo tokenLoginVerifyInfo) {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 第三方用户
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ThirdPartyUser {
|
||||
|
||||
/**
|
||||
* 用户的唯一标识,用于关联到 PowerJob 的 username
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* JWT 登录的二次校验配置
|
||||
* 可空,空则代表放弃二次校验(会出现第三方登录改了密码当 PowerJob JWT 登录依然可用的情况)
|
||||
*/
|
||||
private TokenLoginVerifyInfo tokenLoginVerifyInfo;
|
||||
|
||||
/* ******** 以下全部选填即可,只是方便数据同步,后续都可以去 PowerJob 控制台更改 ******** */
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nick;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
/**
|
||||
* web 回调地址
|
||||
*/
|
||||
private String webHook;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* JWT 登录时的校验信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/16
|
||||
*/
|
||||
@Data
|
||||
public class TokenLoginVerifyInfo implements Serializable {
|
||||
|
||||
/**
|
||||
* 加密 token 部分,比如密码的 md5,会直接写入 JWT 下发给前端
|
||||
* 如果需要使用 JWT 二次校验,则该参数必须存在
|
||||
*/
|
||||
private String encryptedToken;
|
||||
|
||||
/**
|
||||
* 补充信息,用于二次校验
|
||||
*/
|
||||
private String additionalInfo;
|
||||
|
||||
/**
|
||||
* 依然是预留字段,第三方实现自用即可
|
||||
*/
|
||||
private String extra;
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package tech.powerjob.server.auth.login.impl;
|
||||
|
||||
import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders;
|
||||
import com.aliyun.dingtalkcontact_1_0.models.GetUserResponseBody;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenRequest;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponse;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.login.*;
|
||||
import tech.powerjob.server.common.Loggers;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* <a href="https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information">钉钉账号体系登录第三方网站</a>
|
||||
* PowerJob 官方支持钉钉账号体系登录原因:
|
||||
* 1. 钉钉作为当下用户体量最大的企业级办公软件,覆盖率足够高,提供钉钉支持能让更多开发者开箱即用
|
||||
* 2. 钉钉的 API 设计和 PowerJob 设想一致,算是个最佳实践,其他企业内部的账号体系可参考这套流程进行接入
|
||||
* - PowerJob 重定向到第三方账号体系登陆页 -> 第三方完成登陆 -> 跳转回调 PowerJob auth 接口 -> PowerJob 解析回调登陆信息,完整用户关联
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/26
|
||||
*/
|
||||
@Service
|
||||
public class DingTalkLoginService implements ThirdPartyLoginService {
|
||||
|
||||
/*
|
||||
配置示例
|
||||
oms.auth.dingtalk.appkey=dinggzqqzqqzqqzqq
|
||||
oms.auth.dingtalk.appSecret=iY-FS8mzqqzqq_xEizqqzqqzqqzqqzqqzqqYEbkZOal
|
||||
oms.auth.dingtalk.callbackUrl=http://localhost:7700
|
||||
*/
|
||||
|
||||
/**
|
||||
* 钉钉应用 AppKey
|
||||
*/
|
||||
@Value("${oms.auth.dingtalk.appkey:#{null}}")
|
||||
private String dingTalkAppKey;
|
||||
/**
|
||||
* 钉钉应用 AppSecret
|
||||
*/
|
||||
@Value("${oms.auth.dingtalk.appSecret:#{null}}")
|
||||
private String dingTalkAppSecret;
|
||||
/**
|
||||
* 回调地址,powerjob 前端控制台地址,即 powerjob-console 地址
|
||||
* 比如本地调试时为 <a href="http://localhost:7700">LocalDemoCallbackUrl</a>
|
||||
* 部署后则为 <a href="http://try.powerjob.tech">demoCallBackUrl</a>
|
||||
*/
|
||||
@Value("${oms.auth.dingtalk.callbackUrl:#{null}}")
|
||||
private String dingTalkCallbackUrl;
|
||||
|
||||
@Override
|
||||
public LoginTypeInfo loginType() {
|
||||
return new LoginTypeInfo()
|
||||
.setType(AuthConstants.ACCOUNT_TYPE_DING)
|
||||
.setName("DingTalk")
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String generateLoginUrl(HttpServletRequest httpServletRequest) {
|
||||
if (StringUtils.isAnyEmpty(dingTalkAppKey, dingTalkAppSecret, dingTalkCallbackUrl)) {
|
||||
throw new IllegalArgumentException("please config 'oms.auth.dingtalk.appkey', 'oms.auth.dingtalk.appSecret' and 'oms.auth.dingtalk.callbackUrl' in properties!");
|
||||
}
|
||||
|
||||
String urlString = URLEncoder.encode(dingTalkCallbackUrl, StandardCharsets.UTF_8.name());
|
||||
String url = "https://login.dingtalk.com/oauth2/auth?" +
|
||||
"redirect_uri=" + urlString +
|
||||
"&response_type=code" +
|
||||
"&client_id=" + dingTalkAppKey +
|
||||
"&scope=openid" +
|
||||
"&state=" + AuthConstants.ACCOUNT_TYPE_DING +
|
||||
"&prompt=consent";
|
||||
Loggers.WEB.info("[DingTalkBizLoginService] login url: {}", url);
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public ThirdPartyUser login(ThirdPartyLoginRequest loginRequest) {
|
||||
try {
|
||||
com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
|
||||
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
|
||||
//应用基础信息-应用信息的AppKey,请务必替换为开发的应用AppKey
|
||||
.setClientId(dingTalkAppKey)
|
||||
//应用基础信息-应用信息的AppSecret,,请务必替换为开发的应用AppSecret
|
||||
.setClientSecret(dingTalkAppSecret)
|
||||
.setCode(loginRequest.getHttpServletRequest().getParameter("authCode"))
|
||||
.setGrantType("authorization_code");
|
||||
GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest);
|
||||
//获取用户个人 token
|
||||
String accessToken = getUserTokenResponse.getBody().getAccessToken();
|
||||
// 查询钉钉用户
|
||||
final GetUserResponseBody dingUser = getUserinfo(accessToken);
|
||||
// 将钉钉用户的唯一ID 和 PowerJob 账户体系的唯一键 username 关联
|
||||
if (dingUser != null) {
|
||||
ThirdPartyUser bizUser = new ThirdPartyUser();
|
||||
bizUser.setUsername(dingUser.getUnionId());
|
||||
bizUser.setNick(dingUser.getNick());
|
||||
bizUser.setPhone(dingUser.getMobile());
|
||||
bizUser.setEmail(dingUser.getEmail());
|
||||
return bizUser;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Loggers.WEB.error("[DingTalkBizLoginService] login by dingTalk failed!", e);
|
||||
throw e;
|
||||
}
|
||||
throw new PowerJobException("login from dingTalk failed!");
|
||||
}
|
||||
|
||||
/* 以下代码均拷自钉钉官网示例 */
|
||||
|
||||
private static com.aliyun.dingtalkoauth2_1_0.Client authClient() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new com.aliyun.dingtalkoauth2_1_0.Client(config);
|
||||
}
|
||||
private static com.aliyun.dingtalkcontact_1_0.Client contactClient() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new com.aliyun.dingtalkcontact_1_0.Client(config);
|
||||
}
|
||||
|
||||
private GetUserResponseBody getUserinfo(String accessToken) throws Exception {
|
||||
com.aliyun.dingtalkcontact_1_0.Client client = contactClient();
|
||||
GetUserHeaders getUserHeaders = new GetUserHeaders();
|
||||
getUserHeaders.xAcsDingtalkAccessToken = accessToken;
|
||||
//获取用户个人信息,如需获取当前授权人的信息,unionId参数必须传me
|
||||
return client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions()).getBody();
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package tech.powerjob.server.auth.login.impl;
|
||||
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.common.AuthErrorCode;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.login.*;
|
||||
import tech.powerjob.server.common.Loggers;
|
||||
import tech.powerjob.server.common.utils.DigestUtils;
|
||||
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PowerJob 自带的登陆服务
|
||||
* 和应用主框架无关,依然属于第三方登录体系
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Service
|
||||
public class PwjbAccountLoginService implements ThirdPartyLoginService {
|
||||
|
||||
@Resource
|
||||
private PwjbUserInfoRepository pwjbUserInfoRepository;
|
||||
|
||||
|
||||
@Override
|
||||
public LoginTypeInfo loginType() {
|
||||
return new LoginTypeInfo()
|
||||
.setType(AuthConstants.ACCOUNT_TYPE_POWER_JOB)
|
||||
.setName("PowerJob Account")
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateLoginUrl(HttpServletRequest httpServletRequest) {
|
||||
// 前端实现跳转,服务端返回特殊指令
|
||||
return AuthConstants.FE_REDIRECT_KEY.concat("powerjobLogin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThirdPartyUser login(ThirdPartyLoginRequest loginRequest) {
|
||||
final String loginInfo = loginRequest.getOriginParams();
|
||||
if (StringUtils.isEmpty(loginInfo)) {
|
||||
throw new IllegalArgumentException("can't find login Info");
|
||||
}
|
||||
|
||||
Map<String, Object> loginInfoMap = JsonUtils.parseMap(loginInfo);
|
||||
|
||||
final String username = MapUtils.getString(loginInfoMap, AuthConstants.PARAM_KEY_USERNAME);
|
||||
final String password = MapUtils.getString(loginInfoMap, AuthConstants.PARAM_KEY_PASSWORD);
|
||||
final String encryption = MapUtils.getString(loginInfoMap, AuthConstants.PARAM_KEY_ENCRYPTION);
|
||||
|
||||
Loggers.WEB.debug("[PowerJobLoginService] username: {}, password: {}, encryption: {}", username, password, encryption);
|
||||
|
||||
if (StringUtils.isAnyEmpty(username, password)) {
|
||||
Loggers.WEB.debug("[PowerJobLoginService] username or password is empty, login failed!");
|
||||
throw new PowerJobAuthException(AuthErrorCode.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
final Optional<PwjbUserInfoDO> userInfoOpt = pwjbUserInfoRepository.findByUsername(username);
|
||||
if (!userInfoOpt.isPresent()) {
|
||||
Loggers.WEB.debug("[PowerJobLoginService] can't find user by username: {}", username);
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_EXIST);
|
||||
}
|
||||
|
||||
final PwjbUserInfoDO dbUser = userInfoOpt.get();
|
||||
|
||||
if (DigestUtils.rePassword(password, username).equals(dbUser.getPassword())) {
|
||||
ThirdPartyUser bizUser = new ThirdPartyUser();
|
||||
bizUser.setUsername(username);
|
||||
|
||||
// 回填第一次创建的信息
|
||||
String extra = dbUser.getExtra();
|
||||
if (StringUtils.isNotEmpty(extra)) {
|
||||
ThirdPartyUser material = JsonUtils.parseObjectIgnoreException(extra, ThirdPartyUser.class);
|
||||
if (material != null) {
|
||||
bizUser.setEmail(material.getEmail());
|
||||
bizUser.setNick(material.getNick());
|
||||
bizUser.setPhone(material.getPhone());
|
||||
bizUser.setWebHook(material.getWebHook());
|
||||
}
|
||||
}
|
||||
|
||||
// 下发加密的密码作为 JWT 的一部分,方便处理改密码后失效的场景
|
||||
TokenLoginVerifyInfo tokenLoginVerifyInfo = new TokenLoginVerifyInfo();
|
||||
tokenLoginVerifyInfo.setEncryptedToken(dbUser.getPassword());
|
||||
bizUser.setTokenLoginVerifyInfo(tokenLoginVerifyInfo);
|
||||
|
||||
return bizUser;
|
||||
}
|
||||
|
||||
Loggers.WEB.debug("[PowerJobLoginService] user[{}]'s password is incorrect, login failed!", username);
|
||||
throw new PowerJobException("password is incorrect");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tokenLoginVerify(String username, TokenLoginVerifyInfo tokenLoginVerifyInfo) {
|
||||
|
||||
if (tokenLoginVerifyInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Optional<PwjbUserInfoDO> userInfoOpt = pwjbUserInfoRepository.findByUsername(username);
|
||||
if (userInfoOpt.isPresent()) {
|
||||
String dbPassword = userInfoOpt.get().getPassword();
|
||||
return StringUtils.equals(dbPassword, tokenLoginVerifyInfo.getEncryptedToken());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package tech.powerjob.server.auth.service.login;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 执行登录的请求
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class LoginRequest {
|
||||
|
||||
/**
|
||||
* 登录类型
|
||||
*/
|
||||
private String loginType;
|
||||
|
||||
/**
|
||||
* 原始参数,给第三方登录方式一个服务端和前端交互的数据通道。PowerJob 本身不感知其中的内容
|
||||
*/
|
||||
private String originParams;
|
||||
|
||||
/**
|
||||
* http原始请求,第三方回调参数传递类型无法枚举,直接传递 HttpServletRequest 满足扩展性要求
|
||||
*/
|
||||
private transient HttpServletRequest httpServletRequest;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package tech.powerjob.server.auth.service.login;
|
||||
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.login.LoginTypeInfo;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PowerJob 登录服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
public interface PowerJobLoginService {
|
||||
|
||||
/**
|
||||
* 获取全部可登录的类型
|
||||
* @return 全部可登录类型
|
||||
*/
|
||||
List<LoginTypeInfo> fetchSupportLoginTypes();
|
||||
|
||||
|
||||
/**
|
||||
* 获取第三方登录链接
|
||||
* @param loginType 登录类型
|
||||
* @param httpServletRequest http请求
|
||||
* @return 重定向地址
|
||||
*/
|
||||
String fetchThirdPartyLoginUrl(String loginType, HttpServletRequest httpServletRequest);
|
||||
|
||||
/**
|
||||
* 执行真正的登录请求,底层调用第三方登录服务完成登录
|
||||
* @param loginRequest 登录请求
|
||||
* @return 登录完成的 PowerJobUser
|
||||
* @throws PowerJobAuthException 鉴权失败抛出异常
|
||||
*/
|
||||
PowerJobUser doLogin(LoginRequest loginRequest) throws PowerJobAuthException;
|
||||
|
||||
/**
|
||||
* 从 JWT 信息中解析用户登录信息
|
||||
* @param httpServletRequest httpServletRequest
|
||||
* @return PowerJob 用户
|
||||
*/
|
||||
Optional<PowerJobUser> ifLogin(HttpServletRequest httpServletRequest);
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
package tech.powerjob.server.auth.service.login.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.LoginUserHolder;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.common.AuthErrorCode;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.common.utils.HttpServletUtils;
|
||||
import tech.powerjob.server.auth.jwt.JwtService;
|
||||
import tech.powerjob.server.auth.login.*;
|
||||
import tech.powerjob.server.auth.service.login.LoginRequest;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.server.common.Loggers;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* PowerJob 登录服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PowerJobLoginServiceImpl implements PowerJobLoginService {
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final UserInfoRepository userInfoRepository;
|
||||
private final Map<String, ThirdPartyLoginService> code2ThirdPartyLoginService;
|
||||
|
||||
@Autowired
|
||||
public PowerJobLoginServiceImpl(JwtService jwtService, UserInfoRepository userInfoRepository, List<ThirdPartyLoginService> thirdPartyLoginServices) {
|
||||
|
||||
this.jwtService = jwtService;
|
||||
this.userInfoRepository = userInfoRepository;
|
||||
|
||||
code2ThirdPartyLoginService = Maps.newHashMap();
|
||||
thirdPartyLoginServices.forEach(s -> {
|
||||
code2ThirdPartyLoginService.put(s.loginType().getType(), s);
|
||||
log.info("[PowerJobLoginService] register ThirdPartyLoginService: {}", s.loginType());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoginTypeInfo> fetchSupportLoginTypes() {
|
||||
return Lists.newArrayList(code2ThirdPartyLoginService.values()).stream().map(ThirdPartyLoginService::loginType).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fetchThirdPartyLoginUrl(String type, HttpServletRequest httpServletRequest) {
|
||||
final ThirdPartyLoginService thirdPartyLoginService = fetchBizLoginService(type);
|
||||
return thirdPartyLoginService.generateLoginUrl(httpServletRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PowerJobUser doLogin(LoginRequest loginRequest) throws PowerJobAuthException {
|
||||
final String loginType = loginRequest.getLoginType();
|
||||
final ThirdPartyLoginService thirdPartyLoginService = fetchBizLoginService(loginType);
|
||||
|
||||
ThirdPartyLoginRequest thirdPartyLoginRequest = new ThirdPartyLoginRequest()
|
||||
.setOriginParams(loginRequest.getOriginParams())
|
||||
.setHttpServletRequest(loginRequest.getHttpServletRequest());
|
||||
|
||||
final ThirdPartyUser bizUser = thirdPartyLoginService.login(thirdPartyLoginRequest);
|
||||
|
||||
String dbUserName = String.format("%s_%s", loginType, bizUser.getUsername());
|
||||
Optional<UserInfoDO> powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
|
||||
|
||||
// 如果不存在用户,先同步创建用户
|
||||
if (!powerJobUserOpt.isPresent()) {
|
||||
UserInfoDO newUser = new UserInfoDO();
|
||||
newUser.setUsername(dbUserName);
|
||||
// 写入账号体系类型
|
||||
newUser.setAccountType(loginType);
|
||||
newUser.setOriginUsername(bizUser.getUsername());
|
||||
|
||||
newUser.setTokenLoginVerifyInfo(JsonUtils.toJSONString(bizUser.getTokenLoginVerifyInfo()));
|
||||
|
||||
// 同步素材
|
||||
newUser.setEmail(bizUser.getEmail());
|
||||
newUser.setPhone(bizUser.getPhone());
|
||||
newUser.setNick(bizUser.getNick());
|
||||
newUser.setWebHook(bizUser.getWebHook());
|
||||
newUser.setExtra(bizUser.getExtra());
|
||||
|
||||
Loggers.WEB.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName);
|
||||
userInfoRepository.saveAndFlush(newUser);
|
||||
|
||||
powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
|
||||
} else {
|
||||
|
||||
// 更新二次校验的 TOKEN 信息
|
||||
UserInfoDO dbUserInfoDO = powerJobUserOpt.get();
|
||||
|
||||
dbUserInfoDO.setTokenLoginVerifyInfo(JsonUtils.toJSONString(bizUser.getTokenLoginVerifyInfo()));
|
||||
dbUserInfoDO.setGmtModified(new Date());
|
||||
|
||||
userInfoRepository.saveAndFlush(dbUserInfoDO);
|
||||
}
|
||||
|
||||
PowerJobUser ret = new PowerJobUser();
|
||||
|
||||
// 理论上 100% 存在
|
||||
if (powerJobUserOpt.isPresent()) {
|
||||
final UserInfoDO dbUser = powerJobUserOpt.get();
|
||||
BeanUtils.copyProperties(dbUser, ret);
|
||||
ret.setUsername(dbUserName);
|
||||
}
|
||||
|
||||
fillJwt(ret, Optional.ofNullable(bizUser.getTokenLoginVerifyInfo()).map(TokenLoginVerifyInfo::getEncryptedToken).orElse(null));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PowerJobUser> ifLogin(HttpServletRequest httpServletRequest) {
|
||||
final Optional<JwtBody> jwtBodyOpt = parseJwt(httpServletRequest);
|
||||
if (!jwtBodyOpt.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
JwtBody jwtBody = jwtBodyOpt.get();
|
||||
|
||||
Optional<UserInfoDO> dbUserInfoOpt = userInfoRepository.findByUsername(jwtBody.getUsername());
|
||||
if (!dbUserInfoOpt.isPresent()) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_EXIST);
|
||||
}
|
||||
|
||||
UserInfoDO dbUser = dbUserInfoOpt.get();
|
||||
|
||||
PowerJobUser powerJobUser = new PowerJobUser();
|
||||
|
||||
String tokenLoginVerifyInfoStr = dbUser.getTokenLoginVerifyInfo();
|
||||
TokenLoginVerifyInfo tokenLoginVerifyInfo = Optional.ofNullable(tokenLoginVerifyInfoStr).map(x -> JsonUtils.parseObjectIgnoreException(x, TokenLoginVerifyInfo.class)).orElse(new TokenLoginVerifyInfo());
|
||||
|
||||
// DB 中的 encryptedToken 存在,代表需要二次校验
|
||||
if (StringUtils.isNotEmpty(tokenLoginVerifyInfo.getEncryptedToken())) {
|
||||
if (!StringUtils.equals(jwtBody.getEncryptedToken(), tokenLoginVerifyInfo.getEncryptedToken())) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
ThirdPartyLoginService thirdPartyLoginService = code2ThirdPartyLoginService.get(dbUser.getAccountType());
|
||||
boolean tokenLoginVerifyOk = thirdPartyLoginService.tokenLoginVerify(dbUser.getOriginUsername(), tokenLoginVerifyInfo);
|
||||
|
||||
if (!tokenLoginVerifyOk) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_AUTH_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
BeanUtils.copyProperties(dbUser, powerJobUser);
|
||||
|
||||
// 兼容某些直接通过 ifLogin 判断登录的场景
|
||||
LoginUserHolder.set(powerJobUser);
|
||||
|
||||
return Optional.of(powerJobUser);
|
||||
}
|
||||
|
||||
private ThirdPartyLoginService fetchBizLoginService(String loginType) {
|
||||
final ThirdPartyLoginService loginService = code2ThirdPartyLoginService.get(loginType);
|
||||
if (loginService == null) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.INVALID_REQUEST, "can't find ThirdPartyLoginService by type: " + loginType);
|
||||
}
|
||||
return loginService;
|
||||
}
|
||||
|
||||
private void fillJwt(PowerJobUser powerJobUser, String encryptedToken) {
|
||||
|
||||
// 不能下发 userId,容易被轮询爆破
|
||||
JwtBody jwtBody = new JwtBody();
|
||||
jwtBody.setUsername(powerJobUser.getUsername());
|
||||
if (StringUtils.isNotEmpty(encryptedToken)) {
|
||||
jwtBody.setEncryptedToken(encryptedToken);
|
||||
}
|
||||
|
||||
Map<String, Object> jwtMap = JsonUtils.parseMap(JsonUtils.toJSONString(jwtBody));
|
||||
|
||||
powerJobUser.setJwtToken(jwtService.build(jwtMap, null));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Optional<JwtBody> parseJwt(HttpServletRequest httpServletRequest) {
|
||||
// header、cookie 都能获取
|
||||
String jwtStr = HttpServletUtils.fetchFromHeader(AuthConstants.JWT_NAME, httpServletRequest);
|
||||
|
||||
/*
|
||||
|
||||
开发阶段跨域无法简单传输 cookies,暂时采取 header 方案传输 JWT
|
||||
|
||||
if (StringUtils.isEmpty(jwtStr)) {
|
||||
for (Cookie cookie : Optional.ofNullable(httpServletRequest.getCookies()).orElse(new Cookie[]{})) {
|
||||
if (cookie.getName().equals(AuthConstants.JWT_NAME)) {
|
||||
jwtStr = cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (StringUtils.isEmpty(jwtStr)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
final Map<String, Object> jwtBodyMap = jwtService.parse(jwtStr, null);
|
||||
|
||||
if (MapUtils.isEmpty(jwtBodyMap)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(JsonUtils.parseObject(JsonUtils.toJSONString(jwtBodyMap), JwtBody.class));
|
||||
}
|
||||
|
||||
@Data
|
||||
static class JwtBody implements Serializable {
|
||||
|
||||
private String username;
|
||||
|
||||
private String encryptedToken;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package tech.powerjob.server.auth.service.permission;
|
||||
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* PowerJob 鉴权服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public interface PowerJobPermissionService {
|
||||
|
||||
|
||||
/**
|
||||
* 判断用户是否有访问权限
|
||||
* @param userId userId
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标ID
|
||||
* @param permission 要求的权限
|
||||
* @return 是否有权限
|
||||
*/
|
||||
boolean hasPermission(Long userId, RoleScope roleScope, Long target, Permission permission);
|
||||
|
||||
/**
|
||||
* 授予用户角色
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @param userId 用户ID
|
||||
* @param role 角色
|
||||
* @param extra 其他
|
||||
*/
|
||||
void grantRole(RoleScope roleScope, Long target, Long userId, Role role, String extra);
|
||||
|
||||
/**
|
||||
* 回收用户角色
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @param userId 用户ID
|
||||
* @param role 角色
|
||||
*/
|
||||
void retrieveRole(RoleScope roleScope, Long target, Long userId, Role role);
|
||||
|
||||
/**
|
||||
* 获取有相关权限的用户
|
||||
* @param roleScope 角色范围
|
||||
* @param target 目标
|
||||
* @return 角色对应的用户列表
|
||||
*/
|
||||
Map<Role, List<Long>> fetchUserWithPermissions(RoleScope roleScope, Long target);
|
||||
|
||||
/**
|
||||
* 获取用户有权限的目标
|
||||
* @param roleScope 角色范围
|
||||
* @param userId 用户ID
|
||||
* @return result
|
||||
*/
|
||||
Map<Role, List<Long>> fetchUserHadPermissionTargets(RoleScope roleScope, Long userId);
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package tech.powerjob.server.auth.service.permission;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.UserRoleDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserRoleRepository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* PowerJobPermissionService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PowerJobPermissionServiceImpl implements PowerJobPermissionService {
|
||||
|
||||
@Resource
|
||||
private AppInfoRepository appInfoRepository;
|
||||
@Resource
|
||||
private UserRoleRepository userRoleRepository;
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Long userId, RoleScope roleScope, Long target, Permission requiredPermission) {
|
||||
final List<UserRoleDO> userRoleList = Optional.ofNullable(userRoleRepository.findAllByUserId(userId)).orElse(Collections.emptyList());
|
||||
|
||||
Multimap<Long, Role> appId2Role = ArrayListMultimap.create();
|
||||
Multimap<Long, Role> namespaceId2Role = ArrayListMultimap.create();
|
||||
|
||||
List<Role> globalRoles = Lists.newArrayList();
|
||||
|
||||
for (UserRoleDO userRole : userRoleList) {
|
||||
final Role role = Role.of(userRole.getRole());
|
||||
|
||||
// 处理全局权限
|
||||
if (RoleScope.GLOBAL.getV() == userRole.getScope()) {
|
||||
if (Role.ADMIN.equals(role)) {
|
||||
return true;
|
||||
}
|
||||
globalRoles.add(role);
|
||||
}
|
||||
|
||||
if (RoleScope.NAMESPACE.getV() == userRole.getScope()) {
|
||||
namespaceId2Role.put(userRole.getTarget(), role);
|
||||
}
|
||||
if (RoleScope.APP.getV() == userRole.getScope()) {
|
||||
appId2Role.put(userRole.getTarget(), role);
|
||||
}
|
||||
}
|
||||
|
||||
// 前置判断需要的权限(新增场景还没有 appId or namespaceId)
|
||||
if (requiredPermission == Permission.NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检验全局穿透权限
|
||||
for (Role role : globalRoles) {
|
||||
if (role.getPermissions().contains(requiredPermission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 无超级管理员权限,校验普通权限
|
||||
if (RoleScope.APP.equals(roleScope)) {
|
||||
return checkAppPermission(target, requiredPermission, appId2Role, namespaceId2Role);
|
||||
}
|
||||
|
||||
if (RoleScope.NAMESPACE.equals(roleScope)) {
|
||||
return checkNamespacePermission(target, requiredPermission, namespaceId2Role);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleScope roleScope, Long target, Long userId, Role role, String extra) {
|
||||
|
||||
UserRoleDO userRoleDO = new UserRoleDO();
|
||||
userRoleDO.setGmtCreate(new Date());
|
||||
userRoleDO.setGmtModified(new Date());
|
||||
userRoleDO.setExtra(extra);
|
||||
|
||||
userRoleDO.setScope(roleScope.getV());
|
||||
userRoleDO.setTarget(target);
|
||||
userRoleDO.setUserId(userId);
|
||||
userRoleDO.setRole(role.getV());
|
||||
|
||||
userRoleRepository.saveAndFlush(userRoleDO);
|
||||
log.info("[PowerJobPermissionService] [grantPermission] saveAndFlush userRole successfully: {}", userRoleDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retrieveRole(RoleScope roleScope, Long target, Long userId, Role role) {
|
||||
List<UserRoleDO> originUserRole = userRoleRepository.findAllByScopeAndTargetAndRoleAndUserId(roleScope.getV(), target, role.getV(), userId);
|
||||
log.info("[PowerJobPermissionService] [retrievePermission] origin rule: {}", originUserRole);
|
||||
Optional.ofNullable(originUserRole).orElse(Collections.emptyList()).forEach(r -> {
|
||||
userRoleRepository.deleteById(r.getId());
|
||||
log.info("[PowerJobPermissionService] [retrievePermission] delete UserRole: {}", r);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Role, List<Long>> fetchUserWithPermissions(RoleScope roleScope, Long target) {
|
||||
List<UserRoleDO> permissionUserList = userRoleRepository.findAllByScopeAndTarget(roleScope.getV(), target);
|
||||
Map<Role, List<Long>> ret = Maps.newHashMap();
|
||||
Optional.ofNullable(permissionUserList).orElse(Collections.emptyList()).forEach(userRoleDO -> {
|
||||
Role role = Role.of(userRoleDO.getRole());
|
||||
List<Long> userIds = ret.computeIfAbsent(role, ignore -> Lists.newArrayList());
|
||||
userIds.add(userRoleDO.getUserId());
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Role, List<Long>> fetchUserHadPermissionTargets(RoleScope roleScope, Long userId) {
|
||||
|
||||
Map<Role, List<Long>> ret = Maps.newHashMap();
|
||||
List<UserRoleDO> userRoleDOList = userRoleRepository.findAllByUserIdAndScope(userId, roleScope.getV());
|
||||
|
||||
Optional.ofNullable(userRoleDOList).orElse(Collections.emptyList()).forEach(r -> {
|
||||
Role role = Role.of(r.getRole());
|
||||
List<Long> targetIds = ret.computeIfAbsent(role, ignore -> Lists.newArrayList());
|
||||
targetIds.add(r.getTarget());
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private boolean checkAppPermission(Long targetId, Permission requiredPermission, Multimap<Long, Role> appId2Role, Multimap<Long, Role> namespaceId2Role) {
|
||||
|
||||
final Collection<Role> appRoles = appId2Role.get(targetId);
|
||||
for (Role role : appRoles) {
|
||||
if (role.getPermissions().contains(requiredPermission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 校验 namespace 穿透权限
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findById(targetId);
|
||||
if (!appInfoOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find appInfo by appId in permission check: " + targetId);
|
||||
}
|
||||
Long namespaceId = Optional.ofNullable(appInfoOpt.get().getNamespaceId()).orElse(-1L);
|
||||
Collection<Role> namespaceRoles = namespaceId2Role.get(namespaceId);
|
||||
for (Role role : namespaceRoles) {
|
||||
if (role.getPermissions().contains(requiredPermission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkNamespacePermission(Long targetId, Permission requiredPermission, Multimap<Long, Role> namespaceId2Role) {
|
||||
Collection<Role> namespaceRoles = namespaceId2Role.get(targetId);
|
||||
for (Role role : namespaceRoles) {
|
||||
if (role.getPermissions().contains(requiredPermission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -0,0 +1,18 @@
|
||||
package tech.powerjob.server.common;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 统一定义日志
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/25
|
||||
*/
|
||||
public class Loggers {
|
||||
|
||||
/**
|
||||
* Web 层统一日志
|
||||
*/
|
||||
public static final Logger WEB = LoggerFactory.getLogger("P_SERVER_LOGGER_WEB");
|
||||
}
|
@ -3,6 +3,8 @@ package tech.powerjob.server.common;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Splitter & Joiner
|
||||
*
|
||||
@ -16,4 +18,9 @@ public class SJ {
|
||||
|
||||
public static final Joiner MONITOR_JOINER = Joiner.on("|").useForNull("-");
|
||||
|
||||
private static final Splitter.MapSplitter MAP_SPLITTER = Splitter.onPattern(";").withKeyValueSeparator(":");
|
||||
|
||||
public static Map<String, String> splitKvString(String kvString) {
|
||||
return MAP_SPLITTER.split(kvString);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package tech.powerjob.server.common.utils;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* 加密工具
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/25
|
||||
*/
|
||||
public class DigestUtils {
|
||||
|
||||
/**
|
||||
* 32位小写 md5
|
||||
* @param input 输入
|
||||
* @return md5
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String md5(String input) {
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
md5.update(input.getBytes());
|
||||
byte[] byteArray = md5.digest();
|
||||
|
||||
BigInteger bigInt = new BigInteger(1, byteArray);
|
||||
// 参数16表示16进制
|
||||
StringBuilder result = new StringBuilder(bigInt.toString(16));
|
||||
// 不足32位高位补零
|
||||
while(result.length() < 32) {
|
||||
result.insert(0, "0");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String rePassword(String password, String salt) {
|
||||
String f1 = String.format("%s_%s_z", salt, password);
|
||||
return String.format("%s_%s_b", salt, md5(f1));
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -1,14 +1,13 @@
|
||||
package tech.powerjob.server.core.service;
|
||||
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@ -25,16 +24,6 @@ public class UserService {
|
||||
@Resource
|
||||
private UserInfoRepository userInfoRepository;
|
||||
|
||||
/**
|
||||
* 保存/修改 用户
|
||||
* @param userInfoDO user
|
||||
*/
|
||||
public void save(UserInfoDO userInfoDO) {
|
||||
userInfoDO.setGmtCreate(new Date());
|
||||
userInfoDO.setGmtModified(userInfoDO.getGmtCreate());
|
||||
userInfoRepository.saveAndFlush(userInfoDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID字符串获取用户信息详细列表
|
||||
* @param userIds 逗号分割的用户ID信息
|
||||
|
@ -1,7 +1,6 @@
|
||||
package tech.powerjob.server.core.uid;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.server.remote.server.self.ServerInfoService;
|
||||
|
||||
@ -22,7 +21,7 @@ public class IdGenerateService {
|
||||
private static final int DATA_CENTER_ID = 0;
|
||||
|
||||
public IdGenerateService(ServerInfoService serverInfoService) {
|
||||
long id = serverInfoService.fetchServiceInfo().getId();
|
||||
long id = serverInfoService.fetchCurrentServerInfo().getId();
|
||||
snowFlakeIdGenerator = new SnowFlakeIdGenerator(DATA_CENTER_ID, id);
|
||||
log.info("[IdGenerateService] initialize IdGenerateService successfully, ID:{}", id);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@ -41,8 +41,8 @@
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
@ -86,7 +86,7 @@ public class QueryConvertUtils {
|
||||
};
|
||||
}
|
||||
|
||||
private static String convertLikeParams(Object o) {
|
||||
public static String convertLikeParams(Object o) {
|
||||
String s = (String) o;
|
||||
if (!s.startsWith("%")) {
|
||||
s = "%" + s;
|
||||
|
@ -22,7 +22,14 @@ public class AppInfoDO {
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
|
||||
private String appName;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 应用分组密码
|
||||
*/
|
||||
@ -35,7 +42,24 @@ public class AppInfoDO {
|
||||
*/
|
||||
private String currentServer;
|
||||
|
||||
/**
|
||||
* 命名空间ID,外键关联
|
||||
*/
|
||||
private Long namespaceId;
|
||||
/**
|
||||
* 管理标签
|
||||
*/
|
||||
private String tags;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
private Long creator;
|
||||
|
||||
private Long modifier;
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package tech.powerjob.server.persistence.remote.model;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 命名空间,用于组织管理 App
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(uniqueConstraints = {@UniqueConstraint(name = "uidx01_namespace", columnNames = {"code"})})
|
||||
public class NamespaceDO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 空间唯一标识
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 空间名称,比如中文描述(XX部门XX空间)
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 鉴权 token,后续 OpenAPI 调用需要
|
||||
*/
|
||||
private String token;
|
||||
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 部门,组织架构相关属性。
|
||||
* 预留数据库字段方便基于组织架构二次开发
|
||||
*/
|
||||
private String dept;
|
||||
|
||||
/**
|
||||
* 标签,扩展性之王,多值逗号分割
|
||||
*/
|
||||
private String tags;
|
||||
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
|
||||
private Long creator;
|
||||
|
||||
private Long modifier;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package tech.powerjob.server.persistence.remote.model;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* PowerJob 自建登录体系的用户表,只存储使用 PowerJob 自带登录方式登录的用户信息
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/13
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uidx01_username", columnNames = {"username"})
|
||||
})
|
||||
public class PwjbUserInfoDO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String extra;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package tech.powerjob.server.persistence.remote.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 杂项
|
||||
* KKV 表存一些配置数据
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@Table(uniqueConstraints = {@UniqueConstraint(name = "uidx01_sundry", columnNames = {"pkey", "skey"})})
|
||||
public class SundryDO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* PKEY
|
||||
*/
|
||||
private String pkey;
|
||||
/**
|
||||
* SKEY
|
||||
*/
|
||||
private String skey;
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 其他参数
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
}
|
@ -8,16 +8,20 @@ import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户信息表
|
||||
* PowerJob 自身维护的全部用户体系数据
|
||||
* 5.0.0 可能不兼容改动:为了支持第三方登录,需要通过 username 与第三方登录系统做匹配,该列需要声明为唯一索引,确保全局唯一
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2020/4/12
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(indexes = {
|
||||
@Index(name = "uidx01_user_info", columnList = "username"),
|
||||
@Index(name = "uidx02_user_info", columnList = "email")
|
||||
})
|
||||
@Table(uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uidx01_user_name", columnNames = {"username"})
|
||||
},
|
||||
indexes = {
|
||||
@Index(name = "uidx02_user_info", columnList = "email")
|
||||
})
|
||||
public class UserInfoDO {
|
||||
|
||||
@Id
|
||||
@ -25,7 +29,17 @@ public class UserInfoDO {
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 账号类型
|
||||
*/
|
||||
private String accountType;
|
||||
|
||||
private String username;
|
||||
/**
|
||||
* since 5.0.0
|
||||
* 昵称(第三方登陆的 username 很难识别,方便后续展示引入 nick)
|
||||
*/
|
||||
private String nick;
|
||||
|
||||
private String password;
|
||||
/**
|
||||
@ -40,11 +54,23 @@ public class UserInfoDO {
|
||||
* webHook
|
||||
*/
|
||||
private String webHook;
|
||||
|
||||
/**
|
||||
* 扩展字段
|
||||
* JWT 登录的二次校验信息
|
||||
*/
|
||||
private String tokenLoginVerifyInfo;
|
||||
|
||||
/**
|
||||
* 扩展字段 for 第三方
|
||||
* PowerJob 内部不允许使用该字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
/**
|
||||
* 原始账号 username
|
||||
*/
|
||||
private String originUsername;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
|
@ -0,0 +1,53 @@
|
||||
package tech.powerjob.server.persistence.remote.model;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户角色表
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(indexes = {
|
||||
@Index(name = "uidx01_user_id", columnList = "userId")
|
||||
})
|
||||
public class UserRoleDO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 授予角色的用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 权限范围,namespace 还是 app
|
||||
*/
|
||||
private Integer scope;
|
||||
/**
|
||||
* 和 scope 一起组成授权目标,比如某个 app 或 某个 namespace
|
||||
*/
|
||||
private Long target;
|
||||
|
||||
/**
|
||||
* 角色,比如 Observer
|
||||
*/
|
||||
private Integer role;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
package tech.powerjob.server.persistence.remote.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -16,7 +18,7 @@ import java.util.Optional;
|
||||
* @author tjq
|
||||
* @since 2020/4/1
|
||||
*/
|
||||
public interface AppInfoRepository extends JpaRepository<AppInfoDO, Long> {
|
||||
public interface AppInfoRepository extends JpaRepository<AppInfoDO, Long>, JpaSpecificationExecutor<AppInfoDO> {
|
||||
|
||||
Optional<AppInfoDO> findByAppName(String appName);
|
||||
|
||||
@ -31,4 +33,8 @@ public interface AppInfoRepository extends JpaRepository<AppInfoDO, Long> {
|
||||
@Query(value = "select id from AppInfoDO where currentServer = :currentServer")
|
||||
List<Long> listAppIdByCurrentServer(@Param("currentServer")String currentServer);
|
||||
|
||||
List<AppInfoDO> findAllByNamespaceId(Long namespaceId);
|
||||
|
||||
|
||||
List<AppInfoDO> findAllByIdIn(Collection<Long> ids);
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package tech.powerjob.server.persistence.remote.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 命名空间
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
public interface NamespaceRepository extends JpaRepository<NamespaceDO, Long>, JpaSpecificationExecutor<NamespaceDO> {
|
||||
|
||||
Optional<NamespaceDO> findByCode(String code);
|
||||
|
||||
List<NamespaceDO> findAllByIdIn(Collection<Long> ids);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package tech.powerjob.server.persistence.remote.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PwjbUserInfoRepository
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/13
|
||||
*/
|
||||
public interface PwjbUserInfoRepository extends JpaRepository<PwjbUserInfoDO, Long> {
|
||||
|
||||
Optional<PwjbUserInfoDO> findByUsername(String username);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package tech.powerjob.server.persistence.remote.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import tech.powerjob.server.persistence.remote.model.SundryDO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* SundryRepository
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
public interface SundryRepository extends JpaRepository<SundryDO, Long> {
|
||||
|
||||
List<SundryDO> findAllByPkey(String pkey);
|
||||
|
||||
Optional<SundryDO> findByPkeyAndSkey(String pkey, String skey);
|
||||
}
|
@ -4,6 +4,7 @@ import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户信息表数据库访问层
|
||||
@ -13,6 +14,8 @@ import java.util.List;
|
||||
*/
|
||||
public interface UserInfoRepository extends JpaRepository<UserInfoDO, Long> {
|
||||
|
||||
Optional<UserInfoDO> findByUsername(String username);
|
||||
|
||||
List<UserInfoDO> findByUsernameLike(String username);
|
||||
|
||||
List<UserInfoDO> findByIdIn(List<Long> userIds);
|
||||
|
@ -0,0 +1,23 @@
|
||||
package tech.powerjob.server.persistence.remote.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import tech.powerjob.server.persistence.remote.model.UserRoleDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UserRoleRepository
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
public interface UserRoleRepository extends JpaRepository<UserRoleDO, Long> {
|
||||
|
||||
List<UserRoleDO> findAllByUserId(Long userId);
|
||||
|
||||
List<UserRoleDO> findAllByScopeAndTarget(Integer scope, Long target);
|
||||
|
||||
List<UserRoleDO> findAllByScopeAndTargetAndRoleAndUserId(Integer scope, Long target, Integer role, Long userId);
|
||||
|
||||
List<UserRoleDO> findAllByUserIdAndScope(Long userId, Integer scope);
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -14,6 +14,13 @@ public interface ServerInfoService {
|
||||
* fetch current server info
|
||||
* @return ServerInfo
|
||||
*/
|
||||
ServerInfo fetchServiceInfo();
|
||||
ServerInfo fetchCurrentServerInfo();
|
||||
|
||||
/**
|
||||
* fetch schedule server info
|
||||
* @param appId appId
|
||||
* @return ServerInfo
|
||||
*/
|
||||
ServerInfo fetchAppServerInfo(Long appId);
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import tech.powerjob.server.common.module.ServerInfo;
|
||||
import tech.powerjob.server.extension.LockService;
|
||||
import tech.powerjob.server.persistence.remote.model.ServerInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.ServerInfoRepository;
|
||||
import tech.powerjob.server.remote.server.redirector.DesignateServer;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -138,7 +139,13 @@ public class ServerInfoServiceImpl implements ServerInfoService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo fetchServiceInfo() {
|
||||
public ServerInfo fetchCurrentServerInfo() {
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DesignateServer
|
||||
public ServerInfo fetchAppServerInfo(Long appId) {
|
||||
return serverInfo;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>4.3.9</version>
|
||||
<version>5.0.0-beta2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@ -43,6 +43,10 @@
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-migrate</artifactId>
|
||||
|
@ -0,0 +1,46 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.interceptor.DynamicPermissionPlugin;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 针对 namespace 和 app 两大鉴权纬度,创建不需要任何权限,但任何修改操作都需要 WRITE 权限
|
||||
* 创建不需要权限,修改需要校验权限
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Slf4j
|
||||
public class ModifyOrCreateDynamicPermission implements DynamicPermissionPlugin {
|
||||
@Override
|
||||
public Permission calculate(HttpServletRequest request, Object handler) {
|
||||
|
||||
try {
|
||||
//获取请求body
|
||||
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
String body = new String(bodyBytes, request.getCharacterEncoding());
|
||||
|
||||
Map<String, Object> inputParams = JsonUtils.parseMap(body);
|
||||
|
||||
Object id = inputParams.get("id");
|
||||
|
||||
// 创建,不需要权限
|
||||
if (id == null) {
|
||||
return Permission.NONE;
|
||||
}
|
||||
|
||||
return Permission.WRITE;
|
||||
} catch (Exception e) {
|
||||
log.error("[ModifyOrCreateDynamicPermission] check permission failed, please fix the bug!!!", e);
|
||||
}
|
||||
|
||||
// 异常情况先放行,不影响功能使用,后续修复 BUG
|
||||
return Permission.NONE;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
|
||||
/**
|
||||
* desc
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public class SaveAppGrantPermissionPlugin extends SaveGrantPermissionPlugin {
|
||||
@Override
|
||||
protected RoleScope fetchRuleScope() {
|
||||
return RoleScope.APP;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.LoginUserHolder;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.GrantPermissionPlugin;
|
||||
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
|
||||
import tech.powerjob.server.common.utils.SpringUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WEB 类保存&修改一体型请求-授权插件
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SaveGrantPermissionPlugin implements GrantPermissionPlugin {
|
||||
|
||||
private static final String KEY_ID = "id";
|
||||
|
||||
@Override
|
||||
public void grant(Object[] args, Object result, Method method, Object originBean) {
|
||||
|
||||
if (args == null || args.length != 1) {
|
||||
throw new IllegalArgumentException("[GrantPermission] args not match, maybe there has some bug");
|
||||
}
|
||||
|
||||
// 理论上不可能,前置已完成判断
|
||||
PowerJobUser powerJobUser = LoginUserHolder.get();
|
||||
if (powerJobUser == null) {
|
||||
throw new IllegalArgumentException("[GrantPermission] user not login, can't grant permission");
|
||||
}
|
||||
|
||||
// 解析ID,非空代表更新,不授权
|
||||
Map<String, Object> saveRequest = JsonUtils.parseMap(JsonUtils.toJSONString(args[0]));
|
||||
Long id = MapUtils.getLong(saveRequest, KEY_ID);
|
||||
if (id != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(result instanceof ResultDTO)) {
|
||||
throw new IllegalArgumentException("[GrantPermission] result not instanceof ResultDTO, maybe there has some bug");
|
||||
}
|
||||
|
||||
ResultDTO<?> resultDTO = (ResultDTO<?>) result;
|
||||
|
||||
if (!resultDTO.isSuccess()) {
|
||||
log.warn("[GrantPermission] result not success, skip grant permission!");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> saveResult = JsonUtils.parseMap(JsonUtils.toJSONString(resultDTO.getData()));
|
||||
Long savedId = MapUtils.getLong(saveResult, KEY_ID);
|
||||
if (savedId == null) {
|
||||
throw new IllegalArgumentException("[GrantPermission] result success but id not exits, maybe there has some bug, please fix it!!!");
|
||||
}
|
||||
|
||||
PowerJobPermissionService powerJobPermissionService = SpringUtils.getBean(PowerJobPermissionService.class);
|
||||
|
||||
Map<String, Object> extra = Maps.newHashMap();
|
||||
extra.put("source", "SaveGrantPermissionPlugin");
|
||||
|
||||
powerJobPermissionService.grantRole(fetchRuleScope(), savedId, powerJobUser.getId(), Role.ADMIN, JsonUtils.toJSONString(extra));
|
||||
}
|
||||
|
||||
protected abstract RoleScope fetchRuleScope();
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package tech.powerjob.server.auth.plugin;
|
||||
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
|
||||
/**
|
||||
* namespace 授权插件
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
public class SaveNamespaceGrantPermissionPlugin extends SaveGrantPermissionPlugin {
|
||||
@Override
|
||||
protected RoleScope fetchRuleScope() {
|
||||
return RoleScope.NAMESPACE;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package tech.powerjob.server.auth.service;
|
||||
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Web Auth 服务
|
||||
* 写在 starter 包下,抽取 controller 的公共逻辑
|
||||
* (powerjob 的 service/core 包核心处理调度核心逻辑,admin 部分代码收口在 stater 包)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
public interface WebAuthService {
|
||||
|
||||
/**
|
||||
* 对当前登录用户授予角色
|
||||
* @param roleScope 角色范围
|
||||
* @param target 目标
|
||||
* @param role 角色
|
||||
* @param extra 其他信息
|
||||
*/
|
||||
void grantRole2LoginUser(RoleScope roleScope, Long target, Role role, String extra);
|
||||
|
||||
/**
|
||||
* 处理授权
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @param componentUserRoleInfo 人员角色信息
|
||||
*/
|
||||
void processPermissionOnSave(RoleScope roleScope, Long target, ComponentUserRoleInfo componentUserRoleInfo);
|
||||
|
||||
/**
|
||||
* 获取目标相关权限人员列表
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @return ComponentUserRoleInfo
|
||||
*/
|
||||
ComponentUserRoleInfo fetchComponentUserRoleInfo(RoleScope roleScope, Long target);
|
||||
|
||||
/**
|
||||
* 判断当前用户是否有权限
|
||||
* @param roleScope 权限范围
|
||||
* @param target 权限目标
|
||||
* @param permission 要求的权限
|
||||
* @return 是否有权限
|
||||
*/
|
||||
boolean hasPermission(RoleScope roleScope, Long target, Permission permission);
|
||||
|
||||
Map<Role, List<Long>> fetchMyPermissionTargets(RoleScope roleScope);
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package tech.powerjob.server.auth.service.impl;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.*;
|
||||
import tech.powerjob.server.auth.common.AuthErrorCode;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* WebAuthService
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/12
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WebAuthServiceImpl implements WebAuthService {
|
||||
|
||||
@Resource
|
||||
private PowerJobPermissionService powerJobPermissionService;
|
||||
|
||||
|
||||
@Override
|
||||
public void grantRole2LoginUser(RoleScope roleScope, Long target, Role role, String extra) {
|
||||
Long userId = LoginUserHolder.getUserId();
|
||||
if (userId == null) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
|
||||
}
|
||||
powerJobPermissionService.grantRole(roleScope, target, userId, role, extra);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processPermissionOnSave(RoleScope roleScope, Long target, ComponentUserRoleInfo o) {
|
||||
ComponentUserRoleInfo componentUserRoleInfo = Optional.ofNullable(o).orElse(new ComponentUserRoleInfo());
|
||||
|
||||
Map<Role, List<Long>> role2Uids = powerJobPermissionService.fetchUserWithPermissions(roleScope, target);
|
||||
diffGrant(roleScope, target, Role.OBSERVER, componentUserRoleInfo.getObserver(), role2Uids);
|
||||
diffGrant(roleScope, target, Role.QA, componentUserRoleInfo.getQa(), role2Uids);
|
||||
diffGrant(roleScope, target, Role.DEVELOPER, componentUserRoleInfo.getDeveloper(), role2Uids);
|
||||
diffGrant(roleScope, target, Role.ADMIN, componentUserRoleInfo.getAdmin(), role2Uids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentUserRoleInfo fetchComponentUserRoleInfo(RoleScope roleScope, Long target) {
|
||||
Map<Role, List<Long>> role2Uids = powerJobPermissionService.fetchUserWithPermissions(roleScope, target);
|
||||
return new ComponentUserRoleInfo()
|
||||
.setObserver(role2Uids.getOrDefault(Role.OBSERVER, Collections.emptyList()))
|
||||
.setQa(role2Uids.getOrDefault(Role.QA, Collections.emptyList()))
|
||||
.setDeveloper(role2Uids.getOrDefault(Role.DEVELOPER, Collections.emptyList()))
|
||||
.setAdmin(role2Uids.getOrDefault(Role.ADMIN, Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(RoleScope roleScope, Long target, Permission permission) {
|
||||
|
||||
PowerJobUser powerJobUser = LoginUserHolder.get();
|
||||
if (powerJobUser == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return powerJobPermissionService.hasPermission(powerJobUser.getId(), roleScope, target, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Role, List<Long>> fetchMyPermissionTargets(RoleScope roleScope) {
|
||||
|
||||
PowerJobUser powerJobUser = LoginUserHolder.get();
|
||||
if (powerJobUser == null) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
|
||||
}
|
||||
|
||||
// 展示不考虑穿透权限的问题(即拥有 namespace 权限也可以看到全部的 apps)
|
||||
return powerJobPermissionService.fetchUserHadPermissionTargets(roleScope, powerJobUser.getId());
|
||||
}
|
||||
|
||||
private void diffGrant(RoleScope roleScope, Long target, Role role, List<Long> uids, Map<Role, List<Long>> originRole2Uids) {
|
||||
|
||||
Set<Long> originUids = Sets.newHashSet(Optional.ofNullable(originRole2Uids.get(role)).orElse(Collections.emptyList()));
|
||||
Set<Long> currentUids = Sets.newHashSet(Optional.ofNullable(uids).orElse(Collections.emptyList()));
|
||||
|
||||
Map<String, Object> extraInfo = Maps.newHashMap();
|
||||
extraInfo.put("grantor", LoginUserHolder.getUserName());
|
||||
extraInfo.put("source", "diffGrant");
|
||||
String extra = JsonUtils.toJSONString(extraInfo);
|
||||
|
||||
Set<Long> allIds = Sets.newHashSet(originUids);
|
||||
allIds.addAll(currentUids);
|
||||
|
||||
Set<Long> allIds2 = Sets.newHashSet(allIds);
|
||||
|
||||
// 在 originUids 不在 currentUids,需要取消授权
|
||||
allIds.removeAll(currentUids);
|
||||
allIds.forEach(cancelPermissionUid -> {
|
||||
powerJobPermissionService.retrieveRole(roleScope, target, cancelPermissionUid, role);
|
||||
log.info("[WebAuthService] [diffGrant] cancelPermission: roleScope={},target={},uid={},role={}", roleScope, target, cancelPermissionUid, role);
|
||||
});
|
||||
|
||||
// 在 currentUids 当不在 orignUids,需要增加授权
|
||||
allIds2.removeAll(originUids);
|
||||
allIds2.forEach(addPermissionUid -> {
|
||||
powerJobPermissionService.grantRole(roleScope, target, addPermissionUid, role, extra);
|
||||
log.info("[WebAuthService] [diffGrant] grantPermission: roleScope={},target={},uid={},role={},extra={}", roleScope, target, addPermissionUid, role, extra);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package tech.powerjob.server.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* 解决 HttpServletRequest 只能被读取一次的问题,方便全局日志 & 鉴权,切面提前读取数据
|
||||
* 在请求进入Servlet容器之前,先经过Filter的过滤器链。在请求进入Controller之前,先经过 HandlerInterceptor 的拦截器链。Filter 一定先于 HandlerInterceptor 执行
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/11
|
||||
*/
|
||||
@Component
|
||||
public class CachingRequestBodyFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
if (request instanceof HttpServletRequest) {
|
||||
CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper((HttpServletRequest) request);
|
||||
chain.doFilter(wrappedRequest, response);
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
// Implement other required methods like init() and destroy() if necessary
|
||||
|
||||
|
||||
public static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final String body;
|
||||
|
||||
public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
InputStream inputStream = request.getInputStream();
|
||||
if (inputStream != null) {
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
char[] charBuffer = new char[128];
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
|
||||
stringBuilder.append(charBuffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
}
|
||||
body = stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
|
||||
|
||||
return new ServletInputStream() {
|
||||
public int read() throws IOException {
|
||||
return byteArrayInputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return byteArrayInputStream.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
return new BufferedReader(new InputStreamReader(this.getInputStream()));
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return this.body;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -41,7 +41,7 @@ public class SwaggerConfig {
|
||||
return new OpenAPI()
|
||||
.info(new Info().title("PowerJob")
|
||||
.description("Distributed scheduling and computing framework.")
|
||||
.version(serverInfoService.fetchServiceInfo().getVersion())
|
||||
.version(serverInfoService.fetchCurrentServerInfo().getVersion())
|
||||
.contact(contact)
|
||||
.license(new License().name("Apache License 2.0").url("https://github.com/PowerJob/PowerJob/blob/master/LICENSE")));
|
||||
}
|
||||
|
@ -3,9 +3,13 @@ package tech.powerjob.server.config;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
import tech.powerjob.server.auth.interceptor.PowerJobAuthInterceptor;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* CORS
|
||||
@ -16,6 +20,10 @@ import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Resource
|
||||
private PowerJobAuthInterceptor powerJobAuthInterceptor;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
@ -26,4 +34,19 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
/*
|
||||
可以添加多个拦截器
|
||||
addPathPatterns("/**") 表示对所有请求都拦截
|
||||
.excludePathPatterns("/base/index") 表示排除对/base/index请求的拦截
|
||||
多个拦截器可以设置order顺序,值越小,preHandle越先执行,postHandle和afterCompletion越后执行
|
||||
order默认的值是0,如果只添加一个拦截器,可以不显示设置order的值
|
||||
*/
|
||||
registry.addInterceptor(powerJobAuthInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/css/**", "/js/**", "/images/**", "/img/**", "/fonts/**", "/favicon.ico")
|
||||
.order(0);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
package tech.powerjob.server.initializer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.extension.LockService;
|
||||
import tech.powerjob.server.persistence.remote.model.SundryDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.SundryRepository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 新系统初始化器
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/5
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NewSystemInitializer implements CommandLineRunner {
|
||||
|
||||
|
||||
private static final String LOCK_PREFIX = "sys_init_lock_";
|
||||
|
||||
private static final int MAX_LOCK_TIME = 5000;
|
||||
|
||||
|
||||
@Resource
|
||||
private LockService lockService;
|
||||
@Resource
|
||||
private SundryRepository sundryRepository;
|
||||
@Resource
|
||||
private SystemInitializeService systemInitializeService;
|
||||
|
||||
private static final String SUNDRY_PKEY = "sys_initialize";
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
initSystemAdmin();
|
||||
initDefaultNamespace();
|
||||
}
|
||||
|
||||
private void initSystemAdmin() {
|
||||
clusterInit(SystemInitializeService.GOAL_INIT_ADMIN, Void -> systemInitializeService.initAdmin());
|
||||
}
|
||||
|
||||
private void initDefaultNamespace() {
|
||||
clusterInit(SystemInitializeService.GOAL_INIT_NAMESPACE, Void -> systemInitializeService.initNamespace());
|
||||
}
|
||||
|
||||
private void clusterInit(String name, Consumer<Void> initFunc) {
|
||||
|
||||
Optional<SundryDO> sundryOpt = sundryRepository.findByPkeyAndSkey(SUNDRY_PKEY, name);
|
||||
if (sundryOpt.isPresent()) {
|
||||
log.info("[NewSystemInitializer] already initialized, skip: {}", name);
|
||||
return;
|
||||
}
|
||||
|
||||
String lockName = LOCK_PREFIX.concat(name);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
||||
boolean lockStatus = lockService.tryLock(lockName, MAX_LOCK_TIME);
|
||||
|
||||
// 无论是否拿到锁,都重现检测一次,如果已完成初始化,则直接 return
|
||||
Optional<SundryDO> sundryOpt2 = sundryRepository.findByPkeyAndSkey(SUNDRY_PKEY, name);
|
||||
if (sundryOpt2.isPresent()) {
|
||||
log.info("[NewSystemInitializer] other server finished initialize, skip process: {}", name);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!lockStatus) {
|
||||
CommonUtils.easySleep(277);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("[NewSystemInitializer] try to initialize: {}", name);
|
||||
initFunc.accept(null);
|
||||
log.info("[NewSystemInitializer] initialize [{}] successfully!", name);
|
||||
|
||||
// 写入初始化成功标记
|
||||
SundryDO sundryDO = new SundryDO();
|
||||
sundryDO.setPkey(SUNDRY_PKEY);
|
||||
sundryDO.setSkey(name);
|
||||
sundryDO.setContent("A");
|
||||
sundryDO.setGmtCreate(new Date());
|
||||
sundryRepository.saveAndFlush(sundryDO);
|
||||
log.info("[NewSystemInitializer] write initialized tag successfully: {}", sundryDO);
|
||||
|
||||
break;
|
||||
} finally {
|
||||
lockService.unlock(lockName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package tech.powerjob.server.initializer;
|
||||
|
||||
/**
|
||||
* 系统初始化服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
public interface SystemInitializeService {
|
||||
|
||||
String GOAL_INIT_ADMIN = "goal_init_admin";
|
||||
String GOAL_INIT_NAMESPACE = "goal_init_namespace";
|
||||
|
||||
|
||||
/**
|
||||
* 初始化超级管理员
|
||||
*/
|
||||
void initAdmin();
|
||||
|
||||
/**
|
||||
* 初始化 namespace
|
||||
*/
|
||||
void initNamespace();
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package tech.powerjob.server.initializer;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.service.login.LoginRequest;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
|
||||
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import tech.powerjob.server.web.service.NamespaceWebService;
|
||||
import tech.powerjob.server.web.service.PwjbUserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 初始化 PowerJob 首次部署相关的内容
|
||||
* 为了可维护性足够高,统一使用 WEB 请求进行初始化,不直接操作底层,防止后续内部逻辑变更后出现问题
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/15
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SystemInitializeServiceImpl implements SystemInitializeService {
|
||||
|
||||
@Value("${oms.auth.initiliaze.admin.password:#{null}}")
|
||||
private String defaultAdminPassword;
|
||||
@Resource
|
||||
private PwjbUserWebService pwjbUserWebService;
|
||||
@Resource
|
||||
private NamespaceWebService namespaceWebService;
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
@Resource
|
||||
private PowerJobPermissionService powerJobPermissionService;
|
||||
|
||||
private static final String SYSTEM_ADMIN_NAME = "ADMIN";
|
||||
|
||||
|
||||
private static final String SYSTEM_DEFAULT_NAMESPACE = "default_namespace";
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = Exception.class)
|
||||
public void initAdmin() {
|
||||
|
||||
String username = SYSTEM_ADMIN_NAME;
|
||||
String password = StringUtils.isEmpty(defaultAdminPassword) ? RandomStringUtils.randomAlphabetic(8) : defaultAdminPassword;
|
||||
|
||||
// STEP1: 创建 PWJB 用户
|
||||
ModifyUserInfoRequest createUser = new ModifyUserInfoRequest();
|
||||
createUser.setUsername(username);
|
||||
createUser.setNick(username);
|
||||
createUser.setPassword(password);
|
||||
|
||||
log.info("[SystemInitializeService] [S1] create default PWJB user by request: {}", createUser);
|
||||
PwjbUserInfoDO savedPwjbUser = pwjbUserWebService.save(createUser);
|
||||
log.info("[SystemInitializeService] [S1] create default PWJB user successfully: {}", savedPwjbUser);
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put(AuthConstants.PARAM_KEY_USERNAME, username);
|
||||
params.put(AuthConstants.PARAM_KEY_PASSWORD, password);
|
||||
|
||||
// STEP2: 创建 USER 对象
|
||||
LoginRequest loginRequest = new LoginRequest()
|
||||
.setLoginType(AuthConstants.ACCOUNT_TYPE_POWER_JOB)
|
||||
.setOriginParams(JsonUtils.toJSONString(params));
|
||||
log.info("[SystemInitializeService] [S2] createPowerJobUser user by request: {}", loginRequest);
|
||||
PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginRequest);
|
||||
log.info("[SystemInitializeService] [S2] createPowerJobUser successfully: {}", powerJobUser);
|
||||
|
||||
// STEP3: 授予全局管理员权限
|
||||
powerJobPermissionService.grantRole(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, powerJobUser.getId(), Role.ADMIN, null);
|
||||
log.info("[SystemInitializeService] [S3] GRANT ADMIN successfully!");
|
||||
|
||||
// 循环10遍,强提醒用户,第一次使用必须更改 admin 密码
|
||||
for (int i = 0; i < 10; i++) {
|
||||
log.warn("[SystemInitializeService] The system has automatically created a super administrator account[username={},password={}], please log in and change the password immediately!", username, password);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackOn = Exception.class)
|
||||
public void initNamespace() {
|
||||
ModifyNamespaceRequest saveNamespaceReq = new ModifyNamespaceRequest();
|
||||
saveNamespaceReq.setName(SYSTEM_DEFAULT_NAMESPACE);
|
||||
saveNamespaceReq.setCode(SYSTEM_DEFAULT_NAMESPACE);
|
||||
|
||||
log.info("[SystemInitializeService] create default namespace by request: {}", saveNamespaceReq);
|
||||
NamespaceDO savedNamespaceDO = namespaceWebService.save(saveNamespaceReq);
|
||||
log.info("[SystemInitializeService] create default namespace successfully: {}", savedNamespaceDO);
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ import java.util.List;
|
||||
public class ServerInfoAwareProcessor {
|
||||
|
||||
public ServerInfoAwareProcessor(ServerInfoService serverInfoService, List<ServerInfoAware> awareList) {
|
||||
final ServerInfo serverInfo = serverInfoService.fetchServiceInfo();
|
||||
final ServerInfo serverInfo = serverInfoService.fetchCurrentServerInfo();
|
||||
log.info("[ServerInfoAwareProcessor] current server info: {}", serverInfo);
|
||||
awareList.forEach(aware -> {
|
||||
aware.setServerInfo(serverInfo);
|
||||
|
@ -1,7 +1,5 @@
|
||||
package tech.powerjob.server.web;
|
||||
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@ -10,6 +8,9 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.web.response.WebResultDTO;
|
||||
|
||||
/**
|
||||
* 统一处理 web 层异常信息
|
||||
@ -23,11 +24,16 @@ public class ControllerExceptionHandler {
|
||||
|
||||
@ResponseBody
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResultDTO<Void> exceptionHandler(Exception e) {
|
||||
public WebResultDTO<Void> exceptionHandler(Exception e) {
|
||||
|
||||
WebResultDTO<Void> ret = new WebResultDTO<>(ResultDTO.failed(ExceptionUtils.getMessage(e)));
|
||||
|
||||
// 不是所有异常都需要打印完整堆栈,后续可以定义内部的Exception,便于判断
|
||||
if (e instanceof IllegalArgumentException || e instanceof PowerJobException) {
|
||||
log.warn("[ControllerException] http request failed, message is {}.", e.getMessage());
|
||||
if (e instanceof PowerJobException) {
|
||||
ret.setCode(((PowerJobException) e).getCode());
|
||||
log.warn("[ControllerException] PowerJobException, message is {}.", e.getMessage());
|
||||
} else if (e instanceof IllegalArgumentException) {
|
||||
log.warn("[ControllerException] http request failed due to IllegalArgument, message is {}.", e.getMessage());
|
||||
} else if (e instanceof HttpMessageNotReadableException || e instanceof MethodArgumentTypeMismatchException) {
|
||||
log.warn("[ControllerException] invalid http request params, exception is {}.", e.getMessage());
|
||||
} else if (e instanceof HttpRequestMethodNotSupportedException) {
|
||||
@ -35,6 +41,7 @@ public class ControllerExceptionHandler {
|
||||
} else {
|
||||
log.error("[ControllerException] http request failed.", e);
|
||||
}
|
||||
return ResultDTO.failed(ExceptionUtils.getMessage(e));
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,50 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.core.service.AppInfoService;
|
||||
import tech.powerjob.server.web.request.AppAssertRequest;
|
||||
import tech.powerjob.server.web.request.ModifyAppInfoRequest;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Data;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.serialize.JsonUtils;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.server.auth.LoginUserHolder;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.common.AuthErrorCode;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission;
|
||||
import tech.powerjob.server.auth.plugin.SaveAppGrantPermissionPlugin;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.QueryConvertUtils;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.web.converter.NamespaceConverter;
|
||||
import tech.powerjob.server.web.request.AppAssertRequest;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
import tech.powerjob.server.web.request.ModifyAppInfoRequest;
|
||||
import tech.powerjob.server.web.request.QueryAppInfoRequest;
|
||||
import tech.powerjob.server.web.response.AppInfoVO;
|
||||
import tech.powerjob.server.web.response.NamespaceBaseVO;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
import tech.powerjob.server.web.service.NamespaceWebService;
|
||||
import tech.powerjob.server.web.service.UserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -35,14 +59,17 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class AppInfoController {
|
||||
|
||||
private final AppInfoService appInfoService;
|
||||
private final WebAuthService webAuthService;
|
||||
|
||||
private final UserWebService userWebService;
|
||||
|
||||
private final AppInfoRepository appInfoRepository;
|
||||
|
||||
private static final int MAX_APP_NUM = 200;
|
||||
private final NamespaceWebService namespaceWebService;
|
||||
|
||||
@PostMapping("/save")
|
||||
public ResultDTO<Void> saveAppInfo(@RequestBody ModifyAppInfoRequest req) {
|
||||
@ApiPermission(name = "App-Save", roleScope = RoleScope.APP, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveAppGrantPermissionPlugin.class)
|
||||
public ResultDTO<AppInfoVO> saveAppInfo(@RequestBody ModifyAppInfoRequest req) {
|
||||
|
||||
req.valid();
|
||||
AppInfoDO appInfoDO;
|
||||
@ -51,59 +78,154 @@ public class AppInfoController {
|
||||
if (id == null) {
|
||||
appInfoDO = new AppInfoDO();
|
||||
appInfoDO.setGmtCreate(new Date());
|
||||
}else {
|
||||
appInfoDO.setCreator(LoginUserHolder.getUserId());
|
||||
} else {
|
||||
appInfoDO = appInfoRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("can't find appInfo by id:" + id));
|
||||
|
||||
// 对比密码
|
||||
if (!Objects.equals(req.getOldPassword(), appInfoDO.getPassword())) {
|
||||
throw new PowerJobException("The password is incorrect.");
|
||||
// 不允许修改 appName
|
||||
if (!appInfoDO.getAppName().equalsIgnoreCase(req.getAppName())) {
|
||||
throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_APP_NAME");
|
||||
}
|
||||
}
|
||||
BeanUtils.copyProperties(req, appInfoDO);
|
||||
|
||||
appInfoDO.setAppName(req.getAppName());
|
||||
appInfoDO.setTitle(req.getTitle());
|
||||
appInfoDO.setPassword(req.getPassword());
|
||||
appInfoDO.setNamespaceId(req.getNamespaceId());
|
||||
appInfoDO.setTags(req.getTags());
|
||||
appInfoDO.setExtra(req.getExtra());
|
||||
|
||||
appInfoDO.setGmtModified(new Date());
|
||||
appInfoDO.setModifier(LoginUserHolder.getUserId());
|
||||
|
||||
appInfoRepository.saveAndFlush(appInfoDO);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
AppInfoDO savedAppInfo = appInfoRepository.saveAndFlush(appInfoDO);
|
||||
|
||||
@PostMapping("/assert")
|
||||
public ResultDTO<Long> assertApp(@RequestBody AppAssertRequest request) {
|
||||
return ResultDTO.success(appInfoService.assertApp(request.getAppName(), request.getPassword()));
|
||||
// 重现授权
|
||||
webAuthService.processPermissionOnSave(RoleScope.APP, savedAppInfo.getId(), req.getComponentUserRoleInfo());
|
||||
|
||||
return ResultDTO.success(convert(Lists.newArrayList(savedAppInfo), false).get(0));
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "App-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.SU)
|
||||
public ResultDTO<Void> deleteAppInfo(Long appId) {
|
||||
appInfoRepository.deleteById(appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResultDTO<List<AppInfoVO>> listAppInfo(@RequestParam(required = false) String condition) {
|
||||
List<AppInfoDO> result;
|
||||
Pageable limit = PageRequest.of(0, MAX_APP_NUM);
|
||||
if (StringUtils.isEmpty(condition)) {
|
||||
result = appInfoRepository.findAll(limit).getContent();
|
||||
}else {
|
||||
result = appInfoRepository.findByAppNameLike("%" + condition + "%", limit).getContent();
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "App-List", roleScope = RoleScope.APP, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<PageResult<AppInfoVO>> listAppInfoByQuery(@RequestBody QueryAppInfoRequest queryAppInfoRequest) {
|
||||
|
||||
Pageable pageable = PageRequest.of(queryAppInfoRequest.getIndex(), queryAppInfoRequest.getPageSize());
|
||||
|
||||
// 相关权限(先查处关联 ids)
|
||||
Set<Long> queryAppIds;
|
||||
Boolean showMyRelated = queryAppInfoRequest.getShowMyRelated();
|
||||
if (BooleanUtils.isTrue(showMyRelated)) {
|
||||
Set<Long> targetIds = Sets.newHashSet();
|
||||
webAuthService.fetchMyPermissionTargets(RoleScope.APP).values().forEach(targetIds::addAll);
|
||||
queryAppIds = targetIds;
|
||||
} else {
|
||||
queryAppIds = Collections.emptySet();
|
||||
}
|
||||
return ResultDTO.success(convert(result));
|
||||
|
||||
Specification<AppInfoDO> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> predicates = Lists.newArrayList();
|
||||
|
||||
Long appId = queryAppInfoRequest.getAppId();
|
||||
Long namespaceId = queryAppInfoRequest.getNamespaceId();
|
||||
|
||||
if (appId != null) {
|
||||
predicates.add(criteriaBuilder.equal(root.get("id"), appId));
|
||||
}
|
||||
|
||||
if (namespaceId != null) {
|
||||
predicates.add(criteriaBuilder.equal(root.get("namespaceId"), namespaceId));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(queryAppInfoRequest.getAppNameLike())) {
|
||||
predicates.add(criteriaBuilder.like(root.get("appName"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getAppNameLike())));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(queryAppInfoRequest.getTagLike())) {
|
||||
predicates.add(criteriaBuilder.like(root.get("tags"), QueryConvertUtils.convertLikeParams(queryAppInfoRequest.getTagLike())));
|
||||
}
|
||||
|
||||
if (!queryAppIds.isEmpty()) {
|
||||
predicates.add(criteriaBuilder.in(root.get("id")).value(queryAppIds));
|
||||
}
|
||||
|
||||
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
|
||||
};
|
||||
|
||||
Page<AppInfoDO> pageAppInfoResult = appInfoRepository.findAll(specification, pageable);
|
||||
|
||||
PageResult<AppInfoVO> pageRet = new PageResult<>(pageAppInfoResult);
|
||||
|
||||
List<AppInfoDO> appInfoDos = pageAppInfoResult.get().collect(Collectors.toList());
|
||||
pageRet.setData(convert(appInfoDos, true));
|
||||
|
||||
return ResultDTO.success(pageRet);
|
||||
}
|
||||
|
||||
private static List<AppInfoVO> convert(List<AppInfoDO> data) {
|
||||
@PostMapping("/becomeAdmin")
|
||||
@ApiPermission(name = "App-BecomeAdmin", roleScope = RoleScope.GLOBAL, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<Void> becomeAdminByAppNameAndPassword(@RequestBody AppAssertRequest appAssertRequest) {
|
||||
String appName = appAssertRequest.getAppName();
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findByAppName(appName);
|
||||
if (!appInfoOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find app by appName: " + appName);
|
||||
}
|
||||
if (!StringUtils.equals(appInfoOpt.get().getPassword(), appAssertRequest.getPassword())) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.INCORRECT_PASSWORD);
|
||||
}
|
||||
|
||||
Map<String, Object> extra = Maps.newHashMap();
|
||||
extra.put("source", "becomeAdminByAppNameAndPassword");
|
||||
|
||||
webAuthService.grantRole2LoginUser(RoleScope.APP, appInfoOpt.get().getId(), Role.ADMIN, JsonUtils.toJSONString(extra));
|
||||
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
private List<AppInfoVO> convert(List<AppInfoDO> data, boolean fillDetail) {
|
||||
if (CollectionUtils.isEmpty(data)) {
|
||||
return Lists.newLinkedList();
|
||||
}
|
||||
return data.stream().map(appInfoDO -> {
|
||||
|
||||
return data.parallelStream().map(appInfoDO -> {
|
||||
AppInfoVO appInfoVO = new AppInfoVO();
|
||||
BeanUtils.copyProperties(appInfoDO, appInfoVO);
|
||||
|
||||
appInfoVO.setGmtCreateStr(CommonUtils.formatTime(appInfoDO.getGmtCreate()));
|
||||
appInfoVO.setGmtModifiedStr(CommonUtils.formatTime(appInfoDO.getGmtModified()));
|
||||
|
||||
if (fillDetail) {
|
||||
// 人员面板
|
||||
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.APP, appInfoDO.getId());
|
||||
appInfoVO.setComponentUserRoleInfo(componentUserRoleInfo);
|
||||
|
||||
// 密码
|
||||
boolean hasPermission = webAuthService.hasPermission(RoleScope.APP, appInfoDO.getId(), Permission.READ);
|
||||
appInfoVO.setPassword(hasPermission ? appInfoDO.getPassword() : AuthConstants.TIPS_NO_PERMISSION_TO_SEE);
|
||||
|
||||
// namespace
|
||||
Optional<NamespaceDO> namespaceOpt = namespaceWebService.findById(appInfoDO.getNamespaceId());
|
||||
if (namespaceOpt.isPresent()) {
|
||||
NamespaceBaseVO baseNamespace = NamespaceConverter.do2BaseVo(namespaceOpt.get());
|
||||
appInfoVO.setNamespace(baseNamespace);
|
||||
appInfoVO.setNamespaceName(baseNamespace.getName());
|
||||
}
|
||||
|
||||
// user 信息
|
||||
appInfoVO.setCreatorShowName(userWebService.fetchBaseUserInfo(appInfoDO.getCreator()).map(UserBaseVO::getShowName).orElse(null));
|
||||
appInfoVO.setModifierShowName(userWebService.fetchBaseUserInfo(appInfoDO.getModifier()).map(UserBaseVO::getShowName).orElse(null));
|
||||
|
||||
}
|
||||
|
||||
return appInfoVO;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class AppInfoVO {
|
||||
private Long id;
|
||||
private String appName;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,123 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.common.utils.CollectionUtils;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.auth.login.LoginTypeInfo;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.auth.service.login.LoginRequest;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 登录 & 权限相关
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/4/16
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
|
||||
@GetMapping("/supportLoginTypes")
|
||||
public ResultDTO<List<LoginTypeInfo>> listSupportLoginTypes() {
|
||||
return ResultDTO.success(powerJobLoginService.fetchSupportLoginTypes());
|
||||
}
|
||||
|
||||
@GetMapping("/thirdPartyLoginUrl")
|
||||
public ResultDTO<String> getThirdPartyLoginUrl(String type, HttpServletRequest request) {
|
||||
String url = powerJobLoginService.fetchThirdPartyLoginUrl(type, request);
|
||||
return ResultDTO.success(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方账号体系回调登录接口,eg, 接受钉钉登录回调
|
||||
* @param httpServletRequest 请求
|
||||
* @param httpServletResponse 响应
|
||||
* @return 登录结果
|
||||
*/
|
||||
@RequestMapping(value = "/thirdPartyLoginCallback", method = {RequestMethod.GET, RequestMethod.POST})
|
||||
public ResultDTO<PowerJobUser> loginCallback(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
|
||||
LoginRequest loginContext = new LoginRequest().setHttpServletRequest(httpServletRequest);
|
||||
|
||||
// 常见登录组件的标准规范(钉钉、企业微信、飞书),第三方原样透传。开发者在对接第三方登录体系时,可能需要修改此处,将 type 回填
|
||||
final String state = httpServletRequest.getParameter("state");
|
||||
loginContext.setLoginType(state);
|
||||
|
||||
final PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginContext);
|
||||
fillJwt4LoginUser(powerJobUser, httpServletResponse);
|
||||
|
||||
return ResultDTO.success(powerJobUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方账号体系直接登录接口,eg, 接受 PowerJob 自带账号密码体系的登录请求
|
||||
* @param loginRequest 登录请求
|
||||
* @param httpServletResponse 响应
|
||||
* @return 登录结果
|
||||
*/
|
||||
@PostMapping("/thirdPartyLoginDirect")
|
||||
public ResultDTO<PowerJobUser> selfLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
loginRequest.setHttpServletRequest(httpServletRequest);
|
||||
try {
|
||||
final PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginRequest);
|
||||
if (powerJobUser == null) {
|
||||
return ResultDTO.failed("USER_NOT_FOUND");
|
||||
}
|
||||
fillJwt4LoginUser(powerJobUser, httpServletResponse);
|
||||
return ResultDTO.success(powerJobUser);
|
||||
} catch (Exception e) {
|
||||
return ResultDTO.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "/ifLogin")
|
||||
public ResultDTO<PowerJobUser> ifLogin(HttpServletRequest httpServletRequest) {
|
||||
final Optional<PowerJobUser> powerJobUser = powerJobLoginService.ifLogin(httpServletRequest);
|
||||
return powerJobUser.map(ResultDTO::success).orElseGet(() -> ResultDTO.success(null));
|
||||
}
|
||||
|
||||
/* ****************** 授权相关 ****************** */
|
||||
|
||||
@GetMapping("/listGlobalAdmin")
|
||||
public ResultDTO<List<Long>> listGlobalAdmin() {
|
||||
// 全局只设置超级管理员权限
|
||||
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID);
|
||||
return ResultDTO.success(componentUserRoleInfo.getAdmin());
|
||||
}
|
||||
|
||||
@PostMapping("/saveGlobalAdmin")
|
||||
@ApiPermission(name = "Auth-SaveGlobalAdmin", roleScope = RoleScope.GLOBAL, requiredPermission = Permission.SU)
|
||||
public ResultDTO<Void> saveGlobalAdmin(@RequestBody ComponentUserRoleInfo componentUserRoleInfo) {
|
||||
|
||||
if (CollectionUtils.isEmpty(componentUserRoleInfo.getAdmin())) {
|
||||
throw new IllegalArgumentException("At least one super administrator is required!");
|
||||
}
|
||||
|
||||
webAuthService.processPermissionOnSave(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, componentUserRoleInfo);
|
||||
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
private void fillJwt4LoginUser(PowerJobUser powerJobUser, HttpServletResponse httpServletResponse) {
|
||||
httpServletResponse.addCookie(new Cookie(AuthConstants.JWT_NAME, powerJobUser.getJwtToken()));
|
||||
}
|
||||
}
|
@ -4,11 +4,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.common.constants.ContainerSourceType;
|
||||
import tech.powerjob.server.common.constants.SwitchableStatus;
|
||||
import tech.powerjob.server.common.utils.OmsFileUtils;
|
||||
@ -39,22 +41,25 @@ import java.util.stream.Collectors;
|
||||
@RequestMapping("/container")
|
||||
public class ContainerController {
|
||||
|
||||
|
||||
private final int port;
|
||||
|
||||
private final ContainerService containerService;
|
||||
|
||||
private final AppInfoRepository appInfoRepository;
|
||||
|
||||
private final ContainerInfoRepository containerInfoRepository;
|
||||
|
||||
public ContainerController(@Value("${server.port}") int port, ContainerService containerService, AppInfoRepository appInfoRepository, ContainerInfoRepository containerInfoRepository) {
|
||||
this.port = port;
|
||||
public ContainerController(ContainerService containerService, AppInfoRepository appInfoRepository, ContainerInfoRepository containerInfoRepository) {
|
||||
this.containerService = containerService;
|
||||
this.appInfoRepository = appInfoRepository;
|
||||
this.containerInfoRepository = containerInfoRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露给 worker 的下载端口,制品本身 version 不可枚举,不单独鉴权
|
||||
* 如果对此有安全性需求,可自行实现加密鉴权逻辑,或者干脆走自己的下载通道下载制品
|
||||
* @param version 容器版本
|
||||
* @param response 响应
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
@GetMapping("/downloadJar")
|
||||
public void downloadJar(String version, HttpServletResponse response) throws IOException {
|
||||
File file = containerService.fetchContainerJarFile(version);
|
||||
@ -66,12 +71,14 @@ public class ContainerController {
|
||||
}
|
||||
|
||||
@PostMapping("/downloadContainerTemplate")
|
||||
@ApiPermission(name = "Container-DownloadContainerTemplate", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public void downloadContainerTemplate(@RequestBody GenerateContainerTemplateRequest req, HttpServletResponse response) throws IOException {
|
||||
File zipFile = ContainerTemplateGenerator.generate(req.getGroup(), req.getArtifact(), req.getName(), req.getPackageName(), req.getJavaVersion());
|
||||
OmsFileUtils.file2HttpResponse(zipFile, response);
|
||||
}
|
||||
|
||||
@PostMapping("/jarUpload")
|
||||
@ApiPermission(name = "Container-JarUpload", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<String> fileUpload(@RequestParam("file") MultipartFile file) throws Exception {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResultDTO.failed("empty file");
|
||||
@ -80,6 +87,7 @@ public class ContainerController {
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Container-Save", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> saveContainer(@RequestBody SaveContainerInfoRequest request) {
|
||||
request.valid();
|
||||
|
||||
@ -93,12 +101,14 @@ public class ContainerController {
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "Container-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> deleteContainer(Long appId, Long containerId) {
|
||||
containerService.delete(appId, containerId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiPermission(name = "Container-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<List<ContainerInfoVO>> listContainers(Long appId) {
|
||||
List<ContainerInfoVO> res = containerInfoRepository.findByAppIdAndStatusNot(appId, SwitchableStatus.DELETED.getV())
|
||||
.stream().map(ContainerController::convert).collect(Collectors.toList());
|
||||
@ -106,6 +116,7 @@ public class ContainerController {
|
||||
}
|
||||
|
||||
@GetMapping("/listDeployedWorker")
|
||||
@ApiPermission(name = "Container-ListDeployedWorker", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<String> listDeployedWorker(Long appId, Long containerId, HttpServletResponse response) {
|
||||
AppInfoDO appInfoDO = appInfoRepository.findById(appId).orElseThrow(() -> new IllegalArgumentException("can't find app by id:" + appId));
|
||||
String targetServer = appInfoDO.getCurrentServer();
|
||||
|
@ -3,6 +3,9 @@ package tech.powerjob.server.web.controller;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.enums.InstanceStatus;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.common.utils.OmsFileUtils;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.StringPage;
|
||||
@ -46,8 +49,6 @@ import java.util.stream.Collectors;
|
||||
@RequestMapping("/instance")
|
||||
public class InstanceController {
|
||||
|
||||
|
||||
|
||||
@Resource
|
||||
private InstanceService instanceService;
|
||||
@Resource
|
||||
@ -59,18 +60,21 @@ public class InstanceController {
|
||||
private InstanceInfoRepository instanceInfoRepository;
|
||||
|
||||
@GetMapping("/stop")
|
||||
@ApiPermission(name = "Instance-Stop", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> stopInstance(Long appId,Long instanceId) {
|
||||
instanceService.stopInstance(appId,instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/retry")
|
||||
@ApiPermission(name = "Instance-Retry", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Void> retryInstance(String appId, Long instanceId) {
|
||||
instanceService.retryInstance(Long.valueOf(appId), instanceId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiPermission(name = "Instance-Detail", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<InstanceDetailVO> getInstanceDetail(Long appId, Long instanceId) {
|
||||
QueryInstanceDetailRequest queryInstanceDetailRequest = new QueryInstanceDetailRequest();
|
||||
queryInstanceDetailRequest.setAppId(appId);
|
||||
@ -97,11 +101,13 @@ public class InstanceController {
|
||||
}
|
||||
|
||||
@GetMapping("/log")
|
||||
@ApiPermission(name = "Instance-Log", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<StringPage> getInstanceLog(Long appId, Long instanceId, Long index) {
|
||||
return ResultDTO.success(instanceLogService.fetchInstanceLog(appId, instanceId, index));
|
||||
}
|
||||
|
||||
@GetMapping("/downloadLogUrl")
|
||||
@ApiPermission(name = "Instance-FetchDownloadLogUrl", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<String> getDownloadUrl(Long appId, Long instanceId) {
|
||||
return ResultDTO.success(instanceLogService.fetchDownloadUrl(appId, instanceId));
|
||||
}
|
||||
@ -133,6 +139,7 @@ public class InstanceController {
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Instance-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<InstanceInfoVO>> list(@RequestBody QueryInstanceRequest request) {
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "gmtModified");
|
||||
|
@ -3,6 +3,9 @@ package tech.powerjob.server.web.controller;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import tech.powerjob.common.request.http.SaveJobInfoRequest;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.common.constants.SwitchableStatus;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.JobInfoDO;
|
||||
@ -39,39 +42,46 @@ public class JobController {
|
||||
private JobInfoRepository jobInfoRepository;
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Job-Save", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> saveJobInfo(@RequestBody SaveJobInfoRequest request) {
|
||||
jobService.saveJob(request);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/copy")
|
||||
@ApiPermission(name = "Job-Copy", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<JobInfoVO> copyJob(String jobId) {
|
||||
return ResultDTO.success(JobInfoVO.from(jobService.copyJob(Long.valueOf(jobId))));
|
||||
}
|
||||
|
||||
@GetMapping("/export")
|
||||
@ApiPermission(name = "Job-Export", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<SaveJobInfoRequest> exportJob(String jobId) {
|
||||
return ResultDTO.success(jobService.exportJob(Long.valueOf(jobId)));
|
||||
}
|
||||
|
||||
@GetMapping("/disable")
|
||||
@ApiPermission(name = "Job-Disable", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> disableJob(String jobId) {
|
||||
jobService.disableJob(Long.valueOf(jobId));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "Job-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> deleteJob(String jobId) {
|
||||
jobService.deleteJob(Long.valueOf(jobId));
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/run")
|
||||
@ApiPermission(name = "Job-Copy", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Long> runImmediately(String appId, String jobId, @RequestParam(required = false) String instanceParams) {
|
||||
return ResultDTO.success(jobService.runJob(Long.valueOf(appId), Long.valueOf(jobId), instanceParams, 0L));
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Job-Copy", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<JobInfoVO>> listJobs(@RequestBody QueryJobInfoRequest request) {
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "id");
|
||||
|
@ -0,0 +1,117 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthConstants;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission;
|
||||
import tech.powerjob.server.auth.plugin.SaveNamespaceGrantPermissionPlugin;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.web.converter.NamespaceConverter;
|
||||
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
|
||||
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
|
||||
import tech.powerjob.server.web.request.QueryNamespaceRequest;
|
||||
import tech.powerjob.server.web.response.NamespaceBaseVO;
|
||||
import tech.powerjob.server.web.response.NamespaceVO;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
import tech.powerjob.server.web.service.NamespaceWebService;
|
||||
import tech.powerjob.server.web.service.UserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 命名空间 Controller
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/namespace")
|
||||
public class NamespaceController {
|
||||
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private UserWebService userWebService;
|
||||
@Resource
|
||||
private NamespaceWebService namespaceWebService;
|
||||
|
||||
@ResponseBody
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Namespace-Save", roleScope = RoleScope.NAMESPACE, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveNamespaceGrantPermissionPlugin.class)
|
||||
public ResultDTO<NamespaceBaseVO> save(@RequestBody ModifyNamespaceRequest req) {
|
||||
|
||||
NamespaceDO savedNamespace = namespaceWebService.save(req);
|
||||
return ResultDTO.success(NamespaceConverter.do2BaseVo(savedNamespace));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ApiPermission(name = "Namespace-Delete", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.SU)
|
||||
public ResultDTO<Void> deleteNamespace(Long id) {
|
||||
namespaceWebService.delete(id);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Namespace-List", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<PageResult<NamespaceVO>> listNamespace(@RequestBody QueryNamespaceRequest queryNamespaceRequest) {
|
||||
|
||||
Page<NamespaceDO> namespacePageResult = namespaceWebService.list(queryNamespaceRequest);
|
||||
|
||||
PageResult<NamespaceVO> ret = new PageResult<>(namespacePageResult);
|
||||
ret.setData(namespacePageResult.get().map(x -> {
|
||||
NamespaceVO detailVo = new NamespaceVO();
|
||||
NamespaceBaseVO baseVO = NamespaceConverter.do2BaseVo(x);
|
||||
BeanUtils.copyProperties(baseVO, detailVo);
|
||||
|
||||
fillDetail(x, detailVo);
|
||||
return detailVo;
|
||||
}).collect(Collectors.toList()));
|
||||
|
||||
return ResultDTO.success(ret);
|
||||
}
|
||||
|
||||
@PostMapping("/listAll")
|
||||
@ApiPermission(name = "Namespace-ListAll", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.NONE)
|
||||
public ResultDTO<List<NamespaceBaseVO>> listAll() {
|
||||
// 数量应该不是很多,先简单处理,不查询精简对象
|
||||
List<NamespaceDO> namespaceRepositoryAll = namespaceWebService.listAll();
|
||||
List<NamespaceBaseVO> namespaceBaseVOList = namespaceRepositoryAll.stream().map(nd -> {
|
||||
NamespaceBaseVO nv = new NamespaceBaseVO();
|
||||
nv.setId(nd.getId());
|
||||
nv.setCode(nd.getCode());
|
||||
nv.setName(nd.getName());
|
||||
nv.genShowName();
|
||||
return nv;
|
||||
}).collect(Collectors.toList());
|
||||
return ResultDTO.success(namespaceBaseVOList);
|
||||
}
|
||||
|
||||
private void fillDetail(NamespaceDO namespaceDO, NamespaceVO namespaceVO) {
|
||||
|
||||
Long namespaceId = namespaceVO.getId();
|
||||
|
||||
// 权限用户关系
|
||||
ComponentUserRoleInfo componentUserRoleInfo = webAuthService.fetchComponentUserRoleInfo(RoleScope.NAMESPACE, namespaceId);
|
||||
namespaceVO.setComponentUserRoleInfo(componentUserRoleInfo);
|
||||
|
||||
// 有权限用户填充 token
|
||||
boolean hasPermission = webAuthService.hasPermission(RoleScope.NAMESPACE, namespaceId, Permission.READ);
|
||||
namespaceVO.setToken(hasPermission ? namespaceDO.getToken() : AuthConstants.TIPS_NO_PERMISSION_TO_SEE);
|
||||
|
||||
// 用户信息
|
||||
namespaceVO.setCreatorShowName(userWebService.fetchBaseUserInfo(namespaceDO.getCreator()).map(UserBaseVO::getShowName).orElse(null));
|
||||
namespaceVO.setModifierShowName(userWebService.fetchBaseUserInfo(namespaceDO.getModifier()).map(UserBaseVO::getShowName).orElse(null));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.web.request.ChangePasswordRequest;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import tech.powerjob.server.web.service.PwjbUserWebService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* PowerJob 自带的登录体系
|
||||
* (同样视为第三方服务,与主框架没有任何关系)
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/13
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/pwjbUser")
|
||||
public class PwjbUserInfoController {
|
||||
|
||||
@Resource
|
||||
private PwjbUserWebService pwjbUserWebService;
|
||||
|
||||
/**
|
||||
* 创建第三方登录体系(PowerJob) 的账户,不允许修改
|
||||
* @param request 请求(此处复用了主框架请求,便于用户一次性把所有参数都填入)
|
||||
* @return 创建结果
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {
|
||||
pwjbUserWebService.save(request);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/changePassword")
|
||||
public ResultDTO<Void> changePassword(@RequestBody ChangePasswordRequest changePasswordRequest) {
|
||||
|
||||
pwjbUserWebService.changePassword(changePasswordRequest);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ import tech.powerjob.common.enums.InstanceStatus;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.common.constants.SwitchableStatus;
|
||||
import tech.powerjob.server.common.module.WorkerInfo;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.InstanceInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.JobInfoRepository;
|
||||
import tech.powerjob.server.remote.server.self.ServerInfoService;
|
||||
@ -21,6 +23,7 @@ import tech.powerjob.server.web.response.WorkerStatusVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -36,6 +39,8 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class SystemInfoController {
|
||||
|
||||
private final AppInfoRepository appInfoRepository;
|
||||
|
||||
private final JobInfoRepository jobInfoRepository;
|
||||
|
||||
private final InstanceInfoRepository instanceInfoRepository;
|
||||
@ -56,6 +61,14 @@ public class SystemInfoController {
|
||||
|
||||
SystemOverviewVO overview = new SystemOverviewVO();
|
||||
|
||||
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findById(appId);
|
||||
if (appInfoOpt.isPresent()) {
|
||||
AppInfoDO appInfo = appInfoOpt.get();
|
||||
|
||||
overview.setAppId(appId);
|
||||
overview.setAppName(appInfo.getAppName());
|
||||
}
|
||||
|
||||
// 总任务数量
|
||||
overview.setJobCount(jobInfoRepository.countByAppIdAndStatusNot(appId, SwitchableStatus.DELETED.getV()));
|
||||
// 运行任务数
|
||||
@ -69,7 +82,8 @@ public class SystemInfoController {
|
||||
// 服务器时间
|
||||
overview.setServerTime(DateFormatUtils.format(new Date(), OmsConstant.TIME_PATTERN));
|
||||
|
||||
overview.setServerInfo(serverInfoService.fetchServiceInfo());
|
||||
overview.setWebServerInfo(serverInfoService.fetchCurrentServerInfo());
|
||||
overview.setScheduleServerInfo(serverInfoService.fetchAppServerInfo(appId));
|
||||
|
||||
return ResultDTO.success(overview);
|
||||
}
|
||||
|
@ -1,21 +1,38 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
import tech.powerjob.server.core.service.UserService;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.PowerJobUser;
|
||||
import tech.powerjob.server.auth.Role;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.common.AuthErrorCode;
|
||||
import tech.powerjob.server.auth.common.PowerJobAuthException;
|
||||
import tech.powerjob.server.auth.service.WebAuthService;
|
||||
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
|
||||
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
import tech.powerjob.server.web.converter.NamespaceConverter;
|
||||
import tech.powerjob.server.web.converter.UserConverter;
|
||||
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
|
||||
import tech.powerjob.server.web.response.AppBaseVO;
|
||||
import tech.powerjob.server.web.response.NamespaceBaseVO;
|
||||
import tech.powerjob.server.web.response.UserBaseVO;
|
||||
import tech.powerjob.server.web.response.UserDetailVO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -27,22 +44,63 @@ import java.util.stream.Collectors;
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserInfoController {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
@Resource
|
||||
private UserInfoRepository userInfoRepository;
|
||||
@Resource
|
||||
private PowerJobLoginService powerJobLoginService;
|
||||
@Resource
|
||||
private WebAuthService webAuthService;
|
||||
@Resource
|
||||
private NamespaceRepository namespaceRepository;
|
||||
@Resource
|
||||
private AppInfoRepository appInfoRepository;
|
||||
|
||||
@SneakyThrows
|
||||
@PostMapping("/modify")
|
||||
public ResultDTO<Void> modifyUser(@RequestBody ModifyUserInfoRequest modifyUserInfoRequest, HttpServletRequest httpServletRequest) {
|
||||
|
||||
Optional<PowerJobUser> powerJobUserOpt = powerJobLoginService.ifLogin(httpServletRequest);
|
||||
if (!powerJobUserOpt.isPresent()) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
|
||||
}
|
||||
|
||||
Long userId = modifyUserInfoRequest.getId();
|
||||
Optional<UserInfoDO> userOpt = userInfoRepository.findById(userId);
|
||||
if (!userOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find user by userId:" + userId);
|
||||
}
|
||||
|
||||
if (!Objects.equals(powerJobUserOpt.get().getId(), userId)) {
|
||||
throw new IllegalAccessException("no permission to change others user info");
|
||||
}
|
||||
|
||||
UserInfoDO dbUser = userOpt.get();
|
||||
|
||||
// 拷入允许修改的内容
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getNick())) {
|
||||
dbUser.setNick(modifyUserInfoRequest.getNick());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getPhone())) {
|
||||
dbUser.setPhone(modifyUserInfoRequest.getPhone());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getEmail())) {
|
||||
dbUser.setEmail(modifyUserInfoRequest.getEmail());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getWebHook())) {
|
||||
dbUser.setWebHook(modifyUserInfoRequest.getWebHook());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(modifyUserInfoRequest.getExtra())) {
|
||||
dbUser.setExtra(modifyUserInfoRequest.getExtra());
|
||||
}
|
||||
|
||||
dbUser.setGmtModified(new Date());
|
||||
userInfoRepository.saveAndFlush(dbUser);
|
||||
|
||||
@PostMapping("save")
|
||||
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {
|
||||
UserInfoDO userInfoDO = new UserInfoDO();
|
||||
BeanUtils.copyProperties(request, userInfoDO);
|
||||
userService.save(userInfoDO);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("list")
|
||||
public ResultDTO<List<UserItemVO>> list(@RequestParam(required = false) String name) {
|
||||
@GetMapping("/list")
|
||||
public ResultDTO<List<UserBaseVO>> list(@RequestParam(required = false) String name) {
|
||||
|
||||
List<UserInfoDO> result;
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
@ -53,18 +111,76 @@ public class UserInfoController {
|
||||
return ResultDTO.success(convert(result));
|
||||
}
|
||||
|
||||
private static List<UserItemVO> convert(List<UserInfoDO> data) {
|
||||
@GetMapping("/detail")
|
||||
public ResultDTO<UserDetailVO> getUserDetail(HttpServletRequest httpServletRequest) {
|
||||
Optional<PowerJobUser> powerJobUserOpt = powerJobLoginService.ifLogin(httpServletRequest);
|
||||
if (!powerJobUserOpt.isPresent()) {
|
||||
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_LOGIN);
|
||||
}
|
||||
Optional<UserInfoDO> userinfoDoOpt = userInfoRepository.findById(powerJobUserOpt.get().getId());
|
||||
if (!userinfoDoOpt.isPresent()) {
|
||||
throw new IllegalArgumentException("can't find user by id: " + powerJobUserOpt.get().getId());
|
||||
}
|
||||
UserDetailVO userDetailVO = new UserDetailVO();
|
||||
BeanUtils.copyProperties(userinfoDoOpt.get(), userDetailVO);
|
||||
userDetailVO.genShowName();
|
||||
|
||||
// 权限信息
|
||||
Map<Role, List<Long>> globalPermissions = webAuthService.fetchMyPermissionTargets(RoleScope.GLOBAL);
|
||||
userDetailVO.setGlobalRoles(globalPermissions.keySet().stream().map(Enum::name).collect(Collectors.toList()));
|
||||
|
||||
Map<Role, List<Long>> namespacePermissions = webAuthService.fetchMyPermissionTargets(RoleScope.NAMESPACE);
|
||||
List<NamespaceDO> nsList = namespaceRepository.findAllByIdIn(mergeIds(namespacePermissions));
|
||||
Map<Long, NamespaceDO> id2NamespaceDo = Maps.newHashMap();
|
||||
nsList.forEach(x -> id2NamespaceDo.put(x.getId(), x));
|
||||
Map<String, List<NamespaceBaseVO>> role2NamespaceBaseVo = Maps.newHashMap();
|
||||
namespacePermissions.forEach((k, v) -> {
|
||||
List<NamespaceBaseVO> namespaceBaseVOS = Lists.newArrayList();
|
||||
role2NamespaceBaseVo.put(k.name(), namespaceBaseVOS);
|
||||
v.forEach(nId -> {
|
||||
NamespaceDO namespaceDO = id2NamespaceDo.get(nId);
|
||||
if (namespaceDO == null) {
|
||||
return;
|
||||
}
|
||||
NamespaceBaseVO namespaceBaseVO = NamespaceConverter.do2BaseVo(namespaceDO);
|
||||
namespaceBaseVOS.add(namespaceBaseVO);
|
||||
});
|
||||
});
|
||||
userDetailVO.setRole2NamespaceList(role2NamespaceBaseVo);
|
||||
|
||||
Map<Role, List<Long>> appPermissions = webAuthService.fetchMyPermissionTargets(RoleScope.APP);
|
||||
List<AppInfoDO> appList = appInfoRepository.findAllByIdIn(mergeIds(appPermissions));
|
||||
Map<Long, AppInfoDO> id2AppInfo = Maps.newHashMap();
|
||||
appList.forEach(x -> id2AppInfo.put(x.getId(), x));
|
||||
Map<String, List<AppBaseVO>> role2AppBaseVo = Maps.newHashMap();
|
||||
appPermissions.forEach((k, v) -> {
|
||||
List<AppBaseVO> appBaseVOS = Lists.newArrayList();
|
||||
role2AppBaseVo.put(k.name(), appBaseVOS);
|
||||
v.forEach(nId -> {
|
||||
AppInfoDO appInfoDO = id2AppInfo.get(nId);
|
||||
if (appInfoDO == null) {
|
||||
return;
|
||||
}
|
||||
AppBaseVO appBaseVO = new AppBaseVO();
|
||||
BeanUtils.copyProperties(appInfoDO, appBaseVO);
|
||||
appBaseVOS.add(appBaseVO);
|
||||
});
|
||||
});
|
||||
userDetailVO.setRole2AppList(role2AppBaseVo);
|
||||
|
||||
return ResultDTO.success(userDetailVO);
|
||||
}
|
||||
|
||||
private static List<UserBaseVO> convert(List<UserInfoDO> data) {
|
||||
if (CollectionUtils.isEmpty(data)) {
|
||||
return Lists.newLinkedList();
|
||||
}
|
||||
return data.stream().map(x -> new UserItemVO(x.getId(), x.getUsername())).collect(Collectors.toList());
|
||||
return data.stream().map(UserConverter::do2BaseVo).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static final class UserItemVO {
|
||||
private Long id;
|
||||
private String username;
|
||||
private static Set<Long> mergeIds(Map<?, List<Long>> map) {
|
||||
Set<Long> ids = Sets.newHashSet();
|
||||
map.values().forEach(ids::addAll);
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,28 @@
|
||||
package tech.powerjob.server.web.controller;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
|
||||
import tech.powerjob.common.request.http.SaveWorkflowRequest;
|
||||
import tech.powerjob.common.response.ResultDTO;
|
||||
import tech.powerjob.server.auth.Permission;
|
||||
import tech.powerjob.server.auth.RoleScope;
|
||||
import tech.powerjob.server.auth.interceptor.ApiPermission;
|
||||
import tech.powerjob.server.common.constants.SwitchableStatus;
|
||||
import tech.powerjob.server.core.workflow.WorkflowService;
|
||||
import tech.powerjob.server.persistence.PageResult;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.model.WorkflowNodeInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.WorkflowInfoRepository;
|
||||
import tech.powerjob.server.core.workflow.WorkflowService;
|
||||
import tech.powerjob.server.web.request.QueryWorkflowInfoRequest;
|
||||
import tech.powerjob.server.web.response.WorkflowInfoVO;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.function.LongToDoubleFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -40,34 +42,40 @@ public class WorkflowController {
|
||||
private WorkflowInfoRepository workflowInfoRepository;
|
||||
|
||||
@PostMapping("/save")
|
||||
@ApiPermission(name = "Workflow-Save", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Long> save(@RequestBody SaveWorkflowRequest req) throws ParseException {
|
||||
return ResultDTO.success(workflowService.saveWorkflow(req));
|
||||
}
|
||||
|
||||
@PostMapping("/copy")
|
||||
@ApiPermission(name = "Workflow-Copy", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Long> copy(Long workflowId, Long appId) {
|
||||
return ResultDTO.success(workflowService.copyWorkflow(workflowId,appId));
|
||||
}
|
||||
|
||||
@GetMapping("/disable")
|
||||
@ApiPermission(name = "Workflow-Disable", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> disableWorkflow(Long workflowId, Long appId) {
|
||||
workflowService.disableWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/enable")
|
||||
@ApiPermission(name = "Workflow-Enable", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> enableWorkflow(Long workflowId, Long appId) {
|
||||
workflowService.enableWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/delete")
|
||||
@ApiPermission(name = "Workflow-Delete", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<Void> deleteWorkflow(Long workflowId, Long appId) {
|
||||
workflowService.deleteWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiPermission(name = "Workflow-List", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<PageResult<WorkflowInfoVO>> list(@RequestBody QueryWorkflowInfoRequest req) {
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "gmtCreate");
|
||||
@ -89,6 +97,7 @@ public class WorkflowController {
|
||||
}
|
||||
|
||||
@GetMapping("/run")
|
||||
@ApiPermission(name = "Workflow-Run", roleScope = RoleScope.APP, requiredPermission = Permission.OPS)
|
||||
public ResultDTO<Long> runWorkflow(Long workflowId, Long appId,
|
||||
@RequestParam(required = false,defaultValue = "0") Long delay,
|
||||
@RequestParam(required = false) String initParams
|
||||
@ -97,12 +106,14 @@ public class WorkflowController {
|
||||
}
|
||||
|
||||
@GetMapping("/fetch")
|
||||
@ApiPermission(name = "Workflow-Fetch", roleScope = RoleScope.APP, requiredPermission = Permission.READ)
|
||||
public ResultDTO<WorkflowInfoVO> fetchWorkflow(Long workflowId, Long appId) {
|
||||
WorkflowInfoDO workflowInfoDO = workflowService.fetchWorkflow(workflowId, appId);
|
||||
return ResultDTO.success(WorkflowInfoVO.from(workflowInfoDO));
|
||||
}
|
||||
|
||||
@PostMapping("/saveNode")
|
||||
@ApiPermission(name = "Workflow-SaveNode", roleScope = RoleScope.APP, requiredPermission = Permission.WRITE)
|
||||
public ResultDTO<List<WorkflowNodeInfoDO>> addWorkflowNode(@RequestBody List<SaveWorkflowNodeRequest> request) {
|
||||
return ResultDTO.success(workflowService.saveWorkflowNode(request));
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user