Merge remote-tracking branch 'origin/master'
commit
918c34247c
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -145,6 +145,8 @@
|
|||
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
||||
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
|
||||
|
||||

|
||||
|
||||
### 工作流程
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|
|
@ -157,6 +159,8 @@
|
|||
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
|
||||
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
|
||||
|
||||

|
||||
|
||||
### 支付系统
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|
|
@ -192,6 +196,8 @@
|
|||
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
|
||||
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
|
||||
|
||||

|
||||
|
||||
### 数据报表
|
||||
|
||||
| | 功能 | 描述 |
|
||||
|
|
|
|||
|
|
@ -1,598 +0,0 @@
|
|||
package liquibase.database.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import liquibase.CatalogAndSchema;
|
||||
import liquibase.Scope;
|
||||
import liquibase.database.AbstractJdbcDatabase;
|
||||
import liquibase.database.DatabaseConnection;
|
||||
import liquibase.database.OfflineConnection;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.DatabaseException;
|
||||
import liquibase.exception.UnexpectedLiquibaseException;
|
||||
import liquibase.exception.ValidationErrors;
|
||||
import liquibase.executor.ExecutorService;
|
||||
import liquibase.statement.DatabaseFunction;
|
||||
import liquibase.statement.SequenceCurrentValueFunction;
|
||||
import liquibase.statement.SequenceNextValueFunction;
|
||||
import liquibase.statement.core.RawCallStatement;
|
||||
import liquibase.statement.core.RawSqlStatement;
|
||||
import liquibase.structure.DatabaseObject;
|
||||
import liquibase.structure.core.Catalog;
|
||||
import liquibase.structure.core.Index;
|
||||
import liquibase.structure.core.PrimaryKey;
|
||||
import liquibase.structure.core.Schema;
|
||||
import liquibase.util.JdbcUtils;
|
||||
import liquibase.util.StringUtil;
|
||||
|
||||
public class DmDatabase extends AbstractJdbcDatabase {
|
||||
private static final String PRODUCT_NAME = "DM DBMS";
|
||||
|
||||
@Override
|
||||
protected String getDefaultDatabaseProductName() {
|
||||
return PRODUCT_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this AbstractDatabase subclass the correct one to use for the given connection.
|
||||
*
|
||||
* @param conn
|
||||
*/
|
||||
@Override
|
||||
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
|
||||
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
|
||||
}
|
||||
|
||||
/**
|
||||
* If this database understands the given url, return the default driver class name. Otherwise return null.
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
@Override
|
||||
public String getDefaultDriver(String url) {
|
||||
if(url.startsWith("jdbc:dm")) {
|
||||
return "dm.jdbc.driver.DmDriver";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an all-lower-case short name of the product. Used for end-user selecting of database type
|
||||
* such as the DBMS precondition.
|
||||
*/
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "dm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDefaultPort() {
|
||||
return 5236;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this database support initially deferrable columns.
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsInitiallyDeferrableColumns() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTablespaces() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return PRIORITY_DEFAULT;
|
||||
}
|
||||
|
||||
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
|
||||
|
||||
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
|
||||
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
|
||||
public static final int ORACLE_12C_MAJOR_VERSION = 12;
|
||||
|
||||
private Set<String> reservedWords = new HashSet<>();
|
||||
private Set<String> userDefinedTypes;
|
||||
private Map<String, String> savedSessionNlsSettings;
|
||||
|
||||
private Boolean canAccessDbaRecycleBin;
|
||||
private Integer databaseMajorVersion;
|
||||
private Integer databaseMinorVersion;
|
||||
|
||||
/**
|
||||
* Default constructor for an object that represents the Oracle Database DBMS.
|
||||
*/
|
||||
public DmDatabase() {
|
||||
super.unquotedObjectsAreUppercased = true;
|
||||
//noinspection HardCodedStringLiteral
|
||||
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
|
||||
// Setting list of Oracle's native functions
|
||||
//noinspection HardCodedStringLiteral
|
||||
dateFunctions.add(new DatabaseFunction("SYSDATE"));
|
||||
//noinspection HardCodedStringLiteral
|
||||
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
|
||||
//noinspection HardCodedStringLiteral
|
||||
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
|
||||
//noinspection HardCodedStringLiteral
|
||||
super.sequenceNextValueFunction = "%s.nextval";
|
||||
//noinspection HardCodedStringLiteral
|
||||
super.sequenceCurrentValueFunction = "%s.currval";
|
||||
}
|
||||
|
||||
private void tryProxySession(final String url, final Connection con) {
|
||||
Matcher m = PROXY_USER.matcher(url);
|
||||
if (m.matches()) {
|
||||
Properties props = new Properties();
|
||||
props.put("PROXY_USER_NAME", m.group(1));
|
||||
try {
|
||||
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(con, 1, props);
|
||||
} catch (Exception e) {
|
||||
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDatabaseMajorVersion() throws DatabaseException {
|
||||
if (databaseMajorVersion == null) {
|
||||
return super.getDatabaseMajorVersion();
|
||||
} else {
|
||||
return databaseMajorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDatabaseMinorVersion() throws DatabaseException {
|
||||
if (databaseMinorVersion == null) {
|
||||
return super.getDatabaseMinorVersion();
|
||||
} else {
|
||||
return databaseMinorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcCatalogName(CatalogAndSchema schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcSchemaName(CatalogAndSchema schema) {
|
||||
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
|
||||
if (StringUtil.isEmpty(generationType)) {
|
||||
return super.getAutoIncrementClause();
|
||||
}
|
||||
|
||||
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
|
||||
String generationStrategy = generationType;
|
||||
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
|
||||
generationStrategy += " ON NULL";
|
||||
}
|
||||
return String.format(autoIncrementClause, generationStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generatePrimaryKeyName(String tableName) {
|
||||
if (tableName.length() > 27) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
|
||||
} else {
|
||||
//noinspection HardCodedStringLiteral
|
||||
return "PK_" + tableName.toUpperCase(Locale.US);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReservedWord(String objectName) {
|
||||
return reservedWords.contains(objectName.toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSequences() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle supports catalogs in liquibase terms
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsSchemas() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConnectionCatalogName() throws DatabaseException {
|
||||
if (getConnection() instanceof OfflineConnection) {
|
||||
return getConnection().getCatalog();
|
||||
}
|
||||
try {
|
||||
//noinspection HardCodedStringLiteral
|
||||
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
|
||||
} catch (Exception e) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultCatalogName() {//NOPMD
|
||||
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
|
||||
*
|
||||
* <p>Convert an ISO8601 date string to one of the following results:
|
||||
* to_date('1995-05-23', 'YYYY-MM-DD')
|
||||
* to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
|
||||
* <p>
|
||||
* Implementation restriction:<br>
|
||||
* Currently, only the following subsets of ISO8601 are supported:<br>
|
||||
* <ul>
|
||||
* <li>YYYY-MM-DD</li>
|
||||
* <li>YYYY-MM-DDThh:mm:ss</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public String getDateLiteral(String isoDate) {
|
||||
String normalLiteral = super.getDateLiteral(isoDate);
|
||||
|
||||
if (isDateOnly(isoDate)) {
|
||||
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
|
||||
} else if (isTimeOnly(isoDate)) {
|
||||
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
|
||||
} else if (isTimestamp(isoDate)) {
|
||||
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
|
||||
} else if (isDateTime(isoDate)) {
|
||||
int seppos = normalLiteral.lastIndexOf('.');
|
||||
if (seppos != -1) {
|
||||
normalLiteral = normalLiteral.substring(0, seppos) + "'";
|
||||
}
|
||||
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
|
||||
}
|
||||
return "UNSUPPORTED:" + isoDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSystemObject(DatabaseObject example) {
|
||||
if (example == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isLiquibaseObject(example)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (example instanceof Schema) {
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
|
||||
return true;
|
||||
}
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
|
||||
return true;
|
||||
}
|
||||
} else if (isSystemObject(example.getSchema())) {
|
||||
return true;
|
||||
}
|
||||
if (example instanceof Catalog) {
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
|
||||
return true;
|
||||
}
|
||||
} else if (example.getName() != null) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("BIN$")) { //oracle deleted table
|
||||
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
|
||||
if (!filteredInOriginalQuery) {
|
||||
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
|
||||
}
|
||||
|
||||
if (filteredInOriginalQuery) {
|
||||
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
|
||||
liquibase.statement.UniqueConstraint));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("DR$")) { //oracle index tables
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
|
||||
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("USLOG$")) { //for update materialized view
|
||||
return true;
|
||||
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.isSystemObject(example);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAutoIncrement() {
|
||||
// Oracle supports Identity beginning with version 12c
|
||||
boolean isAutoIncrementSupported = false;
|
||||
|
||||
try {
|
||||
if (getDatabaseMajorVersion() >= 12) {
|
||||
isAutoIncrementSupported = true;
|
||||
}
|
||||
|
||||
// Returning true will generate create table command with 'IDENTITY' clause, example:
|
||||
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
|
||||
|
||||
// While returning false will continue to generate create table command without 'IDENTITY' clause, example:
|
||||
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
|
||||
|
||||
} catch (DatabaseException ex) {
|
||||
isAutoIncrementSupported = false;
|
||||
}
|
||||
|
||||
return isAutoIncrementSupported;
|
||||
}
|
||||
|
||||
|
||||
// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
|
||||
// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
|
||||
//
|
||||
// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
|
||||
//
|
||||
// UniqueConstraint constraint = null;
|
||||
// for (Map map : maps) {
|
||||
// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
|
||||
// returnSet.add(constraint);
|
||||
// Table table = new Table((String) map.get("TABLE_NAME"));
|
||||
// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
|
||||
// }
|
||||
// }
|
||||
// if (constraint != null) {
|
||||
// returnSet.add(constraint);
|
||||
// }
|
||||
//
|
||||
// return returnSet;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public boolean supportsRestrictForeignKeys() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataTypeMaxParameters(String dataTypeName) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
|
||||
return 0;
|
||||
}
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
|
||||
return 0;
|
||||
}
|
||||
return super.getDataTypeMaxParameters(dataTypeName);
|
||||
}
|
||||
|
||||
public String getSystemTableWhereClause(String tableNameColumn) {
|
||||
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
|
||||
"AQ$",
|
||||
"DR$",
|
||||
"SYS_IOT_OVER",
|
||||
"MLOG$_",
|
||||
"RUPD$_",
|
||||
"WM$_",
|
||||
"ISEQ$$_",
|
||||
"USLOG$",
|
||||
"SYS_FBA"));
|
||||
|
||||
for (int i = 0;i<clauses.size(); i++) {
|
||||
clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
|
||||
}
|
||||
return "("+ StringUtil.join(clauses, " AND ") + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean jdbcCallsCatalogsSchemas() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Set<String> getUserDefinedTypes() {
|
||||
if (userDefinedTypes == null) {
|
||||
userDefinedTypes = new HashSet<>();
|
||||
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
|
||||
try {
|
||||
try {
|
||||
//noinspection HardCodedStringLiteral
|
||||
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
|
||||
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
|
||||
//noinspection HardCodedStringLiteral
|
||||
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
|
||||
}
|
||||
} catch (DatabaseException e) {
|
||||
//ignore error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userDefinedTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
|
||||
return databaseFunction.toString();
|
||||
}
|
||||
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
|
||||
SequenceCurrentValueFunction)) {
|
||||
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
|
||||
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
|
||||
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
|
||||
|
||||
}
|
||||
|
||||
return super.generateDatabaseFunctionValue(databaseFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationErrors validate() {
|
||||
ValidationErrors errors = super.validate();
|
||||
DatabaseConnection connection = getConnection();
|
||||
if ((connection == null) || (connection instanceof OfflineConnection)) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (!canAccessDbaRecycleBin()) {
|
||||
errors.addWarning(getDbaRecycleBinWarning());
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
}
|
||||
|
||||
public String getDbaRecycleBinWarning() {
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
|
||||
// HardCodedStringLiteral
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
|
||||
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
|
||||
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
|
||||
" issue.\n" +
|
||||
"\n" +
|
||||
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
|
||||
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
|
||||
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
|
||||
"\n" +
|
||||
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
|
||||
}
|
||||
|
||||
public boolean canAccessDbaRecycleBin() {
|
||||
if (canAccessDbaRecycleBin == null) {
|
||||
DatabaseConnection connection = getConnection();
|
||||
if ((connection == null) || (connection instanceof OfflineConnection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Statement statement = null;
|
||||
try {
|
||||
statement = ((JdbcConnection) connection).createStatement();
|
||||
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
|
||||
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
|
||||
this.canAccessDbaRecycleBin = true;
|
||||
} catch (Exception e) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
|
||||
this.canAccessDbaRecycleBin = false;
|
||||
} else {
|
||||
//noinspection HardCodedStringLiteral
|
||||
Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
|
||||
this.canAccessDbaRecycleBin = false;
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.close(null, statement);
|
||||
}
|
||||
}
|
||||
|
||||
return canAccessDbaRecycleBin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsNotNullConstraintNames() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
|
||||
* the following form (case-insensitive comparison):
|
||||
* 1st character: A-Z
|
||||
* 2..n characters: A-Z0-9$_#
|
||||
* The maximum length of an identifier differs by Oracle version and object type.
|
||||
*/
|
||||
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
|
||||
if ((identifier == null) || (identifier.length() < 1))
|
||||
return false;
|
||||
|
||||
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
|
||||
* we do know seem to be supported as 12cR2 long identifiers, so:
|
||||
*/
|
||||
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
|
||||
* is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
|
||||
* object types).
|
||||
*
|
||||
* @return the maximum length of an object identifier, in bytes
|
||||
*/
|
||||
public int getIdentifierMaximumLength() {
|
||||
try {
|
||||
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
|
||||
return SHORT_IDENTIFIERS_LENGTH;
|
||||
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
|
||||
return SHORT_IDENTIFIERS_LENGTH;
|
||||
} else {
|
||||
return LONG_IDENTIFIERS_LEGNTH;
|
||||
}
|
||||
} catch (DatabaseException ex) {
|
||||
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
package liquibase.datatype.core;
|
||||
|
||||
import liquibase.change.core.LoadDataChange;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.core.*;
|
||||
import liquibase.datatype.DataTypeInfo;
|
||||
import liquibase.datatype.DatabaseDataType;
|
||||
import liquibase.datatype.LiquibaseDataType;
|
||||
import liquibase.exception.UnexpectedLiquibaseException;
|
||||
import liquibase.statement.DatabaseFunction;
|
||||
import liquibase.util.StringUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
|
||||
public class BooleanType extends LiquibaseDataType {
|
||||
|
||||
@Override
|
||||
public DatabaseDataType toDatabaseDataType(Database database) {
|
||||
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
|
||||
if ((database instanceof Firebird3Database)) {
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
}
|
||||
|
||||
if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
|
||||
return new DatabaseDataType("SMALLINT");
|
||||
} else if (database instanceof MSSQLDatabase) {
|
||||
return new DatabaseDataType(database.escapeDataTypeName("bit"));
|
||||
} else if (database instanceof MySQLDatabase) {
|
||||
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
|
||||
return new DatabaseDataType("BIT", getParameters());
|
||||
}
|
||||
return new DatabaseDataType("BIT", 1);
|
||||
} else if (database instanceof OracleDatabase) {
|
||||
return new DatabaseDataType("NUMBER", 1);
|
||||
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
|
||||
return new DatabaseDataType("BIT");
|
||||
} else if (database instanceof DerbyDatabase) {
|
||||
if (((DerbyDatabase) database).supportsBooleanDataType()) {
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
} else {
|
||||
return new DatabaseDataType("SMALLINT");
|
||||
}
|
||||
} else if (database instanceof DB2Database) {
|
||||
if (((DB2Database) database).supportsBooleanDataType())
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
else
|
||||
return new DatabaseDataType("SMALLINT");
|
||||
} else if (database instanceof HsqlDatabase) {
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
} else if (database instanceof PostgresDatabase) {
|
||||
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
|
||||
return new DatabaseDataType("BIT", getParameters());
|
||||
}
|
||||
} else if (database instanceof DmDatabase) { // dhb52: DM Support
|
||||
return new DatabaseDataType("bit");
|
||||
}
|
||||
|
||||
return super.toDatabaseDataType(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String objectToSql(Object value, Database database) {
|
||||
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String returnValue;
|
||||
if (value instanceof String) {
|
||||
value = ((String) value).replaceAll("'", "");
|
||||
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
|
||||
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
} else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
|
||||
returnValue = "b'"
|
||||
+ value.toString()
|
||||
.replace("b", "")
|
||||
.replace("\"", "")
|
||||
.replace("::it", "")
|
||||
+ "'::\"bit\"";
|
||||
} else {
|
||||
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
|
||||
}
|
||||
} else if (value instanceof Long) {
|
||||
if (Long.valueOf(1).equals(value)) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
}
|
||||
} else if (value instanceof Number) {
|
||||
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
}
|
||||
} else if (value instanceof DatabaseFunction) {
|
||||
return value.toString();
|
||||
} else if (value instanceof Boolean) {
|
||||
if (((Boolean) value)) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
}
|
||||
} else {
|
||||
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
protected boolean isNumericBoolean(Database database) {
|
||||
if (database instanceof Firebird3Database) {
|
||||
return false;
|
||||
}
|
||||
if (database instanceof DerbyDatabase) {
|
||||
return !((DerbyDatabase) database).supportsBooleanDataType();
|
||||
} else if (database instanceof DB2Database) {
|
||||
return !((DB2Database) database).supportsBooleanDataType();
|
||||
}
|
||||
return (database instanceof Db2zDatabase)
|
||||
|| (database instanceof FirebirdDatabase)
|
||||
|| (database instanceof MSSQLDatabase)
|
||||
|| (database instanceof MySQLDatabase)
|
||||
|| (database instanceof OracleDatabase)
|
||||
|| (database instanceof SQLiteDatabase)
|
||||
|| (database instanceof SybaseASADatabase)
|
||||
|| (database instanceof SybaseDatabase)
|
||||
|| (database instanceof DmDatabase); // dhb52: DM Support
|
||||
}
|
||||
|
||||
/**
|
||||
* The database-specific value to use for "false" "boolean" columns.
|
||||
*/
|
||||
public String getFalseBooleanValue(Database database) {
|
||||
if (isNumericBoolean(database)) {
|
||||
return "0";
|
||||
}
|
||||
if (database instanceof InformixDatabase) {
|
||||
return "'f'";
|
||||
}
|
||||
return "FALSE";
|
||||
}
|
||||
|
||||
/**
|
||||
* The database-specific value to use for "true" "boolean" columns.
|
||||
*/
|
||||
public String getTrueBooleanValue(Database database) {
|
||||
if (isNumericBoolean(database)) {
|
||||
return "1";
|
||||
}
|
||||
if (database instanceof InformixDatabase) {
|
||||
return "'t'";
|
||||
}
|
||||
return "TRUE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
|
||||
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +0,0 @@
|
|||
防止IDEA将`.`和`/`混为一谈
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
liquibase.database.core.CockroachDatabase
|
||||
liquibase.database.core.DB2Database
|
||||
liquibase.database.core.Db2zDatabase
|
||||
liquibase.database.core.DerbyDatabase
|
||||
liquibase.database.core.Firebird3Database
|
||||
liquibase.database.core.FirebirdDatabase
|
||||
liquibase.database.core.H2Database
|
||||
liquibase.database.core.HsqlDatabase
|
||||
liquibase.database.core.InformixDatabase
|
||||
liquibase.database.core.Ingres9Database
|
||||
liquibase.database.core.MSSQLDatabase
|
||||
liquibase.database.core.MariaDBDatabase
|
||||
liquibase.database.core.MockDatabase
|
||||
liquibase.database.core.MySQLDatabase
|
||||
liquibase.database.core.OracleDatabase
|
||||
liquibase.database.core.PostgresDatabase
|
||||
liquibase.database.core.SQLiteDatabase
|
||||
liquibase.database.core.SybaseASADatabase
|
||||
liquibase.database.core.SybaseDatabase
|
||||
liquibase.database.core.DmDatabase
|
||||
liquibase.database.core.UnsupportedDatabase
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
-- 菜单 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status, component_name
|
||||
)
|
||||
VALUES (
|
||||
'Ureport2报表管理', '', 2, 0, 1281,
|
||||
'ureport-data', '', 'report/ureport/index', 0, 'UReportData'
|
||||
);
|
||||
|
||||
-- 按钮父菜单ID
|
||||
-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
|
||||
SELECT @parentId := LAST_INSERT_ID();
|
||||
|
||||
-- 按钮 SQL
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'Ureport2报表查询', 'report:ureport-data:query', 3, 1, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'Ureport2报表创建', 'report:ureport-data:create', 3, 2, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'Ureport2报表更新', 'report:ureport-data:update', 3, 3, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'Ureport2报表删除', 'report:ureport-data:delete', 3, 4, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
INSERT INTO system_menu(
|
||||
name, permission, type, sort, parent_id,
|
||||
path, icon, component, status
|
||||
)
|
||||
VALUES (
|
||||
'Ureport2报表导出', 'report:ureport-data:export', 3, 5, @parentId,
|
||||
'', '', '', 0
|
||||
);
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `report_ureport_data`;
|
||||
CREATE TABLE `report_ureport_data` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件名称',
|
||||
`status` tinyint(4) NOT NULL COMMENT '状态',
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文件内容',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Ureport2报表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of report_ureport_data
|
||||
-- ----------------------------
|
||||
INSERT INTO `report_ureport_data` VALUES (11, 'role.ureport.xml', 0, '<?xml version=\"1.0\" encoding=\"UTF-8\"?><ureport><cell expand=\"Down\" name=\"A1\" row=\"1\" col=\"1\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><dataset-value dataset-name=\"role\" aggregate=\"group\" property=\"name\" order=\"none\" mapping-type=\"simple\"></dataset-value></cell><cell expand=\"Down\" name=\"B1\" row=\"1\" col=\"2\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><dataset-value dataset-name=\"role\" aggregate=\"group\" property=\"code\" order=\"none\" mapping-type=\"simple\"></dataset-value></cell><cell expand=\"Down\" name=\"C1\" row=\"1\" col=\"3\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><dataset-value dataset-name=\"role\" aggregate=\"group\" property=\"status\" order=\"none\" mapping-type=\"simple\"></dataset-value></cell><cell expand=\"None\" name=\"D1\" row=\"1\" col=\"4\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"A2\" row=\"2\" col=\"1\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"B2\" row=\"2\" col=\"2\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"C2\" row=\"2\" col=\"3\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"D2\" row=\"2\" col=\"4\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"A3\" row=\"3\" col=\"1\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"B3\" row=\"3\" col=\"2\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"C3\" row=\"3\" col=\"3\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><cell expand=\"None\" name=\"D3\" row=\"3\" col=\"4\"><cell-style font-size=\"10\" align=\"center\" valign=\"middle\"></cell-style><simple-value><![CDATA[]]></simple-value></cell><row row-number=\"1\" height=\"18\"/><row row-number=\"2\" height=\"18\"/><row row-number=\"3\" height=\"18\"/><column col-number=\"1\" width=\"80\"/><column col-number=\"2\" width=\"80\"/><column col-number=\"3\" width=\"80\"/><column col-number=\"4\" width=\"80\"/><datasource name=\"UReportDataSource\" type=\"buildin\"><dataset name=\"role\" type=\"sql\"><sql><![CDATA[select * from system_role]]></sql><field name=\"id\"/><field name=\"name\"/><field name=\"code\"/><field name=\"sort\"/><field name=\"data_scope\"/><field name=\"data_scope_dept_ids\"/><field name=\"status\"/><field name=\"type\"/><field name=\"remark\"/><field name=\"creator\"/><field name=\"create_time\"/><field name=\"updater\"/><field name=\"update_time\"/><field name=\"deleted\"/><field name=\"tenant_id\"/></dataset></datasource><paper type=\"A4\" left-margin=\"90\" right-margin=\"90\"\n top-margin=\"72\" bottom-margin=\"72\" paging-mode=\"fitpage\" fixrows=\"0\"\n width=\"595\" height=\"842\" orientation=\"portrait\" html-report-align=\"left\" bg-image=\"\" html-interval-refresh-value=\"0\" column-enabled=\"false\"></paper></ureport>', NULL, NULL, '2023-11-25 22:40:58', NULL, '2023-11-25 23:00:42', b'0', 0);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -30,6 +30,7 @@
|
|||
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>
|
||||
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
|
||||
<mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>
|
||||
<easy-trans.version>2.2.11</easy-trans.version>
|
||||
<redisson.version>3.18.0</redisson.version> <!-- Spring Boot 2.X 最多使用 3.18.0 版本,否则会报 Tuple NoClassDefFoundError -->
|
||||
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
|
||||
<!-- 消息队列 -->
|
||||
|
|
@ -129,11 +130,6 @@
|
|||
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.mouzt</groupId>
|
||||
<artifactId>bizlog-sdk</artifactId>
|
||||
|
|
@ -254,6 +250,32 @@
|
|||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-spring-boot-starter</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-anno</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
<module>yudao-spring-boot-starter-excel</module>
|
||||
<module>yudao-spring-boot-starter-test</module>
|
||||
|
||||
<module>yudao-spring-boot-starter-biz-operatelog</module>
|
||||
<module>yudao-spring-boot-starter-biz-tenant</module>
|
||||
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
||||
<module>yudao-spring-boot-starter-biz-ip</module>
|
||||
|
|
|
|||
|
|
@ -138,6 +138,11 @@
|
|||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ public class NumberUtils {
|
|||
return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null;
|
||||
}
|
||||
|
||||
public static Integer parseInt(String str) {
|
||||
return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过经纬度获取地球上两点之间的距离
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.common.util.spring;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import org.springframework.aop.framework.AdvisedSupport;
|
||||
import org.springframework.aop.framework.AopProxy;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
/**
|
||||
* Spring AOP 工具类
|
||||
*
|
||||
* 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
|
||||
*/
|
||||
public class SpringAopUtils {
|
||||
|
||||
/**
|
||||
* 获取代理的目标对象
|
||||
*
|
||||
* @param proxy 代理对象
|
||||
* @return 目标对象
|
||||
*/
|
||||
public static Object getTarget(Object proxy) throws Exception {
|
||||
// 不是代理对象
|
||||
if (!AopUtils.isAopProxy(proxy)) {
|
||||
return proxy;
|
||||
}
|
||||
// Jdk 代理
|
||||
if (AopUtils.isJdkDynamicProxy(proxy)) {
|
||||
return getJdkDynamicProxyTargetObject(proxy);
|
||||
}
|
||||
// Cglib 代理
|
||||
return getCglibProxyTargetObject(proxy);
|
||||
}
|
||||
|
||||
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
|
||||
Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
|
||||
AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
|
||||
return advisedSupport.getTargetSource().getTarget();
|
||||
}
|
||||
|
||||
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
|
||||
AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
|
||||
AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
|
||||
return advisedSupport.getTargetSource().getTarget();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package cn.iocoder.yudao.framework.common.util.spring;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring 工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class SpringUtils extends SpringUtil {
|
||||
|
||||
/**
|
||||
* 是否为生产环境
|
||||
*
|
||||
* @return 是否生产环境
|
||||
*/
|
||||
public static boolean isProd() {
|
||||
String activeProfile = getActiveProfile();
|
||||
return Objects.equals("prod", activeProfile);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package cn.iocoder.yudao.framework.common.util.string;
|
||||
|
||||
import cn.hutool.core.text.StrPool;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -45,6 +47,15 @@ public class StrUtils {
|
|||
return Arrays.stream(longs).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static Set<Long> splitToLongSet(String value) {
|
||||
return splitToLongSet(value, StrPool.COMMA);
|
||||
}
|
||||
|
||||
public static Set<Long> splitToLongSet(String value, CharSequence separator) {
|
||||
long[] longs = StrUtil.splitToLong(value, separator);
|
||||
return Arrays.stream(longs).boxed().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static List<Integer> splitToInteger(String value, CharSequence separator) {
|
||||
int[] integers = StrUtil.splitToInt(value, separator);
|
||||
return Arrays.stream(integers).boxed().collect(Collectors.toList());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package com.fhs.trans.service;
|
||||
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 只有实现了这个接口的才能自动翻译
|
||||
*
|
||||
* 为什么要赋值粘贴到 yudao-common 包下?
|
||||
* 因为 AutoTransable 属于 easy-trans-service 下,无法方便的在 yudao-module-xxx-api 模块下使用
|
||||
*
|
||||
* @author jackwang
|
||||
* @since 2020-05-19 10:26:15
|
||||
*/
|
||||
public interface AutoTransable<V extends VO> {
|
||||
|
||||
/**
|
||||
* 根据 ids 查询数据列表
|
||||
*
|
||||
* 改方法已过期啦,请使用 selectByIds
|
||||
*
|
||||
* @param ids 编号数组
|
||||
* @return 数据列表
|
||||
*/
|
||||
@Deprecated
|
||||
default List<V> findByIds(List<? extends Object> ids){
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ids 查询
|
||||
*
|
||||
* @param ids 编号数组
|
||||
* @return 数据列表
|
||||
*/
|
||||
default List<V> selectByIds(List<? extends Object> ids){
|
||||
return this.findByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 db 中所有的数据
|
||||
*
|
||||
* @return db 中所有的数据
|
||||
*/
|
||||
default List<V> select(){
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 id 获取 vo
|
||||
*
|
||||
* @param primaryValue id
|
||||
* @return vo
|
||||
*/
|
||||
V selectById(Object primaryValue);
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
|||
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* 数据权限 Util
|
||||
*
|
||||
|
|
@ -40,4 +42,22 @@ public class DataPermissionUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略数据权限,执行对应的逻辑
|
||||
*
|
||||
* @param callable 逻辑
|
||||
* @return 执行结果
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static <T> T executeIgnore(Callable<T> callable) {
|
||||
DataPermission dataPermission = getDisableDataPermissionDisable();
|
||||
DataPermissionContextHolder.add(dataPermission);
|
||||
try {
|
||||
// 执行 callable
|
||||
return callable.call();
|
||||
} finally {
|
||||
DataPermissionContextHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -522,6 +522,29 @@ id,name,type,parentId
|
|||
441931,凤岗镇,4,441900
|
||||
441932,长安镇,4,441900
|
||||
442000,中山市,3,440000
|
||||
442001,石岐街道,4,442000
|
||||
442002,东区街道,4,442000
|
||||
442003,中山港街道,4,442000
|
||||
442004,西区街道,4,442000
|
||||
442005,南区街道,4,442000
|
||||
442006,五桂山街道,4,442000
|
||||
442007,民众街道,4,442000
|
||||
442008,南朗街道,4,442000
|
||||
442009,黄圃镇,4,442000
|
||||
442010,东凤镇,4,442000
|
||||
442011,古镇镇,4,442000
|
||||
442012,沙溪镇,4,442000
|
||||
442013,坦洲镇,4,442000
|
||||
442014,港口镇,4,442000
|
||||
442015,三角镇,4,442000
|
||||
442016,横栏镇,4,442000
|
||||
442017,南头镇,4,442000
|
||||
442018,阜沙镇,4,442000
|
||||
442019,三乡镇,4,442000
|
||||
442020,板芙镇,4,442000
|
||||
442021,大涌镇,4,442000
|
||||
442022,神湾镇,4,442000
|
||||
442023,小榄镇,4,442000
|
||||
445100,潮州市,3,440000
|
||||
445200,揭阳市,3,440000
|
||||
445300,云浮市,3,440000
|
||||
|
|
|
|||
|
|
|
@ -1,58 +0,0 @@
|
|||
<?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>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>操作日志</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 远程调用相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-rpc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkServiceImpl;
|
||||
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
public class YudaoOperateLogAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public OperateLogAspect operateLogAspect() {
|
||||
return new OperateLogAspect();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
|
||||
return new OperateLogFrameworkServiceImpl(operateLogApi);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.config;
|
||||
|
||||
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
/**
|
||||
* 操作日志使用到 Feign 的配置项
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableFeignClients(clients = OperateLogApi.class) // 主要是引入相关的 API 服务
|
||||
public class YudaoOperateLogRpcAutoConfiguration {
|
||||
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.annotations;
|
||||
|
||||
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 操作日志注解
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface OperateLog {
|
||||
|
||||
// ========== 模块字段 ==========
|
||||
|
||||
/**
|
||||
* 操作模块
|
||||
*
|
||||
* 为空时,会尝试读取 {@link Tag#name()} 属性
|
||||
*/
|
||||
String module() default "";
|
||||
/**
|
||||
* 操作名
|
||||
*
|
||||
* 为空时,会尝试读取 {@link Operation#summary()} 属性
|
||||
*/
|
||||
String name() default "";
|
||||
/**
|
||||
* 操作分类
|
||||
*
|
||||
* 实际并不是数组,因为枚举不能设置 null 作为默认值
|
||||
*/
|
||||
OperateTypeEnum[] type() default {};
|
||||
|
||||
// ========== 开关字段 ==========
|
||||
|
||||
/**
|
||||
* 是否记录操作日志
|
||||
*/
|
||||
boolean enable() default true;
|
||||
/**
|
||||
* 是否记录方法参数
|
||||
*/
|
||||
boolean logArgs() default true;
|
||||
/**
|
||||
* 是否记录方法结果的数据
|
||||
*/
|
||||
boolean logResultData() default true;
|
||||
|
||||
}
|
||||
|
|
@ -1,380 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.aop;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLog;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
|
||||
|
||||
/**
|
||||
* 拦截使用 @OperateLog 注解,如果满足条件,则生成操作日志。
|
||||
* 满足如下任一条件,则会进行记录:
|
||||
* 1. 使用 @ApiOperation + 非 @GetMapping
|
||||
* 2. 使用 @OperateLog 注解
|
||||
* <p>
|
||||
* 但是,如果声明 @OperateLog 注解时,将 enable 属性设置为 false 时,强制不记录。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
public class OperateLogAspect {
|
||||
|
||||
/**
|
||||
* 用于记录操作内容的上下文
|
||||
*
|
||||
* @see OperateLog#getContent()
|
||||
*/
|
||||
private static final ThreadLocal<String> CONTENT = new ThreadLocal<>();
|
||||
/**
|
||||
* 用于记录拓展字段的上下文
|
||||
*
|
||||
* @see OperateLog#getExts()
|
||||
*/
|
||||
private static final ThreadLocal<Map<String, Object>> EXTS = new ThreadLocal<>();
|
||||
|
||||
@Resource
|
||||
private OperateLogFrameworkService operateLogFrameworkService;
|
||||
|
||||
@Around("@annotation(operation)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, Operation operation) throws Throwable {
|
||||
// 可能也添加了 @ApiOperation 注解
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog = getMethodAnnotation(joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog.class);
|
||||
return around0(joinPoint, operateLog, operation);
|
||||
}
|
||||
|
||||
@Around("!@annotation(io.swagger.v3.oas.annotations.Operation) && @annotation(operateLog)")
|
||||
// 兼容处理,只添加 @OperateLog 注解的情况
|
||||
public Object around(ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog) throws Throwable {
|
||||
return around0(joinPoint, operateLog, null);
|
||||
}
|
||||
|
||||
private Object around0(ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
|
||||
Operation operation) throws Throwable {
|
||||
// 目前,只有管理员,才记录操作日志!所以非管理员,直接调用,不进行记录
|
||||
Integer userType = WebFrameworkUtils.getLoginUserType();
|
||||
if (!Objects.equals(userType, UserTypeEnum.ADMIN.getValue())) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
// 记录开始时间
|
||||
LocalDateTime startTime = LocalDateTime.now();
|
||||
try {
|
||||
// 执行原有方法
|
||||
Object result = joinPoint.proceed();
|
||||
// 记录正常执行时的操作日志
|
||||
this.log(joinPoint, operateLog, operation, startTime, result, null);
|
||||
return result;
|
||||
} catch (Throwable exception) {
|
||||
this.log(joinPoint, operateLog, operation, startTime, null, exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
clearThreadLocal();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContent(String content) {
|
||||
CONTENT.set(content);
|
||||
}
|
||||
|
||||
public static void addExt(String key, Object value) {
|
||||
if (EXTS.get() == null) {
|
||||
EXTS.set(new HashMap<>());
|
||||
}
|
||||
EXTS.get().put(key, value);
|
||||
}
|
||||
|
||||
private static void clearThreadLocal() {
|
||||
CONTENT.remove();
|
||||
EXTS.remove();
|
||||
}
|
||||
|
||||
private void log(ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
|
||||
Operation operation,
|
||||
LocalDateTime startTime, Object result, Throwable exception) {
|
||||
try {
|
||||
// 判断不记录的情况
|
||||
if (!isLogEnable(joinPoint, operateLog)) {
|
||||
return;
|
||||
}
|
||||
// 真正记录操作日志
|
||||
this.log0(joinPoint, operateLog, operation, startTime, result, exception);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) operateLog({}) apiOperation({}) result({}) exception({}) ]",
|
||||
joinPoint, operateLog, operation, result, exception, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void log0(ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
|
||||
Operation operation,
|
||||
LocalDateTime startTime, Object result, Throwable exception) {
|
||||
OperateLog operateLogObj = new OperateLog();
|
||||
// 补全通用字段
|
||||
operateLogObj.setTraceId(TracerUtils.getTraceId());
|
||||
operateLogObj.setStartTime(startTime);
|
||||
// 补充用户信息
|
||||
fillUserFields(operateLogObj);
|
||||
// 补全模块信息
|
||||
fillModuleFields(operateLogObj, joinPoint, operateLog, operation);
|
||||
// 补全请求信息
|
||||
fillRequestFields(operateLogObj);
|
||||
// 补全方法信息
|
||||
fillMethodFields(operateLogObj, joinPoint, operateLog, startTime, result, exception);
|
||||
|
||||
// 异步记录日志
|
||||
operateLogFrameworkService.createOperateLog(operateLogObj);
|
||||
}
|
||||
|
||||
private static void fillUserFields(OperateLog operateLogObj) {
|
||||
operateLogObj.setUserId(WebFrameworkUtils.getLoginUserId());
|
||||
operateLogObj.setUserType(WebFrameworkUtils.getLoginUserType());
|
||||
}
|
||||
|
||||
private static void fillModuleFields(OperateLog operateLogObj,
|
||||
ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
|
||||
Operation operation) {
|
||||
// module 属性
|
||||
if (operateLog != null) {
|
||||
operateLogObj.setModule(operateLog.module());
|
||||
}
|
||||
if (StrUtil.isEmpty(operateLogObj.getModule())) {
|
||||
Tag tag = getClassAnnotation(joinPoint, Tag.class);
|
||||
if (tag != null) {
|
||||
// 优先读取 @Tag 的 name 属性
|
||||
if (StrUtil.isNotEmpty(tag.name())) {
|
||||
operateLogObj.setModule(tag.name());
|
||||
}
|
||||
// 没有的话,读取 @API 的 description 属性
|
||||
if (StrUtil.isEmpty(operateLogObj.getModule()) && ArrayUtil.isNotEmpty(tag.description())) {
|
||||
operateLogObj.setModule(tag.description());
|
||||
}
|
||||
}
|
||||
}
|
||||
// name 属性
|
||||
if (operateLog != null) {
|
||||
operateLogObj.setName(operateLog.name());
|
||||
}
|
||||
if (StrUtil.isEmpty(operateLogObj.getName()) && operation != null) {
|
||||
operateLogObj.setName(operation.summary());
|
||||
}
|
||||
// type 属性
|
||||
if (operateLog != null && ArrayUtil.isNotEmpty(operateLog.type())) {
|
||||
operateLogObj.setType(operateLog.type()[0].getType());
|
||||
}
|
||||
if (operateLogObj.getType() == null) {
|
||||
RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
|
||||
OperateTypeEnum operateLogType = convertOperateLogType(requestMethod);
|
||||
operateLogObj.setType(operateLogType != null ? operateLogType.getType() : null);
|
||||
}
|
||||
// content 和 exts 属性
|
||||
operateLogObj.setContent(CONTENT.get());
|
||||
operateLogObj.setExts(EXTS.get());
|
||||
}
|
||||
|
||||
private static void fillRequestFields(OperateLog operateLogObj) {
|
||||
// 获得 Request 对象
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
// 补全请求信息
|
||||
operateLogObj.setRequestMethod(request.getMethod());
|
||||
operateLogObj.setRequestUrl(request.getRequestURI());
|
||||
operateLogObj.setUserIp(ServletUtils.getClientIP(request));
|
||||
operateLogObj.setUserAgent(ServletUtils.getUserAgent(request));
|
||||
}
|
||||
|
||||
private static void fillMethodFields(OperateLog operateLogObj,
|
||||
ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
|
||||
LocalDateTime startTime, Object result, Throwable exception) {
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
operateLogObj.setJavaMethod(methodSignature.toString());
|
||||
if (operateLog == null || operateLog.logArgs()) {
|
||||
operateLogObj.setJavaMethodArgs(obtainMethodArgs(joinPoint));
|
||||
}
|
||||
if (operateLog == null || operateLog.logResultData()) {
|
||||
operateLogObj.setResultData(obtainResultData(result));
|
||||
}
|
||||
operateLogObj.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
|
||||
// (正常)处理 resultCode 和 resultMsg 字段
|
||||
if (result instanceof CommonResult) {
|
||||
CommonResult<?> commonResult = (CommonResult<?>) result;
|
||||
operateLogObj.setResultCode(commonResult.getCode());
|
||||
operateLogObj.setResultMsg(commonResult.getMsg());
|
||||
} else {
|
||||
operateLogObj.setResultCode(SUCCESS.getCode());
|
||||
}
|
||||
// (异常)处理 resultCode 和 resultMsg 字段
|
||||
if (exception != null) {
|
||||
operateLogObj.setResultCode(INTERNAL_SERVER_ERROR.getCode());
|
||||
operateLogObj.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLogEnable(ProceedingJoinPoint joinPoint,
|
||||
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog) {
|
||||
// 有 @OperateLog 注解的情况下
|
||||
if (operateLog != null) {
|
||||
return operateLog.enable();
|
||||
}
|
||||
// Cloud 专属逻辑:如果是 RPC 请求,则必须 @OperateLog 注解,才会记录操作日志
|
||||
String className = joinPoint.getSignature().getDeclaringType().getName();
|
||||
if (WebFrameworkUtils.isRpcRequest(className)) {
|
||||
return false;
|
||||
}
|
||||
// 没有 @ApiOperation 注解的情况下,只记录 POST、PUT、DELETE 的情况
|
||||
return obtainFirstLogRequestMethod(obtainRequestMethod(joinPoint)) != null;
|
||||
}
|
||||
|
||||
private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) {
|
||||
if (ArrayUtil.isEmpty(requestMethods)) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.stream(requestMethods).filter(requestMethod ->
|
||||
requestMethod == RequestMethod.POST
|
||||
|| requestMethod == RequestMethod.PUT
|
||||
|| requestMethod == RequestMethod.DELETE)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) {
|
||||
if (ArrayUtil.isEmpty(requestMethods)) {
|
||||
return null;
|
||||
}
|
||||
// 优先,匹配最优的 POST、PUT、DELETE
|
||||
RequestMethod result = obtainFirstLogRequestMethod(requestMethods);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
// 然后,匹配次优的 GET
|
||||
result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET)
|
||||
.findFirst().orElse(null);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
// 兜底,获得第一个
|
||||
return requestMethods[0];
|
||||
}
|
||||
|
||||
private static OperateTypeEnum convertOperateLogType(RequestMethod requestMethod) {
|
||||
if (requestMethod == null) {
|
||||
return null;
|
||||
}
|
||||
switch (requestMethod) {
|
||||
case GET:
|
||||
return OperateTypeEnum.GET;
|
||||
case POST:
|
||||
return OperateTypeEnum.CREATE;
|
||||
case PUT:
|
||||
return OperateTypeEnum.UPDATE;
|
||||
case DELETE:
|
||||
return OperateTypeEnum.DELETE;
|
||||
default:
|
||||
return OperateTypeEnum.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) {
|
||||
RequestMapping requestMapping = AnnotationUtils.getAnnotation( // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解
|
||||
((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class);
|
||||
return requestMapping != null ? requestMapping.method() : new RequestMethod[]{};
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
|
||||
return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
|
||||
return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
|
||||
}
|
||||
|
||||
private static String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
|
||||
// TODO 提升:参数脱敏和忽略
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
String[] argNames = methodSignature.getParameterNames();
|
||||
Object[] argValues = joinPoint.getArgs();
|
||||
// 拼接参数
|
||||
Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);
|
||||
for (int i = 0; i < argNames.length; i++) {
|
||||
String argName = argNames[i];
|
||||
Object argValue = argValues[i];
|
||||
// 被忽略时,标记为 ignore 字符串,避免和 null 混在一起
|
||||
args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]");
|
||||
}
|
||||
return JsonUtils.toJsonString(args);
|
||||
}
|
||||
|
||||
private static String obtainResultData(Object result) {
|
||||
// TODO 提升:结果脱敏和忽略
|
||||
if (result instanceof CommonResult) {
|
||||
result = ((CommonResult<?>) result).getData();
|
||||
}
|
||||
return JsonUtils.toJsonString(result);
|
||||
}
|
||||
|
||||
private static boolean isIgnoreArgs(Object object) {
|
||||
Class<?> clazz = object.getClass();
|
||||
// 处理数组的情况
|
||||
if (clazz.isArray()) {
|
||||
return IntStream.range(0, Array.getLength(object))
|
||||
.anyMatch(index -> isIgnoreArgs(Array.get(object, index)));
|
||||
}
|
||||
// 递归,处理数组、Collection、Map 的情况
|
||||
if (Collection.class.isAssignableFrom(clazz)) {
|
||||
return ((Collection<?>) object).stream()
|
||||
.anyMatch((Predicate<Object>) OperateLogAspect::isIgnoreArgs);
|
||||
}
|
||||
if (Map.class.isAssignableFrom(clazz)) {
|
||||
return isIgnoreArgs(((Map<?, ?>) object).values());
|
||||
}
|
||||
// obj
|
||||
return object instanceof MultipartFile
|
||||
|| object instanceof HttpServletRequest
|
||||
|| object instanceof HttpServletResponse
|
||||
|| object instanceof BindingResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core;
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 操作日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class OperateLog {
|
||||
|
||||
/**
|
||||
* 链路追踪编号
|
||||
*/
|
||||
private String traceId;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
|
||||
/**
|
||||
* 操作模块
|
||||
*/
|
||||
private String module;
|
||||
|
||||
/**
|
||||
* 操作名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 操作分类
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 操作明细
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 拓展字段
|
||||
*/
|
||||
private Map<String, Object> exts;
|
||||
|
||||
/**
|
||||
* 请求方法名
|
||||
*/
|
||||
private String requestMethod;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
private String requestUrl;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
|
||||
/**
|
||||
* 浏览器 UserAgent
|
||||
*/
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* Java 方法名
|
||||
*/
|
||||
private String javaMethod;
|
||||
|
||||
/**
|
||||
* Java 方法的参数
|
||||
*/
|
||||
private String javaMethodArgs;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 执行时长,单位:毫秒
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 结果码
|
||||
*/
|
||||
private Integer resultCode;
|
||||
|
||||
/**
|
||||
* 结果提示
|
||||
*/
|
||||
private String resultMsg;
|
||||
|
||||
/**
|
||||
* 结果数据
|
||||
*/
|
||||
private String resultData;
|
||||
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.service;
|
||||
|
||||
/**
|
||||
* 操作日志 Framework Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface OperateLogFrameworkService {
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
*
|
||||
* @param operateLog 操作日志请求
|
||||
*/
|
||||
void createOperateLog(OperateLog operateLog);
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
/**
|
||||
* 操作日志 Framework Service 实现类
|
||||
*
|
||||
* 基于 {@link OperateLogApi} 实现,记录操作日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class OperateLogFrameworkServiceImpl implements OperateLogFrameworkService {
|
||||
|
||||
private final OperateLogApi operateLogApi;
|
||||
|
||||
@Override
|
||||
@Async
|
||||
public void createOperateLog(OperateLog operateLog) {
|
||||
OperateLogCreateReqDTO reqDTO = BeanUtil.toBean(operateLog, OperateLogCreateReqDTO.class);
|
||||
operateLogApi.createOperateLog(reqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.util;
|
||||
|
||||
import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
|
||||
|
||||
/**
|
||||
* 操作日志工具类
|
||||
* 目前主要的作用,是提供给业务代码,记录操作明细和拓展字段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class OperateLogUtils {
|
||||
|
||||
public static void setContent(String content) {
|
||||
OperateLogAspect.setContent(content);
|
||||
}
|
||||
|
||||
public static void addExt(String key, Object value) {
|
||||
OperateLogAspect.addExt(key, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
/**
|
||||
* 用户操作日志:记录用户的操作,用于对用户的操作的审计与追溯,永久保存。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.operatelog;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogRpcAutoConfiguration
|
||||
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration
|
||||
|
|
@ -30,16 +30,6 @@ public class TenantContextHolder {
|
|||
return TENANT_ID.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得租户编号 String
|
||||
*
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static String getTenantIdStr() {
|
||||
Long tenantId = getTenantId();
|
||||
return StrUtil.toStringOrNull(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得租户编号。如果不存在,则抛出 NullPointerException 异常
|
||||
*
|
||||
|
|
|
|||
|
|
@ -71,6 +71,15 @@
|
|||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.mybatis.core.dataobject;
|
|||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fhs.core.trans.vo.TransPojo;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
|
|
@ -12,10 +14,14 @@ import java.time.LocalDateTime;
|
|||
/**
|
||||
* 基础实体对象
|
||||
*
|
||||
* 为什么实现 {@link TransPojo} 接口?
|
||||
* 因为使用 Easy-Trans TransType.SIMPLE 模式,集成 MyBatis Plus 查询
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public abstract class BaseDO implements Serializable {
|
||||
@JsonIgnoreProperties(value = "transMap") // 由于 Easy-Trans 会添加 transMap 属性,避免 Jackson 在 Spring Cache 反序列化报错
|
||||
public abstract class BaseDO implements Serializable, TransPojo {
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
package cn.iocoder.yudao.framework;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package cn.iocoder.yudao.framework.translate.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.translate.core.TranslateUtils;
|
||||
import com.fhs.trans.service.impl.TransService;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
public class YudaoTranslateAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings({"InstantiationOfUtilityClass", "SpringJavaInjectionPointsAutowiringInspection"})
|
||||
public TranslateUtils translateUtils(TransService transService) {
|
||||
TranslateUtils.init(transService);
|
||||
return new TranslateUtils();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package cn.iocoder.yudao.framework.translate.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import com.fhs.trans.service.impl.TransService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* VO 数据翻译 Utils
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TranslateUtils {
|
||||
|
||||
private static TransService transService;
|
||||
|
||||
public static void init(TransService transService) {
|
||||
TranslateUtils.transService = transService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据翻译
|
||||
*
|
||||
* 使用场景:无法使用 @TransMethodResult 注解的场景,只能通过手动触发翻译
|
||||
*
|
||||
* @param data 数据
|
||||
* @return 翻译结果
|
||||
*/
|
||||
public static <T extends VO> List<T> translate(List<T> data) {
|
||||
if (CollUtil.isNotEmpty((data))) {
|
||||
transService.transBatch(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 使用 Easy-Trans 提升使用 VO 数据翻译的开发效率
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.translate;
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration
|
||||
cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration
|
||||
cn.iocoder.yudao.framework.translate.config.YudaoTranslateAutoConfiguration
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.rpc.core.util;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import feign.RequestTemplate;
|
||||
import feign.template.HeaderTemplate;
|
||||
import feign.template.Literal;
|
||||
import feign.template.Template;
|
||||
import feign.template.TemplateChunk;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link feign.Feign} 工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class FeignUtils {
|
||||
|
||||
/**
|
||||
* 添加 JSON 格式的 Header
|
||||
*
|
||||
* @param requestTemplate 请求
|
||||
* @param name header 名
|
||||
* @param value header 值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void createJsonHeader(RequestTemplate requestTemplate, String name, Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
// 添加 header
|
||||
String valueStr = JsonUtils.toJsonString(value);
|
||||
requestTemplate.header(name, valueStr);
|
||||
// fix:由于 OpenFeign 针对 { 会进行分词,所以需要反射修改
|
||||
// 具体分析,可见 https://zhuanlan.zhihu.com/p/360501330 文档
|
||||
Map<String, HeaderTemplate> headers = (Map<String, HeaderTemplate>)
|
||||
ReflectUtil.getFieldValue(requestTemplate, "headers");
|
||||
HeaderTemplate template = headers.get(name);
|
||||
List<Template> templateValues = (List<Template>)
|
||||
ReflectUtil.getFieldValue(template, "values");
|
||||
List<TemplateChunk> templateChunks = (List<TemplateChunk>)
|
||||
ReflectUtil.getFieldValue(templateValues.get(0), "templateChunks");
|
||||
templateChunks.set(0, Literal.create(valueStr));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
|||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
|
||||
import com.mzt.logapi.beans.LogRecord;
|
||||
import com.mzt.logapi.service.ILogRecordService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -30,7 +30,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
|
|||
@Override
|
||||
public void record(LogRecord logRecord) {
|
||||
// 1. 补全通用字段
|
||||
OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
|
||||
OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
// 补充用户信息
|
||||
fillUserFields(reqDTO);
|
||||
|
|
@ -40,12 +40,10 @@ public class LogRecordServiceImpl implements ILogRecordService {
|
|||
fillRequestFields(reqDTO);
|
||||
|
||||
// 2. 异步记录日志
|
||||
operateLogApi.createOperateLogV2(reqDTO);
|
||||
// TODO 测试结束删除或搞个开关
|
||||
log.info("操作日志 ===> {}", reqDTO);
|
||||
operateLogApi.createOperateLog(reqDTO);
|
||||
}
|
||||
|
||||
private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
|
||||
private static void fillUserFields(OperateLogCreateReqDTO reqDTO) {
|
||||
// 使用 SecurityFrameworkUtils。因为要考虑,rpc、mq、job,它其实不是 web;
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
|
|
@ -55,7 +53,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
|
|||
reqDTO.setUserType(loginUser.getUserType());
|
||||
}
|
||||
|
||||
public static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, LogRecord logRecord) {
|
||||
public static void fillModuleFields(OperateLogCreateReqDTO reqDTO, LogRecord logRecord) {
|
||||
reqDTO.setType(logRecord.getType()); // 大模块类型,例如:CRM 客户
|
||||
reqDTO.setSubType(logRecord.getSubType());// 操作名称,例如:转移客户
|
||||
reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号,例如:客户编号
|
||||
|
|
@ -63,7 +61,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
|
|||
reqDTO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
|
||||
}
|
||||
|
||||
private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
|
||||
private static void fillRequestFields(OperateLogCreateReqDTO reqDTO) {
|
||||
// 获得 Request 对象
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
if (request == null) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import java.util.Map;
|
|||
@Data
|
||||
public class LoginUser {
|
||||
|
||||
public static final String INFO_KEY_NICKNAME = "nickname";
|
||||
public static final String INFO_KEY_DEPT_ID = "deptId";
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
|
|
@ -27,6 +30,10 @@ public class LoginUser {
|
|||
* 关联 {@link UserTypeEnum}
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 额外的用户信息
|
||||
*/
|
||||
private Map<String, String> info;
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
|||
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
|
||||
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
|
|
@ -22,6 +24,8 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Token 过滤器,验证 token 的有效性
|
||||
|
|
@ -30,6 +34,7 @@ import java.io.IOException;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
|
|
@ -91,6 +96,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|||
}
|
||||
// 构建登录用户
|
||||
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
|
||||
.setInfo(accessToken.getUserInfo()) // 额外的用户信息
|
||||
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
|
||||
} catch (ServiceException serviceException) {
|
||||
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
|
||||
|
|
@ -122,9 +128,19 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|||
.setTenantId(WebFrameworkUtils.getTenantId(request));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private LoginUser buildLoginUserByHeader(HttpServletRequest request) {
|
||||
String loginUserStr = request.getHeader(SecurityFrameworkUtils.LOGIN_USER_HEADER);
|
||||
return StrUtil.isNotEmpty(loginUserStr) ? JsonUtils.parseObject(loginUserStr, LoginUser.class) : null;
|
||||
if (StrUtil.isEmpty(loginUserStr)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8.name()); // 解码,解决中文乱码问题
|
||||
return JsonUtils.parseObject(loginUserStr, LoginUser.class);
|
||||
} catch (Exception ex) {
|
||||
log.error("[buildLoginUserByHeader][解析 LoginUser({}) 发生异常]", loginUserStr, ex); ;
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,38 @@
|
|||
package cn.iocoder.yudao.framework.security.core.rpc;
|
||||
|
||||
import cn.iocoder.yudao.framework.rpc.core.util.FeignUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* LoginUser 的 RequestInterceptor 实现类:Feign 请求时,将 {@link LoginUser} 设置到 header 中,继续透传给被调用的服务
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class LoginUserRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
LoginUser user = SecurityFrameworkUtils.getLoginUser();
|
||||
if (user != null) {
|
||||
FeignUtils.createJsonHeader(requestTemplate, SecurityFrameworkUtils.LOGIN_USER_HEADER, user);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String userStr = JsonUtils.toJsonString(user);
|
||||
userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8.name()); // 编码,避免中文乱码
|
||||
requestTemplate.header(SecurityFrameworkUtils.LOGIN_USER_HEADER, userStr);
|
||||
} catch (Exception ex) {
|
||||
log.error("[apply][序列化 LoginUser({}) 发生异常]", user, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.framework.security.core.util;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
|
|
@ -91,6 +92,28 @@ public class SecurityFrameworkUtils {
|
|||
return loginUser != null ? loginUser.getId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的昵称,从上下文中
|
||||
*
|
||||
* @return 昵称
|
||||
*/
|
||||
@Nullable
|
||||
public static String getLoginUserNickname() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? MapUtil.getStr(loginUser.getInfo(), LoginUser.INFO_KEY_NICKNAME) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的部门编号,从上下文中
|
||||
*
|
||||
* @return 部门编号
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getLoginUserDeptId() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前用户
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.apilog.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.filter.ApiAccessLogFilter;
|
||||
import cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
|
||||
|
|
@ -15,18 +16,22 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
@AutoConfiguration(after = YudaoWebAutoConfiguration.class)
|
||||
public class YudaoApiLogAutoConfiguration {
|
||||
public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {
|
||||
return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {
|
||||
return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);
|
||||
}
|
||||
|
|
@ -49,4 +54,9 @@ public class YudaoApiLogAutoConfiguration {
|
|||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new ApiAccessLogInterceptor());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.annotations;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 访问日志注解
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ApiAccessLog {
|
||||
|
||||
// ========== 开关字段 ==========
|
||||
|
||||
/**
|
||||
* 是否记录访问日志
|
||||
*/
|
||||
boolean enable() default true;
|
||||
/**
|
||||
* 是否记录请求参数
|
||||
*
|
||||
* 默认记录,主要考虑请求数据一般不大。可手动设置为 false 进行关闭
|
||||
*/
|
||||
boolean requestEnable() default true;
|
||||
/**
|
||||
* 是否记录响应结果
|
||||
*
|
||||
* 默认不记录,主要考虑响应数据可能比较大。可手动设置为 true 进行打开
|
||||
*/
|
||||
boolean responseEnable() default false;
|
||||
/**
|
||||
* 敏感参数数组
|
||||
*
|
||||
* 添加后,请求参数、响应结果不会记录该参数
|
||||
*/
|
||||
String[] sanitizeKeys() default {};
|
||||
|
||||
// ========== 模块字段 ==========
|
||||
|
||||
/**
|
||||
* 操作模块
|
||||
*
|
||||
* 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.tags.Tag#name()} 属性
|
||||
*/
|
||||
String operateModule() default "";
|
||||
/**
|
||||
* 操作名
|
||||
*
|
||||
* 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.Operation#summary()} 属性
|
||||
*/
|
||||
String operateName() default "";
|
||||
/**
|
||||
* 操作分类
|
||||
*
|
||||
* 实际并不是数组,因为枚举不能设置 null 作为默认值
|
||||
*/
|
||||
OperateTypeEnum[] operateType() default {};
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.operatelog.core.enums;
|
||||
package cn.iocoder.yudao.framework.apilog.core.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
|
|
@ -15,9 +14,6 @@ public enum OperateTypeEnum {
|
|||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* 绝大多数情况下,不会记录查询动作,因为过于大量显得没有意义。
|
||||
* 在有需要的时候,通过声明 {@link OperateLog} 注解来记录
|
||||
*/
|
||||
GET(1),
|
||||
/**
|
||||
|
|
@ -1,18 +1,30 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.filter;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLog;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
|
|
@ -21,18 +33,24 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* API 访问日志 Filter
|
||||
*
|
||||
* 目的:记录 API 访问日志到数据库中
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiAccessLogFilter extends ApiRequestFilter {
|
||||
|
||||
private static final String[] SANITIZE_KEYS = new String[]{"password", "token", "accessToken", "refreshToken"};
|
||||
|
||||
private final String applicationName;
|
||||
|
||||
private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
|
||||
|
|
@ -44,6 +62,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
// 获得开始时间
|
||||
|
|
@ -66,45 +85,167 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
|
|||
|
||||
private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime,
|
||||
Map<String, String> queryString, String requestBody, Exception ex) {
|
||||
ApiAccessLog accessLog = new ApiAccessLog();
|
||||
ApiAccessLogCreateReqDTO accessLog = new ApiAccessLogCreateReqDTO();
|
||||
try {
|
||||
this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex);
|
||||
boolean enable = buildApiAccessLog(accessLog, request, beginTime, queryString, requestBody, ex);
|
||||
if (!enable) {
|
||||
return;
|
||||
}
|
||||
apiAccessLogFrameworkService.createApiAccessLog(accessLog);
|
||||
} catch (Throwable th) {
|
||||
log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, LocalDateTime beginTime,
|
||||
private boolean buildApiAccessLog(ApiAccessLogCreateReqDTO accessLog, HttpServletRequest request, LocalDateTime beginTime,
|
||||
Map<String, String> queryString, String requestBody, Exception ex) {
|
||||
// 判断:是否要记录操作日志
|
||||
HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(ATTRIBUTE_HANDLER_METHOD);
|
||||
ApiAccessLog accessLogAnnotation = null;
|
||||
if (handlerMethod != null) {
|
||||
accessLogAnnotation = handlerMethod.getMethodAnnotation(ApiAccessLog.class);
|
||||
if (accessLogAnnotation != null && BooleanUtil.isFalse(accessLogAnnotation.enable())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理用户信息
|
||||
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
|
||||
accessLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
|
||||
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request))
|
||||
.setUserType(WebFrameworkUtils.getLoginUserType(request));
|
||||
// 设置访问结果
|
||||
CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);
|
||||
if (result != null) {
|
||||
accessLog.setResultCode(result.getCode());
|
||||
accessLog.setResultMsg(result.getMsg());
|
||||
accessLog.setResultCode(result.getCode()).setResultMsg(result.getMsg());
|
||||
} else if (ex != null) {
|
||||
accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode());
|
||||
accessLog.setResultMsg(ExceptionUtil.getRootCauseMessage(ex));
|
||||
accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode())
|
||||
.setResultMsg(ExceptionUtil.getRootCauseMessage(ex));
|
||||
} else {
|
||||
accessLog.setResultCode(0);
|
||||
accessLog.setResultMsg("");
|
||||
accessLog.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode()).setResultMsg("");
|
||||
}
|
||||
// 设置请求字段
|
||||
accessLog.setTraceId(TracerUtils.getTraceId()).setApplicationName(applicationName)
|
||||
.setRequestUrl(request.getRequestURI()).setRequestMethod(request.getMethod())
|
||||
.setUserAgent(ServletUtils.getUserAgent(request)).setUserIp(ServletUtils.getClientIP(request));
|
||||
String[] sanitizeKeys = accessLogAnnotation != null ? accessLogAnnotation.sanitizeKeys() : null;
|
||||
Boolean requestEnable = accessLogAnnotation != null ? accessLogAnnotation.requestEnable() : Boolean.TRUE;
|
||||
if (!BooleanUtil.isFalse(requestEnable)) { // 默认记录,所以判断 !false
|
||||
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
|
||||
.put("query", sanitizeMap(queryString, sanitizeKeys))
|
||||
.put("body", sanitizeJson(requestBody, sanitizeKeys)).build();
|
||||
accessLog.setRequestParams(toJsonString(requestParams));
|
||||
}
|
||||
Boolean responseEnable = accessLogAnnotation != null ? accessLogAnnotation.responseEnable() : Boolean.FALSE;
|
||||
if (BooleanUtil.isTrue(responseEnable)) { // 默认不记录,默认强制要求 true
|
||||
accessLog.setResponseBody(sanitizeJson(result, sanitizeKeys));
|
||||
}
|
||||
// 设置其它字段
|
||||
accessLog.setTraceId(TracerUtils.getTraceId());
|
||||
accessLog.setApplicationName(applicationName);
|
||||
accessLog.setRequestUrl(request.getRequestURI());
|
||||
Map<String, Object> requestParams = MapUtil.<String, Object>builder().put("query", queryString).put("body", requestBody).build();
|
||||
accessLog.setRequestParams(toJsonString(requestParams));
|
||||
accessLog.setRequestMethod(request.getMethod());
|
||||
accessLog.setUserAgent(ServletUtils.getUserAgent(request));
|
||||
accessLog.setUserIp(ServletUtils.getClientIP(request));
|
||||
// 持续时间
|
||||
accessLog.setBeginTime(beginTime);
|
||||
accessLog.setEndTime(LocalDateTime.now());
|
||||
accessLog.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS));
|
||||
accessLog.setBeginTime(beginTime).setEndTime(LocalDateTime.now())
|
||||
.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS));
|
||||
|
||||
// 操作模块
|
||||
if (handlerMethod != null) {
|
||||
Tag tagAnnotation = handlerMethod.getBeanType().getAnnotation(Tag.class);
|
||||
Operation operationAnnotation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
String operateModule = accessLogAnnotation != null ? accessLogAnnotation.operateModule() :
|
||||
tagAnnotation != null ? StrUtil.nullToDefault(tagAnnotation.name(), tagAnnotation.description()) : null;
|
||||
String operateName = accessLogAnnotation != null ? accessLogAnnotation.operateName() :
|
||||
operationAnnotation != null ? operationAnnotation.summary() : null;
|
||||
OperateTypeEnum operateType = accessLogAnnotation != null && accessLogAnnotation.operateType().length > 0 ?
|
||||
accessLogAnnotation.operateType()[0] : parseOperateLogType(request);
|
||||
accessLog.setOperateModule(operateModule).setOperateName(operateName).setOperateType(operateType.getType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========== 解析 @ApiAccessLog、@Swagger 注解 ==========
|
||||
|
||||
private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) {
|
||||
RequestMethod requestMethod = ArrayUtil.firstMatch(method ->
|
||||
StrUtil.equalsAnyIgnoreCase(method.name(), request.getMethod()), RequestMethod.values());
|
||||
if (requestMethod == null) {
|
||||
return OperateTypeEnum.OTHER;
|
||||
}
|
||||
switch (requestMethod) {
|
||||
case GET:
|
||||
return OperateTypeEnum.GET;
|
||||
case POST:
|
||||
return OperateTypeEnum.CREATE;
|
||||
case PUT:
|
||||
return OperateTypeEnum.UPDATE;
|
||||
case DELETE:
|
||||
return OperateTypeEnum.DELETE;
|
||||
default:
|
||||
return OperateTypeEnum.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 请求和响应的脱敏逻辑,移除类似 password、token 等敏感字段 ==========
|
||||
|
||||
private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) {
|
||||
if (CollUtil.isNotEmpty(map)) {
|
||||
return null;
|
||||
}
|
||||
if (sanitizeKeys != null) {
|
||||
MapUtil.removeAny(map, sanitizeKeys);
|
||||
}
|
||||
MapUtil.removeAny(map, SANITIZE_KEYS);
|
||||
return JsonUtils.toJsonString(map);
|
||||
}
|
||||
|
||||
private static String sanitizeJson(String jsonString, String[] sanitizeKeys) {
|
||||
if (StrUtil.isEmpty(jsonString)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode rootNode = JsonUtils.parseTree(jsonString);
|
||||
sanitizeJson(rootNode, sanitizeKeys);
|
||||
return JsonUtils.toJsonString(rootNode);
|
||||
} catch (Exception e) {
|
||||
// 脱敏失败的情况下,直接忽略异常,避免影响用户请求
|
||||
log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
|
||||
private static String sanitizeJson(CommonResult<?> commonResult, String[] sanitizeKeys) {
|
||||
if (commonResult == null) {
|
||||
return null;
|
||||
}
|
||||
String jsonString = toJsonString(commonResult);
|
||||
try {
|
||||
JsonNode rootNode = JsonUtils.parseTree(jsonString);
|
||||
sanitizeJson(rootNode.get("data"), sanitizeKeys); // 只处理 data 字段,不处理 code、msg 字段,避免错误被脱敏掉
|
||||
return JsonUtils.toJsonString(rootNode);
|
||||
} catch (Exception e) {
|
||||
// 脱敏失败的情况下,直接忽略异常,避免影响用户请求
|
||||
log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e);
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
|
||||
private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) {
|
||||
// 情况一:数组,遍历处理
|
||||
if (node.isArray()) {
|
||||
for (JsonNode childNode : node) {
|
||||
sanitizeJson(childNode, sanitizeKeys);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 情况二:非 Object,只是某个值,直接返回
|
||||
if (!node.isObject()) {
|
||||
return;
|
||||
}
|
||||
// 情况三:Object,遍历处理
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
if (ArrayUtil.contains(sanitizeKeys, entry.getKey())
|
||||
|| ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
sanitizeJson(entry.getValue(), sanitizeKeys);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.interceptor;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* API 访问日志 Interceptor
|
||||
*
|
||||
* 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiAccessLogInterceptor implements HandlerInterceptor {
|
||||
|
||||
public static final String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD";
|
||||
|
||||
private static final String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch";
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 记录 HandlerMethod,提供给 ApiAccessLogFilter 使用
|
||||
HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null;
|
||||
if (handlerMethod != null) {
|
||||
request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);
|
||||
}
|
||||
|
||||
// 打印 request 日志
|
||||
if (!SpringUtils.isProd()) {
|
||||
Map<String, String> queryString = ServletUtils.getParamMap(request);
|
||||
String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
|
||||
if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {
|
||||
log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
|
||||
} else {
|
||||
log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
|
||||
StrUtil.nullToDefault(requestBody, queryString.toString()));
|
||||
}
|
||||
// 计时
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 打印 response 日志
|
||||
if (!SpringUtils.isProd()) {
|
||||
StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
|
||||
stopWatch.stop();
|
||||
log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]",
|
||||
request.getRequestURI(), stopWatch.getTotalTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 访问日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class ApiAccessLog {
|
||||
|
||||
/**
|
||||
* 链路追踪编号
|
||||
*/
|
||||
private String traceId;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
@NotNull(message = "应用名不能为空")
|
||||
private String applicationName;
|
||||
|
||||
/**
|
||||
* 请求方法名
|
||||
*/
|
||||
@NotNull(message = "http 请求方法不能为空")
|
||||
private String requestMethod;
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
@NotNull(message = "访问地址不能为空")
|
||||
private String requestUrl;
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
@NotNull(message = "请求参数不能为空")
|
||||
private String requestParams;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotNull(message = "ip 不能为空")
|
||||
private String userIp;
|
||||
/**
|
||||
* 浏览器 UA
|
||||
*/
|
||||
@NotNull(message = "User-Agent 不能为空")
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 开始请求时间
|
||||
*/
|
||||
@NotNull(message = "开始请求时间不能为空")
|
||||
private LocalDateTime beginTime;
|
||||
/**
|
||||
* 结束请求时间
|
||||
*/
|
||||
@NotNull(message = "结束请求时间不能为空")
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 执行时长,单位:毫秒
|
||||
*/
|
||||
@NotNull(message = "执行时长不能为空")
|
||||
private Integer duration;
|
||||
/**
|
||||
* 结果码
|
||||
*/
|
||||
@NotNull(message = "错误码不能为空")
|
||||
private Integer resultCode;
|
||||
/**
|
||||
* 结果提示
|
||||
*/
|
||||
private String resultMsg;
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
|
||||
/**
|
||||
* API 访问日志 Framework Service 接口
|
||||
*
|
||||
|
|
@ -10,8 +12,8 @@ public interface ApiAccessLogFrameworkService {
|
|||
/**
|
||||
* 创建 API 访问日志
|
||||
*
|
||||
* @param apiAccessLog API 访问日志
|
||||
* @param reqDTO API 访问日志
|
||||
*/
|
||||
void createApiAccessLog(ApiAccessLog apiAccessLog);
|
||||
void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -10,7 +8,7 @@ import org.springframework.scheduling.annotation.Async;
|
|||
/**
|
||||
* API 访问日志 Framework Service 实现类
|
||||
*
|
||||
* 基于 {@link ApiAccessLogApi} 远程服务,记录访问日志
|
||||
* 基于 {@link ApiAccessLogApi} 服务,记录访问日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
|
@ -21,9 +19,8 @@ public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkSe
|
|||
|
||||
@Override
|
||||
@Async
|
||||
public void createApiAccessLog(ApiAccessLog apiAccessLog) {
|
||||
ApiAccessLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiAccessLog, ApiAccessLogCreateReqDTO.class);
|
||||
apiAccessLogApi.createApiAccessLog(reqDTO).checkError();
|
||||
public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
|
||||
apiAccessLogApi.createApiAccessLog(reqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 错误日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class ApiErrorLog {
|
||||
|
||||
/**
|
||||
* 链路编号
|
||||
*/
|
||||
private String traceId;
|
||||
/**
|
||||
* 账号编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
@NotNull(message = "应用名不能为空")
|
||||
private String applicationName;
|
||||
|
||||
/**
|
||||
* 请求方法名
|
||||
*/
|
||||
@NotNull(message = "http 请求方法不能为空")
|
||||
private String requestMethod;
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
@NotNull(message = "访问地址不能为空")
|
||||
private String requestUrl;
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
@NotNull(message = "请求参数不能为空")
|
||||
private String requestParams;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotNull(message = "ip 不能为空")
|
||||
private String userIp;
|
||||
/**
|
||||
* 浏览器 UA
|
||||
*/
|
||||
@NotNull(message = "User-Agent 不能为空")
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 异常时间
|
||||
*/
|
||||
@NotNull(message = "异常时间不能为空")
|
||||
private LocalDateTime exceptionTime;
|
||||
/**
|
||||
* 异常名
|
||||
*/
|
||||
@NotNull(message = "异常名不能为空")
|
||||
private String exceptionName;
|
||||
/**
|
||||
* 异常发生的类全名
|
||||
*/
|
||||
@NotNull(message = "异常发生的类全名不能为空")
|
||||
private String exceptionClassName;
|
||||
/**
|
||||
* 异常发生的类文件
|
||||
*/
|
||||
@NotNull(message = "异常发生的类文件不能为空")
|
||||
private String exceptionFileName;
|
||||
/**
|
||||
* 异常发生的方法名
|
||||
*/
|
||||
@NotNull(message = "异常发生的方法名不能为空")
|
||||
private String exceptionMethodName;
|
||||
/**
|
||||
* 异常发生的方法所在行
|
||||
*/
|
||||
@NotNull(message = "异常发生的方法所在行不能为空")
|
||||
private Integer exceptionLineNumber;
|
||||
/**
|
||||
* 异常的栈轨迹异常的栈轨迹
|
||||
*/
|
||||
@NotNull(message = "异常的栈轨迹不能为空")
|
||||
private String exceptionStackTrace;
|
||||
/**
|
||||
* 异常导致的根消息
|
||||
*/
|
||||
@NotNull(message = "异常导致的根消息不能为空")
|
||||
private String exceptionRootCauseMessage;
|
||||
/**
|
||||
* 异常导致的消息
|
||||
*/
|
||||
@NotNull(message = "异常导致的消息不能为空")
|
||||
private String exceptionMessage;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
|
||||
/**
|
||||
* API 错误日志 Framework Service 接口
|
||||
*
|
||||
|
|
@ -10,8 +12,8 @@ public interface ApiErrorLogFrameworkService {
|
|||
/**
|
||||
* 创建 API 错误日志
|
||||
*
|
||||
* @param apiErrorLog API 错误日志
|
||||
* @param reqDTO API 错误日志
|
||||
*/
|
||||
void createApiErrorLog(ApiErrorLog apiErrorLog);
|
||||
void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -10,7 +8,7 @@ import org.springframework.scheduling.annotation.Async;
|
|||
/**
|
||||
* API 错误日志 Framework Service 实现类
|
||||
*
|
||||
* 基于 {@link ApiErrorLogApi} 远程服务,记录错误日志
|
||||
* 基于 {@link ApiErrorLogApi} 服务,记录错误日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
|
@ -21,9 +19,8 @@ public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkServ
|
|||
|
||||
@Override
|
||||
@Async
|
||||
public void createApiErrorLog(ApiErrorLog apiErrorLog) {
|
||||
ApiErrorLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiErrorLog, ApiErrorLogCreateReqDTO.class);
|
||||
apiErrorLogApi.createApiErrorLog(reqDTO).checkError();
|
||||
public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
|
||||
apiErrorLogApi.createApiErrorLog(reqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
//package cn.iocoder.yudao.framework.swagger.core;
|
||||
//
|
||||
//import cn.hutool.core.util.ReflectUtil;
|
||||
//import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
//import org.springframework.beans.BeansException;
|
||||
//import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
//import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
//import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
|
||||
//
|
||||
//import java.util.List;
|
||||
//
|
||||
///**
|
||||
// * 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题
|
||||
// * 该问题对应的 issue 为 https://github.com/springfox/springfox/issues/3462
|
||||
// *
|
||||
// * @author 芋道源码
|
||||
// */
|
||||
//public class SpringFoxHandlerProviderBeanPostProcessor implements BeanPostProcessor {
|
||||
//
|
||||
// @Override
|
||||
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
// if (bean instanceof WebMvcRequestHandlerProvider) {
|
||||
// customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
||||
// }
|
||||
// return bean;
|
||||
// }
|
||||
//
|
||||
// private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
|
||||
// // 移除,只保留 patternParser
|
||||
// List<T> copy = CollectionUtils.filterList(mappings, mapping -> mapping.getPatternParser() == null);
|
||||
// // 添加到 mappings 中
|
||||
// mappings.clear();
|
||||
// mappings.addAll(copy);
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("unchecked")
|
||||
// private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
|
||||
// return (List<RequestMappingInfoHandlerMapping>)
|
||||
// ReflectUtil.getFieldValue(bean, "handlerMappings");
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
@ -3,8 +3,7 @@ package cn.iocoder.yudao.framework.web.core.handler;
|
|||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLog;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
|
||||
|
|
@ -12,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
|||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
|
@ -46,6 +46,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
|
|||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
private final String applicationName;
|
||||
|
||||
private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;
|
||||
|
|
@ -230,17 +231,17 @@ public class GlobalExceptionHandler {
|
|||
// 情况三:处理异常
|
||||
log.error("[defaultExceptionHandler]", ex);
|
||||
// 插入异常日志
|
||||
this.createExceptionLog(req, ex);
|
||||
createExceptionLog(req, ex);
|
||||
// 返回 ERROR CommonResult
|
||||
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
|
||||
}
|
||||
|
||||
private void createExceptionLog(HttpServletRequest req, Throwable e) {
|
||||
// 插入错误日志
|
||||
ApiErrorLog errorLog = new ApiErrorLog();
|
||||
ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
|
||||
try {
|
||||
// 初始化 errorLog
|
||||
initExceptionLog(errorLog, req, e);
|
||||
buildExceptionLog(errorLog, req, e);
|
||||
// 执行插入 errorLog
|
||||
apiErrorLogFrameworkService.createApiErrorLog(errorLog);
|
||||
} catch (Throwable th) {
|
||||
|
|
@ -248,7 +249,7 @@ public class GlobalExceptionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void initExceptionLog(ApiErrorLog errorLog, HttpServletRequest request, Throwable e) {
|
||||
private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
|
||||
// 处理用户信息
|
||||
errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
|
||||
errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
|
||||
|
|
@ -269,12 +270,12 @@ public class GlobalExceptionHandler {
|
|||
errorLog.setApplicationName(applicationName);
|
||||
errorLog.setRequestUrl(request.getRequestURI());
|
||||
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
|
||||
.put("query", ServletUtil.getParamMap(request))
|
||||
.put("body", ServletUtil.getBody(request)).build();
|
||||
.put("query", ServletUtils.getParamMap(request))
|
||||
.put("body", ServletUtils.getBody(request)).build();
|
||||
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
|
||||
errorLog.setRequestMethod(request.getMethod());
|
||||
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
|
||||
errorLog.setUserIp(ServletUtil.getClientIP(request));
|
||||
errorLog.setUserIp(ServletUtils.getClientIP(request));
|
||||
errorLog.setExceptionTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
|
|||
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
|
||||
@ConditionalOnBean(ObjectMapper.class)
|
||||
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
|
||||
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
|
||||
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
|
||||
PathMatcher pathMatcher,
|
||||
XssCleaner xssCleaner) {
|
||||
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
|
||||
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
|
||||
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.xss.core.json;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.xss.config.XssProperties;
|
||||
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
|
@ -7,7 +9,9 @@ import com.fasterxml.jackson.databind.DeserializationContext;
|
|||
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
|
@ -20,10 +24,29 @@ import java.io.IOException;
|
|||
@AllArgsConstructor
|
||||
public class XssStringJsonDeserializer extends StringDeserializer {
|
||||
|
||||
/**
|
||||
* 属性
|
||||
*/
|
||||
private final XssProperties properties;
|
||||
/**
|
||||
* 路径匹配器
|
||||
*/
|
||||
private final PathMatcher pathMatcher;
|
||||
|
||||
private final XssCleaner xssCleaner;
|
||||
|
||||
@Override
|
||||
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
// 1. 白名单 URL 的处理
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
if (request != null) {
|
||||
String uri = ServletUtils.getRequest().getRequestURI();
|
||||
if (properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri))) {
|
||||
return p.getText();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 真正使用 xssCleaner 进行过滤
|
||||
if (p.hasToken(JsonToken.VALUE_STRING)) {
|
||||
return xssCleaner.clean(p.getText());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.gateway.filter.security;
|
|||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
|
|
@ -22,6 +23,10 @@ public class LoginUser {
|
|||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 额外的用户信息
|
||||
*/
|
||||
private Map<String, String> info;
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.gateway.filter.security;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.gateway.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.gateway.util.WebFrameworkUtils;
|
||||
|
|
@ -27,7 +26,6 @@ import java.util.Objects;
|
|||
import java.util.function.Function;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildCache;
|
||||
|
||||
/**
|
||||
* Token 过滤器,验证 token 的有效性
|
||||
|
|
@ -154,6 +152,7 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
|||
// 创建登录用户
|
||||
OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData();
|
||||
return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType())
|
||||
.setInfo(tokenInfo.getUserInfo()) // 额外的用户信息
|
||||
.setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@ package cn.iocoder.yudao.gateway.util;
|
|||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.gateway.filter.security.LoginUser;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 安全服务工具类
|
||||
*
|
||||
|
|
@ -14,6 +20,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class SecurityFrameworkUtils {
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
|
@ -99,8 +106,16 @@ public class SecurityFrameworkUtils {
|
|||
* @param builder 请求
|
||||
* @param user 用户
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static void setLoginUserHeader(ServerHttpRequest.Builder builder, LoginUser user) {
|
||||
builder.header(LOGIN_USER_HEADER, JsonUtils.toJsonString(user));
|
||||
try {
|
||||
String userStr = JsonUtils.toJsonString(user);
|
||||
userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8.name()); // 编码,避免中文乱码
|
||||
builder.header(LOGIN_USER_HEADER, userStr);
|
||||
} catch (Exception ex) {
|
||||
log.error("[setLoginUserHeader][序列化 user({}) 发生异常]", user, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
<modules>
|
||||
<module>yudao-module-bpm-api</module>
|
||||
<module>yudao-module-bpm-biz</module>
|
||||
<module>yudao-spring-boot-starter-flowable</module>
|
||||
</modules>
|
||||
<artifactId>yudao-module-bpm</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "RPC 服务 - 流程实例的创建 Request DTO")
|
||||
|
|
@ -21,4 +22,14 @@ public class BpmProcessInstanceCreateReqDTO {
|
|||
@NotEmpty(message = "业务的唯一标识不能为空")
|
||||
private String businessKey; // 例如说,请假申请的编号。通过它,可以查询到对应的实例
|
||||
|
||||
/**
|
||||
* 发起人自选审批人 Map
|
||||
*
|
||||
* key:taskKey 任务编码
|
||||
* value:审批人的数组
|
||||
* 例如:{ taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
|
||||
*/
|
||||
@Schema(description = "发起人自选审批人 Map")
|
||||
private Map<String, List<Long>> startUserSelectAssignees;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,37 +10,32 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
|||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 通用流程处理 模块 1-009-000-000 ==========
|
||||
ErrorCode HIGHLIGHT_IMG_ERROR = new ErrorCode(1_009_000_002, "获取高亮流程图异常");
|
||||
|
||||
// ========== OA 流程模块 1-009-001-000 ==========
|
||||
ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1_009_001_001, "请假申请不存在");
|
||||
ErrorCode OA_PM_POST_NOT_EXISTS = new ErrorCode(1_009_001_002, "项目经理岗位未设置");
|
||||
ErrorCode OA_DEPART_PM_POST_NOT_EXISTS = new ErrorCode(1_009_001_009, "部门的项目经理不存在");
|
||||
ErrorCode OA_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_004, "部门经理岗位未设置");
|
||||
ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_005, "部门的部门经理不存在");
|
||||
ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1_009_001_006, "HR岗位未设置");
|
||||
ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1_009_001_007, "请假天数必须>=1");
|
||||
|
||||
// ========== 流程模型 1-009-002-000 ==========
|
||||
ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1_009_002_000, "已经存在流程标识为【{}】的流程");
|
||||
ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_009_002_001, "流程模型不存在");
|
||||
ErrorCode MODEL_KEY_VALID = new ErrorCode(1_009_002_002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
|
||||
ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1_009_002_003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
|
||||
ErrorCode MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG = new ErrorCode(1_009_002_004, "部署流程失败," +
|
||||
"原因:用户任务({})未配置分配规则,请点击【修改流程】按钮进行配置");
|
||||
ErrorCode MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS = new ErrorCode(1_009_003_005, "流程定义部署失败,原因:信息未发生变化");
|
||||
ErrorCode MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG = new ErrorCode(1_009_002_004, "部署流程失败," +
|
||||
"原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
|
||||
ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
|
||||
ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
|
||||
|
||||
// ========== 流程定义 1-009-003-000 ==========
|
||||
ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
|
||||
ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1_009_003_001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
|
||||
ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_009_003_002, "流程定义不存在");
|
||||
ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1_009_003_003, "流程定义处于挂起状态");
|
||||
ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1_009_003_004, "流程定义的模型不存在");
|
||||
|
||||
// ========== 流程实例 1-009-004-000 ==========
|
||||
ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
|
||||
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
|
||||
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
|
||||
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置");
|
||||
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在");
|
||||
|
||||
// ========== 流程任务 1-009-005-000 ==========
|
||||
ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
|
||||
|
|
@ -50,24 +45,34 @@ public interface ErrorCodeConstants {
|
|||
ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
|
||||
ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
|
||||
ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
|
||||
ErrorCode TASK_ADD_SIGN_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");
|
||||
ErrorCode TASK_ADD_SIGN_TYPE_ERROR = new ErrorCode(1_009_005_010, "任务加签:当前任务已经{},不能{}");
|
||||
ErrorCode TASK_ADD_SIGN_USER_REPEAT = new ErrorCode(1_009_005_011, "任务加签失败,加签人与现有审批人[{}]重复");
|
||||
ErrorCode TASK_SUB_SIGN_NO_PARENT = new ErrorCode(1_009_005_011, "任务减签失败,被减签的任务必须是通过加签生成的任务");
|
||||
|
||||
// ========== 流程任务分配规则 1-009-006-000 ==========
|
||||
ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则");
|
||||
ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在");
|
||||
ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1_009_006_002, "只有流程模型的任务分配规则,才允许被修改");
|
||||
ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");
|
||||
ErrorCode TASK_SIGN_CREATE_TYPE_ERROR = new ErrorCode(1_009_005_010, "任务加签:当前任务已经{},不能{}");
|
||||
ErrorCode TASK_SIGN_CREATE_USER_REPEAT = new ErrorCode(1_009_005_011, "任务加签失败,加签人与现有审批人[{}]重复");
|
||||
ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
|
||||
ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
|
||||
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
|
||||
ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
|
||||
ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1_009_006_004, "操作失败,原因:任务分配脚本({}) 不存在");
|
||||
|
||||
// ========== 动态表单模块 1-009-010-000 ==========
|
||||
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
|
||||
ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1_009_010_001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
|
||||
|
||||
// ========== 用户组模块 1-009-011-000 ==========
|
||||
ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1_009_011_000, "用户组不存在");
|
||||
ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1_009_011_001, "名字为【{}】的用户组已被禁用");
|
||||
ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1_009_011_000, "用户分组不存在");
|
||||
ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1_009_011_001, "名字为【{}】的用户分组已被禁用");
|
||||
|
||||
// ========== 用户组模块 1-009-012-000 ==========
|
||||
ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在");
|
||||
ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复");
|
||||
ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_009_012_002, "流程分类编码【{}】重复");
|
||||
|
||||
// ========== BPM 流程监听器 1-009-013-000 ==========
|
||||
ErrorCode PROCESS_LISTENER_NOT_EXISTS = new ErrorCode(1_009_013_000, "流程监听器不存在");
|
||||
ErrorCode PROCESS_LISTENER_CLASS_NOT_FOUND = new ErrorCode(1_009_013_001, "流程监听器类({})不存在");
|
||||
ErrorCode PROCESS_LISTENER_CLASS_IMPLEMENTS_ERROR = new ErrorCode(1_009_013_002, "流程监听器类({})没有实现接口({})");
|
||||
ErrorCode PROCESS_LISTENER_EXPRESSION_INVALID = new ErrorCode(1_009_013_003, "流程监听器表达式({})不合法");
|
||||
|
||||
// ========== BPM 流程表达式 1-009-014-000 ==========
|
||||
ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在");
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* BPM 模型的表单类型的枚举
|
||||
*
|
||||
|
|
@ -10,12 +13,20 @@ import lombok.Getter;
|
|||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmModelFormTypeEnum {
|
||||
public enum BpmModelFormTypeEnum implements IntArrayValuable {
|
||||
|
||||
NORMAL(10, "流程表单"), // 对应 BpmFormDO
|
||||
CUSTOM(20, "业务表单") // 业务自己定义的表单,自己进行数据的存储
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelFormTypeEnum::getType).toArray();
|
||||
|
||||
private final Integer type;
|
||||
private final String desc;
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.definition;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BPM 流程监听器的类型
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmProcessListenerType {
|
||||
|
||||
EXECUTION("execution", "执行监听器"),
|
||||
TASK("task", "任务执行器");
|
||||
|
||||
private final String type;
|
||||
private final String name;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.definition;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BPM 流程监听器的值类型
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmProcessListenerValueType {
|
||||
|
||||
CLASS("class", "Java 类"),
|
||||
DELEGATE_EXPRESSION("delegateExpression", "代理表达式"),
|
||||
EXPRESSION("expression", "表达式");
|
||||
|
||||
private final String type;
|
||||
private final String name;
|
||||
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.definition;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BPM 任务分配规则的类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmTaskAssignRuleTypeEnum {
|
||||
|
||||
ROLE(10, "角色"),
|
||||
DEPT_MEMBER(20, "部门的成员"), // 包括负责人
|
||||
DEPT_LEADER(21, "部门的负责人"),
|
||||
POST(22, "岗位"),
|
||||
USER(30, "用户"),
|
||||
USER_GROUP(40, "用户组"),
|
||||
SCRIPT(50, "自定义脚本"), // 例如说,发起人所在部门的领导、发起人所在部门的领导的领导
|
||||
;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.definition;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BPM 任务规则的脚本枚举
|
||||
* 目前暂时通过 TODO 芋艿:硬编码,未来可以考虑 Groovy 动态脚本的方式
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmTaskRuleScriptEnum {
|
||||
|
||||
START_USER(10L, "流程发起人"),
|
||||
|
||||
LEADER_X1(20L, "流程发起人的一级领导"),
|
||||
LEADER_X2(21L, "流程发起人的二级领导");
|
||||
|
||||
/**
|
||||
* 脚本编号
|
||||
*/
|
||||
private final Long id;
|
||||
/**
|
||||
* 脚本描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +1,35 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 流程任务 -- comment类型枚举
|
||||
* 流程任务的 Comment 评论类型枚举
|
||||
*
|
||||
* @author kehaiyou
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmCommentTypeEnum {
|
||||
|
||||
APPROVE(1, "通过", ""),
|
||||
REJECT(2, "不通过", ""),
|
||||
CANCEL(3, "已取消", ""),
|
||||
BACK(4, "退回", ""),
|
||||
DELEGATE(5, "委派", ""),
|
||||
ADD_SIGN(6, "加签", "[{}]{}给了[{}],理由为:{}"),
|
||||
SUB_SIGN(7, "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"),
|
||||
APPROVE("1", "审批通过", "审批通过,原因是:{}"),
|
||||
REJECT("2", "不通过", "审批不通过:原因是:{}"),
|
||||
CANCEL("3", "已取消", "系统自动取消,原因是:{}"),
|
||||
RETURN("4", "退回", "任务被退回,原因是:{}"),
|
||||
DELEGATE_START("5", "委派发起", "[{}]将任务委派给[{}],委派理由为:{}"),
|
||||
DELEGATE_END("6", "委派完成", "[{}]完成委派任务,任务重新回到[{}]手中,审批建议为:{}"),
|
||||
TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"),
|
||||
ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"),
|
||||
SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
*
|
||||
* 由于 BPM Comment 类型为 String,所以这里就不使用 Integer
|
||||
*/
|
||||
private final Integer type;
|
||||
private final String type;
|
||||
/**
|
||||
* 操作名字
|
||||
*/
|
||||
|
|
@ -32,4 +39,8 @@ public enum BpmCommentTypeEnum {
|
|||
*/
|
||||
private final String comment;
|
||||
|
||||
public String formatComment(Object... params) {
|
||||
return StrUtil.format(comment, params);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 流程实例/任务的删除原因枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmDeleteReasonEnum {
|
||||
|
||||
// ========== 流程实例的独有原因 ==========
|
||||
|
||||
REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法
|
||||
CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程
|
||||
CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程
|
||||
|
||||
// ========== 流程任务的独有原因 ==========
|
||||
|
||||
CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等
|
||||
;
|
||||
|
||||
private final String reason;
|
||||
|
||||
/**
|
||||
* 格式化理由
|
||||
*
|
||||
* @param args 参数
|
||||
* @return 理由
|
||||
*/
|
||||
public String format(Object... args) {
|
||||
return StrUtil.format(reason, args);
|
||||
}
|
||||
|
||||
// ========== 逻辑 ==========
|
||||
|
||||
public static boolean isRejectReason(String reason) {
|
||||
return StrUtil.startWith(reason, "审批不通过任务,原因:");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 流程实例的删除原因
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmProcessInstanceDeleteReasonEnum {
|
||||
|
||||
REJECT_TASK("不通过任务,原因:{}"), // 修改文案时,需要注意 isRejectReason 方法
|
||||
CANCEL_TASK("主动取消任务,原因:{}"),
|
||||
|
||||
// ========== 流程任务的独有原因 ==========
|
||||
MULTI_TASK_END("系统自动取消,原因:多任务审批已经满足条件,无需审批该任务"), // 多实例满足 condition 而结束时,其它任务实例任务会被取消,对应的删除原因是 MI_END
|
||||
|
||||
;
|
||||
|
||||
private final String reason;
|
||||
|
||||
/**
|
||||
* 格式化理由
|
||||
*
|
||||
* @param args 参数
|
||||
* @return 理由
|
||||
*/
|
||||
public String format(Object... args) {
|
||||
return StrUtil.format(reason, args);
|
||||
}
|
||||
|
||||
// ========== 逻辑 ==========
|
||||
|
||||
public static boolean isRejectReason(String reason) {
|
||||
return StrUtil.startWith(reason, "不通过任务,原因:");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Flowable 的删除原因,翻译成对应的中文原因
|
||||
*
|
||||
* @param reason 原始原因
|
||||
* @return 原因
|
||||
*/
|
||||
public static String translateReason(String reason) {
|
||||
if (StrUtil.isEmpty(reason)) {
|
||||
return reason;
|
||||
}
|
||||
switch (reason) {
|
||||
case "MI_END": return MULTI_TASK_END.getReason();
|
||||
default: return reason;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流程实例的结果
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmProcessInstanceResultEnum {
|
||||
|
||||
PROCESS(1, "处理中"),
|
||||
APPROVE(2, "通过"),
|
||||
REJECT(3, "不通过"),
|
||||
CANCEL(4, "已取消"),
|
||||
|
||||
// ========== 流程任务独有的状态 ==========
|
||||
|
||||
BACK(5, "驳回"), // 退回
|
||||
DELEGATE(6, "委派"),
|
||||
/**
|
||||
* 【加签】源任务已经审批完成,但是它使用了后加签,后加签的任务未完成,源任务就会是这个状态
|
||||
* 相当于是 通过 APPROVE 的特殊状态
|
||||
* 例如:A审批, A 后加签了 B,并且审批通过了任务,但是 B 还未审批,则当前任务状态为“待后加签任务完成”
|
||||
*/
|
||||
SIGN_AFTER(7, "待后加签任务完成"),
|
||||
/**
|
||||
* 【加签】源任务未审批,但是向前加签了,所以源任务状态变为“待前加签任务完成”
|
||||
* 相当于是 处理中 PROCESS 的特殊状态
|
||||
* 例如:A 审批, A 前加签了 B,B 还未审核
|
||||
*/
|
||||
SIGN_BEFORE(8, "待前加签任务完成"),
|
||||
/**
|
||||
* 【加签】后加签任务被创建时的初始状态
|
||||
* 相当于是 处理中 PROCESS 的特殊状态
|
||||
* 因为需要源任务先完成,才能到后加签的人来审批,所以加了一个状态区分
|
||||
*/
|
||||
WAIT_BEFORE_TASK(9, "待前置任务完成");
|
||||
|
||||
/**
|
||||
* 能被减签的状态
|
||||
*/
|
||||
public static final List<Integer> CAN_SUB_SIGN_STATUS_LIST = Arrays.asList(PROCESS.result, WAIT_BEFORE_TASK.result);
|
||||
|
||||
/**
|
||||
* 结果
|
||||
* <p>
|
||||
* 如果新增时,注意 {@link #isEndResult(Integer)} 是否需要变更
|
||||
*/
|
||||
private final Integer result;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
/**
|
||||
* 判断该结果是否已经处于 End 最终结果
|
||||
* <p>
|
||||
* 主要用于一些结果更新的逻辑,如果已经是最终结果,就不再进行更新
|
||||
*
|
||||
* @param result 结果
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isEndResult(Integer result) {
|
||||
return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(),
|
||||
CANCEL.getResult(), BACK.getResult(),
|
||||
SIGN_AFTER.getResult());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +1,26 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 流程实例的状态
|
||||
* 流程实例 ProcessInstance 的状态
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmProcessInstanceStatusEnum {
|
||||
public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
|
||||
|
||||
RUNNING(1, "进行中"),
|
||||
FINISH(2, "已完成");
|
||||
RUNNING(1, "审批中"),
|
||||
APPROVE(2, "审批通过"),
|
||||
REJECT(3, "审批不通过"),
|
||||
CANCEL(4, "已取消");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmProcessInstanceStatusEnum::getStatus).toArray();
|
||||
|
||||
/**
|
||||
* 状态
|
||||
|
|
@ -24,4 +31,9 @@ public enum BpmProcessInstanceStatusEnum {
|
|||
*/
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 流程任务 -- 加签类型枚举类型
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmTaskAddSignTypeEnum {
|
||||
|
||||
/**
|
||||
* 向前加签,需要前置任务审批完成,才回到原审批人
|
||||
*/
|
||||
BEFORE("before", "向前加签"),
|
||||
/**
|
||||
* 向后加签,需要后置任务全部审批完,才会通过原审批人节点
|
||||
*/
|
||||
AFTER("after", "向后加签"),
|
||||
/**
|
||||
* 创建后置加签时的过度状态,用于控制向后加签生成的任务状态
|
||||
*/
|
||||
AFTER_CHILDREN_TASK("afterChildrenTask", "向后加签生成的子任务");
|
||||
|
||||
private final String type;
|
||||
|
||||
private final String desc;
|
||||
|
||||
public static String formatDesc(String type) {
|
||||
for (BpmTaskAddSignTypeEnum value : values()) {
|
||||
if (value.type.equals(type)) {
|
||||
return value.desc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 流程任务的加签类型枚举
|
||||
*
|
||||
* @author kehaiyou
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmTaskSignTypeEnum {
|
||||
|
||||
/**
|
||||
* 向前加签,需要前置任务审批完成,才回到原审批人
|
||||
*/
|
||||
BEFORE("before", "向前加签"),
|
||||
/**
|
||||
* 向后加签,需要后置任务全部审批完,才会通过原审批人节点
|
||||
*/
|
||||
AFTER("after", "向后加签");
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final String type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static String nameOfType(String type) {
|
||||
for (BpmTaskSignTypeEnum value : values()) {
|
||||
if (value.type.equals(type)) {
|
||||
return value.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BpmTaskSignTypeEnum of(String type) {
|
||||
return ArrayUtil.firstMatch(value -> value.getType().equals(type), values());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package cn.iocoder.yudao.module.bpm.enums.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 流程任务 Task 的状态枚举
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum BpmTaskStatusEnum {
|
||||
|
||||
RUNNING(1, "审批中"),
|
||||
APPROVE(2, "审批通过"),
|
||||
REJECT(3, "审批不通过"),
|
||||
CANCEL(4, "已取消"),
|
||||
|
||||
RETURN(5, "已退回"),
|
||||
DELEGATE(6, "委派中"),
|
||||
|
||||
/**
|
||||
* 使用场景:
|
||||
* 1. 任务被向后【加签】时,它在审批通过后,会变成 APPROVING 这个状态,然后等到【加签】出来的任务都被审批后,才会变成 APPROVE 审批通过
|
||||
*/
|
||||
APPROVING(7, "审批通过中"),
|
||||
/**
|
||||
* 使用场景:
|
||||
* 1. 任务被向前【加签】时,它会变成 WAIT 状态,需要等待【加签】出来的任务被审批后,它才能继续变为 RUNNING 继续审批
|
||||
* 2. 任务被向后【加签】时,【加签】出来的任务处于 WAIT 状态,它们需要等待该任务被审批后,它们才能继续变为 RUNNING 继续审批
|
||||
*/
|
||||
WAIT(0, "待审批");
|
||||
|
||||
/**
|
||||
* 状态
|
||||
* <p>
|
||||
* 如果新增时,注意 {@link #isEndStatus(Integer)} 是否需要变更
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 判断该状态是否已经处于 End 最终状态
|
||||
* <p>
|
||||
* 主要用于一些状态更新的逻辑,如果已经是最终状态,就不再进行更新
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isEndStatus(Integer status) {
|
||||
return ObjectUtils.equalsAny(status,
|
||||
APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(),
|
||||
RETURN.getStatus(), APPROVING.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,14 +6,13 @@ import org.springframework.context.ApplicationEvent;
|
|||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 流程实例的结果发生变化的 Event
|
||||
* 定位:由于额外增加了 {@link BpmProcessInstanceExtDO#getResult()} 结果,所以增加该事件
|
||||
* 流程实例的状态(结果)发生变化的 Event
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
@Data
|
||||
public class BpmProcessInstanceResultEvent extends ApplicationEvent {
|
||||
public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
|
||||
|
||||
/**
|
||||
* 流程实例的编号
|
||||
|
|
@ -28,15 +27,15 @@ public class BpmProcessInstanceResultEvent extends ApplicationEvent {
|
|||
/**
|
||||
* 流程实例的结果
|
||||
*/
|
||||
@NotNull(message = "流程实例的结果不能为空")
|
||||
private Integer result;
|
||||
@NotNull(message = "流程实例的状态不能为空")
|
||||
private Integer status;
|
||||
/**
|
||||
* 流程实例对应的业务标识
|
||||
* 例如说,请假
|
||||
*/
|
||||
private String businessKey;
|
||||
|
||||
public BpmProcessInstanceResultEvent(Object source) {
|
||||
public BpmProcessInstanceStatusEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
|
|
@ -3,17 +3,16 @@ package cn.iocoder.yudao.module.bpm.event;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
// TODO 芋艿:跨服务的情况下,该逻辑无法跑通
|
||||
/**
|
||||
* {@link BpmProcessInstanceResultEvent} 的监听器
|
||||
* {@link BpmProcessInstanceStatusEvent} 的监听器
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public abstract class BpmProcessInstanceResultEventListener
|
||||
implements ApplicationListener<BpmProcessInstanceResultEvent> {
|
||||
public abstract class BpmProcessInstanceStatusEventListener
|
||||
implements ApplicationListener<BpmProcessInstanceStatusEvent> {
|
||||
|
||||
@Override
|
||||
public final void onApplicationEvent(BpmProcessInstanceResultEvent event) {
|
||||
public final void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
|
||||
if (!StrUtil.equals(event.getProcessDefinitionKey(), getProcessDefinitionKey())) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -30,6 +29,6 @@ public abstract class BpmProcessInstanceResultEventListener
|
|||
*
|
||||
* @param event 事件
|
||||
*/
|
||||
protected abstract void onEvent(BpmProcessInstanceResultEvent event);
|
||||
protected abstract void onEvent(BpmProcessInstanceStatusEvent event);
|
||||
|
||||
}
|
||||
|
|
@ -34,10 +34,6 @@
|
|||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
|
||||
|
|
@ -100,11 +96,20 @@
|
|||
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工作流相关 -->
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
|
||||
<version>${revision}</version>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Flowable 工作流相关 -->
|
||||
<dependency>
|
||||
<groupId>org.flowable</groupId>
|
||||
<artifactId>flowable-spring-boot-starter-process</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flowable</groupId>
|
||||
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - BPM 流程分类")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/category")
|
||||
@Validated
|
||||
public class BpmCategoryController {
|
||||
|
||||
@Resource
|
||||
private BpmCategoryService categoryService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建流程分类")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:category:create')")
|
||||
public CommonResult<Long> createCategory(@Valid @RequestBody BpmCategorySaveReqVO createReqVO) {
|
||||
return success(categoryService.createCategory(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新流程分类")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:category:update')")
|
||||
public CommonResult<Boolean> updateCategory(@Valid @RequestBody BpmCategorySaveReqVO updateReqVO) {
|
||||
categoryService.updateCategory(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除流程分类")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:category:delete')")
|
||||
public CommonResult<Boolean> deleteCategory(@RequestParam("id") Long id) {
|
||||
categoryService.deleteCategory(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得流程分类")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:category:query')")
|
||||
public CommonResult<BpmCategoryRespVO> getCategory(@RequestParam("id") Long id) {
|
||||
BpmCategoryDO category = categoryService.getCategory(id);
|
||||
return success(BeanUtils.toBean(category, BpmCategoryRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得流程分类分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:category:query')")
|
||||
public CommonResult<PageResult<BpmCategoryRespVO>> getCategoryPage(@Valid BpmCategoryPageReqVO pageReqVO) {
|
||||
PageResult<BpmCategoryDO> pageResult = categoryService.getCategoryPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, BpmCategoryRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项")
|
||||
public CommonResult<List<BpmCategoryRespVO>> getCategorySimpleList() {
|
||||
List<BpmCategoryDO> list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
list.sort(Comparator.comparingInt(BpmCategoryDO::getSort));
|
||||
return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId())
|
||||
.setName(category.getName()).setCode(category.getCode())));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.*;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmFormConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -18,6 +20,7 @@ import javax.validation.Valid;
|
|||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - 动态表单")
|
||||
@RestController
|
||||
|
|
@ -31,14 +34,14 @@ public class BpmFormController {
|
|||
@PostMapping("/create")
|
||||
@Operation(summary = "创建动态表单")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:form:create')")
|
||||
public CommonResult<Long> createForm(@Valid @RequestBody BpmFormCreateReqVO createReqVO) {
|
||||
public CommonResult<Long> createForm(@Valid @RequestBody BpmFormSaveReqVO createReqVO) {
|
||||
return success(formService.createForm(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新动态表单")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:form:update')")
|
||||
public CommonResult<Boolean> updateForm(@Valid @RequestBody BpmFormUpdateReqVO updateReqVO) {
|
||||
public CommonResult<Boolean> updateForm(@Valid @RequestBody BpmFormSaveReqVO updateReqVO) {
|
||||
formService.updateForm(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
|
@ -58,14 +61,15 @@ public class BpmFormController {
|
|||
@PreAuthorize("@ss.hasPermission('bpm:form:query')")
|
||||
public CommonResult<BpmFormRespVO> getForm(@RequestParam("id") Long id) {
|
||||
BpmFormDO form = formService.getForm(id);
|
||||
return success(BpmFormConvert.INSTANCE.convert(form));
|
||||
return success(BeanUtils.toBean(form, BpmFormRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/list-all-simple")
|
||||
@GetMapping({"/list-all-simple", "/simple-list"})
|
||||
@Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
|
||||
public CommonResult<List<BpmFormSimpleRespVO>> getSimpleForms() {
|
||||
public CommonResult<List<BpmFormRespVO>> getFormSimpleList() {
|
||||
List<BpmFormDO> list = formService.getFormList();
|
||||
return success(BpmFormConvert.INSTANCE.convertList2(list));
|
||||
return success(convertList(list, formDO -> // 只返回 id、name 字段
|
||||
new BpmFormRespVO().setId(formDO.getId()).setName(formDO.getName())));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
|
|
@ -73,7 +77,7 @@ public class BpmFormController {
|
|||
@PreAuthorize("@ss.hasPermission('bpm:form:query')")
|
||||
public CommonResult<PageResult<BpmFormRespVO>> getFormPage(@Valid BpmFormPageReqVO pageVO) {
|
||||
PageResult<BpmFormDO> pageResult = formService.getFormPage(pageVO);
|
||||
return success(BpmFormConvert.INSTANCE.convertPage(pageResult));
|
||||
return success(BeanUtils.toBean(pageResult, BpmFormRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,42 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.io.IoUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.Model;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - 流程模型")
|
||||
@RestController
|
||||
|
|
@ -28,11 +46,39 @@ public class BpmModelController {
|
|||
|
||||
@Resource
|
||||
private BpmModelService modelService;
|
||||
@Resource
|
||||
private BpmFormService formService;
|
||||
@Resource
|
||||
private BpmCategoryService categoryService;
|
||||
@Resource
|
||||
private BpmProcessDefinitionService processDefinitionService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得模型分页")
|
||||
public CommonResult<PageResult<BpmModelPageItemRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
|
||||
return success(modelService.getModelPage(pageVO));
|
||||
public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
|
||||
PageResult<Model> pageResult = modelService.getModelPage(pageVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty(pageResult.getTotal()));
|
||||
}
|
||||
|
||||
// 拼接数据
|
||||
// 获得 Form 表单
|
||||
Set<Long> formIds = convertSet(pageResult.getList(), model -> {
|
||||
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
|
||||
return metaInfo != null ? metaInfo.getFormId() : null;
|
||||
});
|
||||
Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
|
||||
// 获得 Category Map
|
||||
Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
|
||||
convertSet(pageResult.getList(), Model::getCategory));
|
||||
// 获得 Deployment Map
|
||||
Set<String> deploymentIds = new HashSet<>();
|
||||
pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
|
||||
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
|
||||
// 获得 ProcessDefinition Map
|
||||
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
|
||||
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
|
||||
return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
|
|
@ -40,8 +86,12 @@ public class BpmModelController {
|
|||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:query')")
|
||||
public CommonResult<BpmModelRespVO> getModel(@RequestParam("id") String id) {
|
||||
BpmModelRespVO model = modelService.getModel(id);
|
||||
return success(model);
|
||||
Model model = modelService.getModel(id);
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] bpmnBytes = modelService.getModelBpmnXML(id);
|
||||
return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
|
|
@ -63,7 +113,7 @@ public class BpmModelController {
|
|||
@Operation(summary = "导入模型")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:import')")
|
||||
public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
|
||||
BpmModelCreateReqVO createReqVO = BpmModelConvert.INSTANCE.convert(importReqVO);
|
||||
BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class);
|
||||
// 读取文件
|
||||
String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
|
||||
return success(modelService.createModel(createReqVO, bpmnXml));
|
||||
|
|
@ -94,4 +144,5 @@ public class BpmModelController {
|
|||
modelService.deleteModel(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
|
@ -18,10 +28,12 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - 流程定义")
|
||||
@RestController
|
||||
|
|
@ -30,30 +42,73 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|||
public class BpmProcessDefinitionController {
|
||||
|
||||
@Resource
|
||||
private BpmProcessDefinitionService bpmDefinitionService;
|
||||
private BpmProcessDefinitionService processDefinitionService;
|
||||
@Resource
|
||||
private BpmFormService formService;
|
||||
@Resource
|
||||
private BpmCategoryService categoryService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得流程定义分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
|
||||
public CommonResult<PageResult<BpmProcessDefinitionPageItemRespVO>> getProcessDefinitionPage(
|
||||
public CommonResult<PageResult<BpmProcessDefinitionRespVO>> getProcessDefinitionPage(
|
||||
BpmProcessDefinitionPageReqVO pageReqVO) {
|
||||
return success(bpmDefinitionService.getProcessDefinitionPage(pageReqVO));
|
||||
PageResult<ProcessDefinition> pageResult = processDefinitionService.getProcessDefinitionPage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty(pageResult.getTotal()));
|
||||
}
|
||||
|
||||
// 获得 Category Map
|
||||
Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
|
||||
convertSet(pageResult.getList(), ProcessDefinition::getCategory));
|
||||
// 获得 Deployment Map
|
||||
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
|
||||
convertSet(pageResult.getList(), ProcessDefinition::getDeploymentId));
|
||||
// 获得 BpmProcessDefinitionInfoDO Map
|
||||
Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
|
||||
convertSet(pageResult.getList(), ProcessDefinition::getId));
|
||||
// 获得 Form Map
|
||||
Map<Long, BpmFormDO> formMap = formService.getFormMap(
|
||||
convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId));
|
||||
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage(
|
||||
pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap));
|
||||
}
|
||||
|
||||
@GetMapping ("/list")
|
||||
@Operation(summary = "获得流程定义列表")
|
||||
@Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
|
||||
public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
|
||||
BpmProcessDefinitionListReqVO listReqVO) {
|
||||
return success(bpmDefinitionService.getProcessDefinitionList(listReqVO));
|
||||
@RequestParam("suspensionState") Integer suspensionState) {
|
||||
List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// 获得 BpmProcessDefinitionInfoDO Map
|
||||
Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
|
||||
convertSet(list, ProcessDefinition::getId));
|
||||
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
|
||||
list, null, processDefinitionMap, null, null));
|
||||
}
|
||||
|
||||
@GetMapping ("/get-bpmn-xml")
|
||||
@Operation(summary = "获得流程定义的 BPMN XML")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@GetMapping ("/get")
|
||||
@Operation(summary = "获得流程定义")
|
||||
@Parameter(name = "id", description = "流程编号", required = true, example = "1024")
|
||||
@Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
|
||||
public CommonResult<String> getProcessDefinitionBpmnXML(@RequestParam("id") String id) {
|
||||
String bpmnXML = bpmDefinitionService.getProcessDefinitionBpmnXML(id);
|
||||
return success(bpmnXML);
|
||||
public CommonResult<BpmProcessDefinitionRespVO> getProcessDefinition(
|
||||
@RequestParam(value = "id", required = false) String id,
|
||||
@RequestParam(value = "key", required = false) String key) {
|
||||
ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id)
|
||||
: processDefinitionService.getActiveProcessDefinition(key);
|
||||
if (processDefinition == null) {
|
||||
return success(null);
|
||||
}
|
||||
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
|
||||
List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
|
||||
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
|
||||
processDefinition, null, null, null, null, bpmnModel, userTaskList));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionSaveReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessExpressionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - BPM 流程表达式")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/process-expression")
|
||||
@Validated
|
||||
public class BpmProcessExpressionController {
|
||||
|
||||
@Resource
|
||||
private BpmProcessExpressionService processExpressionService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建流程表达式")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-expression:create')")
|
||||
public CommonResult<Long> createProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO createReqVO) {
|
||||
return success(processExpressionService.createProcessExpression(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新流程表达式")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-expression:update')")
|
||||
public CommonResult<Boolean> updateProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO updateReqVO) {
|
||||
processExpressionService.updateProcessExpression(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除流程表达式")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-expression:delete')")
|
||||
public CommonResult<Boolean> deleteProcessExpression(@RequestParam("id") Long id) {
|
||||
processExpressionService.deleteProcessExpression(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得流程表达式")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-expression:query')")
|
||||
public CommonResult<BpmProcessExpressionRespVO> getProcessExpression(@RequestParam("id") Long id) {
|
||||
BpmProcessExpressionDO processExpression = processExpressionService.getProcessExpression(id);
|
||||
return success(BeanUtils.toBean(processExpression, BpmProcessExpressionRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得流程表达式分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-expression:query')")
|
||||
public CommonResult<PageResult<BpmProcessExpressionRespVO>> getProcessExpressionPage(
|
||||
@Valid BpmProcessExpressionPageReqVO pageReqVO) {
|
||||
PageResult<BpmProcessExpressionDO> pageResult = processExpressionService.getProcessExpressionPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerSaveReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessListenerDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessListenerService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - BPM 流程监听器")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/process-listener")
|
||||
@Validated
|
||||
public class BpmProcessListenerController {
|
||||
|
||||
@Resource
|
||||
private BpmProcessListenerService processListenerService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建流程监听器")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-listener:create')")
|
||||
public CommonResult<Long> createProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO createReqVO) {
|
||||
return success(processListenerService.createProcessListener(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新流程监听器")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-listener:update')")
|
||||
public CommonResult<Boolean> updateProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO updateReqVO) {
|
||||
processListenerService.updateProcessListener(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除流程监听器")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-listener:delete')")
|
||||
public CommonResult<Boolean> deleteProcessListener(@RequestParam("id") Long id) {
|
||||
processListenerService.deleteProcessListener(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得流程监听器")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-listener:query')")
|
||||
public CommonResult<BpmProcessListenerRespVO> getProcessListener(@RequestParam("id") Long id) {
|
||||
BpmProcessListenerDO processListener = processListenerService.getProcessListener(id);
|
||||
return success(BeanUtils.toBean(processListener, BpmProcessListenerRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得流程监听器分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-listener:query')")
|
||||
public CommonResult<PageResult<BpmProcessListenerRespVO>> getProcessListenerPage(
|
||||
@Valid BpmProcessListenerPageReqVO pageReqVO) {
|
||||
PageResult<BpmProcessListenerDO> pageResult = processListenerService.getProcessListenerPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, BpmProcessListenerRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 任务分配规则")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/task-assign-rule")
|
||||
@Validated
|
||||
public class BpmTaskAssignRuleController {
|
||||
|
||||
@Resource
|
||||
private BpmTaskAssignRuleService taskAssignRuleService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得任务分配规则列表")
|
||||
@Parameters({
|
||||
@Parameter(name = "modelId", description = "模型编号", example = "1024"),
|
||||
@Parameter(name = "processDefinitionId", description = "流程定义的编号", example = "2048")
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:query')")
|
||||
public CommonResult<List<BpmTaskAssignRuleRespVO>> getTaskAssignRuleList(
|
||||
@RequestParam(value = "modelId", required = false) String modelId,
|
||||
@RequestParam(value = "processDefinitionId", required = false) String processDefinitionId) {
|
||||
return success(taskAssignRuleService.getTaskAssignRuleList(modelId, processDefinitionId));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建任务分配规则")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:create')")
|
||||
public CommonResult<Long> createTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleCreateReqVO reqVO) {
|
||||
return success(taskAssignRuleService.createTaskAssignRule(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新任务分配规则")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:update')")
|
||||
public CommonResult<Boolean> updateTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleUpdateReqVO reqVO) {
|
||||
taskAssignRuleService.updateTaskAssignRule(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmUserGroupConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -22,6 +21,7 @@ import javax.validation.Valid;
|
|||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@Tag(name = "管理后台 - 用户组")
|
||||
@RestController
|
||||
|
|
@ -35,14 +35,14 @@ public class BpmUserGroupController {
|
|||
@PostMapping("/create")
|
||||
@Operation(summary = "创建用户组")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:user-group:create')")
|
||||
public CommonResult<Long> createUserGroup(@Valid @RequestBody BpmUserGroupCreateReqVO createReqVO) {
|
||||
public CommonResult<Long> createUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO createReqVO) {
|
||||
return success(userGroupService.createUserGroup(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新用户组")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:user-group:update')")
|
||||
public CommonResult<Boolean> updateUserGroup(@Valid @RequestBody BpmUserGroupUpdateReqVO updateReqVO) {
|
||||
public CommonResult<Boolean> updateUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO updateReqVO) {
|
||||
userGroupService.updateUserGroup(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ public class BpmUserGroupController {
|
|||
@PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
|
||||
public CommonResult<BpmUserGroupRespVO> getUserGroup(@RequestParam("id") Long id) {
|
||||
BpmUserGroupDO userGroup = userGroupService.getUserGroup(id);
|
||||
return success(BpmUserGroupConvert.INSTANCE.convert(userGroup));
|
||||
return success(BeanUtils.toBean(userGroup, BpmUserGroupRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
|
|
@ -70,16 +70,14 @@ public class BpmUserGroupController {
|
|||
@PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
|
||||
public CommonResult<PageResult<BpmUserGroupRespVO>> getUserGroupPage(@Valid BpmUserGroupPageReqVO pageVO) {
|
||||
PageResult<BpmUserGroupDO> pageResult = userGroupService.getUserGroupPage(pageVO);
|
||||
return success(BpmUserGroupConvert.INSTANCE.convertPage(pageResult));
|
||||
return success(BeanUtils.toBean(pageResult, BpmUserGroupRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/list-all-simple")
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获取用户组精简信息列表", description = "只包含被开启的用户组,主要用于前端的下拉选项")
|
||||
public CommonResult<List<BpmUserGroupRespVO>> getSimpleUserGroups() {
|
||||
// 获用户门列表,只要开启状态的
|
||||
public CommonResult<List<BpmUserGroupRespVO>> getUserGroupSimpleList() {
|
||||
List<BpmUserGroupDO> list = userGroupService.getUserGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
// 排序后,返回给前端
|
||||
return success(BpmUserGroupConvert.INSTANCE.convertList2(list));
|
||||
return success(convertList(list, group -> new BpmUserGroupRespVO().setId(group.getId()).setName(group.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - BPM 流程分类分页 Request VO")
|
||||
@Data
|
||||
public class BpmCategoryPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "分类名", example = "王五")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "分类标志", example = "OA")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "分类状态", example = "1")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - BPM 流程分类 Response VO")
|
||||
@Data
|
||||
public class BpmCategoryRespVO {
|
||||
|
||||
@Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "分类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - BPM 流程分类新增/修改 Request VO")
|
||||
@Data
|
||||
public class BpmCategorySaveReqVO {
|
||||
|
||||
@Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
|
||||
@NotEmpty(message = "分类名不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "分类描述", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
|
||||
@NotEmpty(message = "分类标志不能为空")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "分类状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "分类排序不能为空")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - BPM 流程表达式分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BpmProcessExpressionPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "表达式名字", example = "李四")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "表达式状态", example = "1")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - BPM 流程表达式 Response VO")
|
||||
@Data
|
||||
public class BpmProcessExpressionRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3870")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "表达式名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@ExcelProperty("表达式名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "表达式状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "表达式", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String expression;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - BPM 流程表达式新增/修改 Request VO")
|
||||
@Data
|
||||
public class BpmProcessExpressionSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3870")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "表达式名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@NotEmpty(message = "表达式名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "表达式状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "表达式状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "表达式", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "表达式不能为空")
|
||||
private String expression;
|
||||
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 动态表单创建 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BpmFormCreateReqVO extends BpmFormBaseVO {
|
||||
|
||||
@Schema(description = "表单的配置-JSON 字符串", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "表单的配置不能为空")
|
||||
private String conf;
|
||||
|
||||
@Schema(description = "表单项的数组-JSON 字符串的数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "表单项的数组不能为空")
|
||||
private List<String> fields;
|
||||
|
||||
}
|
||||
|
|
@ -3,13 +3,9 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
|
|||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
@Schema(description = "管理后台 - 动态表单分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BpmFormPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "表单名称", example = "芋道")
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue