Merge remote-tracking branch 'origin/master'

pull/111/head
xueshuai 2024-04-08 16:57:28 +08:00
commit 918c34247c
436 changed files with 7684 additions and 9538 deletions

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

View File

@ -145,6 +145,8 @@
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
![功能图](/.image/common/system-feature.png)
### 工作流程
| | 功能 | 描述 |
@ -157,6 +159,8 @@
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
![功能图](/.image/common/bpm-feature.png)
### 支付系统
| | 功能 | 描述 |
@ -192,6 +196,8 @@
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
![功能图](/.image/common/infra-feature.png)
### 数据报表
| | 功能 | 描述 |

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -1 +0,0 @@
防止IDEA将`.`和`/`混为一谈

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
/**
*
*

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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);
}

View File

@ -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();
}
}
}

View File

@ -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 id name type parentId
522 441931 凤岗镇 4 441900
523 441932 长安镇 4 441900
524 442000 中山市 3 440000
525 442001 石岐街道 4 442000
526 442002 东区街道 4 442000
527 442003 中山港街道 4 442000
528 442004 西区街道 4 442000
529 442005 南区街道 4 442000
530 442006 五桂山街道 4 442000
531 442007 民众街道 4 442000
532 442008 南朗街道 4 442000
533 442009 黄圃镇 4 442000
534 442010 东凤镇 4 442000
535 442011 古镇镇 4 442000
536 442012 沙溪镇 4 442000
537 442013 坦洲镇 4 442000
538 442014 港口镇 4 442000
539 442015 三角镇 4 442000
540 442016 横栏镇 4 442000
541 442017 南头镇 4 442000
542 442018 阜沙镇 4 442000
543 442019 三乡镇 4 442000
544 442020 板芙镇 4 442000
545 442021 大涌镇 4 442000
546 442022 神湾镇 4 442000
547 442023 小榄镇 4 442000
548 445100 潮州市 3 440000
549 445200 揭阳市 3 440000
550 445300 云浮市 3 440000

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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 {
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.core.service;
/**
* Framework Service
*
* @author
*/
public interface OperateLogFrameworkService {
/**
*
*
* @param operateLog
*/
void createOperateLog(OperateLog operateLog);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,6 +0,0 @@
/**
*
*
* @author
*/
package cn.iocoder.yudao.framework.operatelog;

View File

@ -1,2 +0,0 @@
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogRpcAutoConfiguration
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration

View File

@ -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
*

View File

@ -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>

View File

@ -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 {
/**
*

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
/**
* 使 Easy-Trans 使 VO
*/
package cn.iocoder.yudao.framework.translate;

View File

@ -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

View File

@ -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));
}
}

View File

@ -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) {

View File

@ -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;
/**
*
*/

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
/**
*
*

View File

@ -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());
}
}

View File

@ -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 {};
}

View File

@ -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),
/**

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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");
// }
//
//}

View File

@ -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());
}

View File

@ -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));
}
/**

View File

@ -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());
}

View File

@ -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;
/**
*
*/

View File

@ -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());
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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
*
* keytaskKey
* value
* { taskKey1 :[1, 2] } taskKey1 userId 1,2
*/
@Schema(description = "发起人自选审批人 Map")
private Map<String, List<Long>> startUserSelectAssignees;
}

View File

@ -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, "流程表达式不存在");
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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, "审批不通过任务,原因:");
}
}

View File

@ -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;
}
}
}

View File

@ -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 BB
*/
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());
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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())));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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())));
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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