Merge branch 'master-jdk21' of https://gitee.com/zhijiantianya/yudao-cloud
# Conflicts: # yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/config/YudaoApiLogAutoConfiguration.java # yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java # yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLog.java # yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLog.java # yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.javapull/107/head
commit
38135a1792
|
@ -1,598 +0,0 @@
|
|||
package liquibase.database.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import liquibase.CatalogAndSchema;
|
||||
import liquibase.Scope;
|
||||
import liquibase.database.AbstractJdbcDatabase;
|
||||
import liquibase.database.DatabaseConnection;
|
||||
import liquibase.database.OfflineConnection;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.DatabaseException;
|
||||
import liquibase.exception.UnexpectedLiquibaseException;
|
||||
import liquibase.exception.ValidationErrors;
|
||||
import liquibase.executor.ExecutorService;
|
||||
import liquibase.statement.DatabaseFunction;
|
||||
import liquibase.statement.SequenceCurrentValueFunction;
|
||||
import liquibase.statement.SequenceNextValueFunction;
|
||||
import liquibase.statement.core.RawCallStatement;
|
||||
import liquibase.statement.core.RawSqlStatement;
|
||||
import liquibase.structure.DatabaseObject;
|
||||
import liquibase.structure.core.Catalog;
|
||||
import liquibase.structure.core.Index;
|
||||
import liquibase.structure.core.PrimaryKey;
|
||||
import liquibase.structure.core.Schema;
|
||||
import liquibase.util.JdbcUtils;
|
||||
import liquibase.util.StringUtil;
|
||||
|
||||
public class DmDatabase extends AbstractJdbcDatabase {
|
||||
private static final String PRODUCT_NAME = "DM DBMS";
|
||||
|
||||
@Override
|
||||
protected String getDefaultDatabaseProductName() {
|
||||
return PRODUCT_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this AbstractDatabase subclass the correct one to use for the given connection.
|
||||
*
|
||||
* @param conn
|
||||
*/
|
||||
@Override
|
||||
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
|
||||
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
|
||||
}
|
||||
|
||||
/**
|
||||
* If this database understands the given url, return the default driver class name. Otherwise return null.
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
@Override
|
||||
public String getDefaultDriver(String url) {
|
||||
if(url.startsWith("jdbc:dm")) {
|
||||
return "dm.jdbc.driver.DmDriver";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an all-lower-case short name of the product. Used for end-user selecting of database type
|
||||
* such as the DBMS precondition.
|
||||
*/
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "dm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getDefaultPort() {
|
||||
return 5236;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this database support initially deferrable columns.
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsInitiallyDeferrableColumns() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTablespaces() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return PRIORITY_DEFAULT;
|
||||
}
|
||||
|
||||
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
|
||||
|
||||
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
|
||||
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
|
||||
public static final int ORACLE_12C_MAJOR_VERSION = 12;
|
||||
|
||||
private Set<String> reservedWords = new HashSet<>();
|
||||
private Set<String> userDefinedTypes;
|
||||
private Map<String, String> savedSessionNlsSettings;
|
||||
|
||||
private Boolean canAccessDbaRecycleBin;
|
||||
private Integer databaseMajorVersion;
|
||||
private Integer databaseMinorVersion;
|
||||
|
||||
/**
|
||||
* Default constructor for an object that represents the Oracle Database DBMS.
|
||||
*/
|
||||
public DmDatabase() {
|
||||
super.unquotedObjectsAreUppercased = true;
|
||||
//noinspection HardCodedStringLiteral
|
||||
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
|
||||
// Setting list of Oracle's native functions
|
||||
//noinspection HardCodedStringLiteral
|
||||
dateFunctions.add(new DatabaseFunction("SYSDATE"));
|
||||
//noinspection HardCodedStringLiteral
|
||||
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
|
||||
//noinspection HardCodedStringLiteral
|
||||
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
|
||||
//noinspection HardCodedStringLiteral
|
||||
super.sequenceNextValueFunction = "%s.nextval";
|
||||
//noinspection HardCodedStringLiteral
|
||||
super.sequenceCurrentValueFunction = "%s.currval";
|
||||
}
|
||||
|
||||
private void tryProxySession(final String url, final Connection con) {
|
||||
Matcher m = PROXY_USER.matcher(url);
|
||||
if (m.matches()) {
|
||||
Properties props = new Properties();
|
||||
props.put("PROXY_USER_NAME", m.group(1));
|
||||
try {
|
||||
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(con, 1, props);
|
||||
} catch (Exception e) {
|
||||
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDatabaseMajorVersion() throws DatabaseException {
|
||||
if (databaseMajorVersion == null) {
|
||||
return super.getDatabaseMajorVersion();
|
||||
} else {
|
||||
return databaseMajorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDatabaseMinorVersion() throws DatabaseException {
|
||||
if (databaseMinorVersion == null) {
|
||||
return super.getDatabaseMinorVersion();
|
||||
} else {
|
||||
return databaseMinorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcCatalogName(CatalogAndSchema schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJdbcSchemaName(CatalogAndSchema schema) {
|
||||
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
|
||||
if (StringUtil.isEmpty(generationType)) {
|
||||
return super.getAutoIncrementClause();
|
||||
}
|
||||
|
||||
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
|
||||
String generationStrategy = generationType;
|
||||
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
|
||||
generationStrategy += " ON NULL";
|
||||
}
|
||||
return String.format(autoIncrementClause, generationStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generatePrimaryKeyName(String tableName) {
|
||||
if (tableName.length() > 27) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
|
||||
} else {
|
||||
//noinspection HardCodedStringLiteral
|
||||
return "PK_" + tableName.toUpperCase(Locale.US);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReservedWord(String objectName) {
|
||||
return reservedWords.contains(objectName.toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSequences() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle supports catalogs in liquibase terms
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsSchemas() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConnectionCatalogName() throws DatabaseException {
|
||||
if (getConnection() instanceof OfflineConnection) {
|
||||
return getConnection().getCatalog();
|
||||
}
|
||||
try {
|
||||
//noinspection HardCodedStringLiteral
|
||||
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
|
||||
} catch (Exception e) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultCatalogName() {//NOPMD
|
||||
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
|
||||
*
|
||||
* <p>Convert an ISO8601 date string to one of the following results:
|
||||
* to_date('1995-05-23', 'YYYY-MM-DD')
|
||||
* to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
|
||||
* <p>
|
||||
* Implementation restriction:<br>
|
||||
* Currently, only the following subsets of ISO8601 are supported:<br>
|
||||
* <ul>
|
||||
* <li>YYYY-MM-DD</li>
|
||||
* <li>YYYY-MM-DDThh:mm:ss</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public String getDateLiteral(String isoDate) {
|
||||
String normalLiteral = super.getDateLiteral(isoDate);
|
||||
|
||||
if (isDateOnly(isoDate)) {
|
||||
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
|
||||
} else if (isTimeOnly(isoDate)) {
|
||||
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
|
||||
} else if (isTimestamp(isoDate)) {
|
||||
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
|
||||
} else if (isDateTime(isoDate)) {
|
||||
int seppos = normalLiteral.lastIndexOf('.');
|
||||
if (seppos != -1) {
|
||||
normalLiteral = normalLiteral.substring(0, seppos) + "'";
|
||||
}
|
||||
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
|
||||
}
|
||||
return "UNSUPPORTED:" + isoDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSystemObject(DatabaseObject example) {
|
||||
if (example == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isLiquibaseObject(example)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (example instanceof Schema) {
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
|
||||
return true;
|
||||
}
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
|
||||
return true;
|
||||
}
|
||||
} else if (isSystemObject(example.getSchema())) {
|
||||
return true;
|
||||
}
|
||||
if (example instanceof Catalog) {
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
|
||||
return true;
|
||||
}
|
||||
} else if (example.getName() != null) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("BIN$")) { //oracle deleted table
|
||||
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
|
||||
if (!filteredInOriginalQuery) {
|
||||
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
|
||||
}
|
||||
|
||||
if (filteredInOriginalQuery) {
|
||||
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
|
||||
liquibase.statement.UniqueConstraint));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("DR$")) { //oracle index tables
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
|
||||
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
|
||||
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
|
||||
return true;
|
||||
} else //noinspection HardCodedStringLiteral
|
||||
if (example.getName().startsWith("USLOG$")) { //for update materialized view
|
||||
return true;
|
||||
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.isSystemObject(example);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAutoIncrement() {
|
||||
// Oracle supports Identity beginning with version 12c
|
||||
boolean isAutoIncrementSupported = false;
|
||||
|
||||
try {
|
||||
if (getDatabaseMajorVersion() >= 12) {
|
||||
isAutoIncrementSupported = true;
|
||||
}
|
||||
|
||||
// Returning true will generate create table command with 'IDENTITY' clause, example:
|
||||
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
|
||||
|
||||
// While returning false will continue to generate create table command without 'IDENTITY' clause, example:
|
||||
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
|
||||
|
||||
} catch (DatabaseException ex) {
|
||||
isAutoIncrementSupported = false;
|
||||
}
|
||||
|
||||
return isAutoIncrementSupported;
|
||||
}
|
||||
|
||||
|
||||
// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
|
||||
// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
|
||||
//
|
||||
// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
|
||||
//
|
||||
// UniqueConstraint constraint = null;
|
||||
// for (Map map : maps) {
|
||||
// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
|
||||
// returnSet.add(constraint);
|
||||
// Table table = new Table((String) map.get("TABLE_NAME"));
|
||||
// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
|
||||
// }
|
||||
// }
|
||||
// if (constraint != null) {
|
||||
// returnSet.add(constraint);
|
||||
// }
|
||||
//
|
||||
// return returnSet;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public boolean supportsRestrictForeignKeys() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataTypeMaxParameters(String dataTypeName) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
|
||||
return 0;
|
||||
}
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
|
||||
return 0;
|
||||
}
|
||||
return super.getDataTypeMaxParameters(dataTypeName);
|
||||
}
|
||||
|
||||
public String getSystemTableWhereClause(String tableNameColumn) {
|
||||
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
|
||||
"AQ$",
|
||||
"DR$",
|
||||
"SYS_IOT_OVER",
|
||||
"MLOG$_",
|
||||
"RUPD$_",
|
||||
"WM$_",
|
||||
"ISEQ$$_",
|
||||
"USLOG$",
|
||||
"SYS_FBA"));
|
||||
|
||||
for (int i = 0;i<clauses.size(); i++) {
|
||||
clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
|
||||
}
|
||||
return "("+ StringUtil.join(clauses, " AND ") + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean jdbcCallsCatalogsSchemas() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Set<String> getUserDefinedTypes() {
|
||||
if (userDefinedTypes == null) {
|
||||
userDefinedTypes = new HashSet<>();
|
||||
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
|
||||
try {
|
||||
try {
|
||||
//noinspection HardCodedStringLiteral
|
||||
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
|
||||
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
|
||||
//noinspection HardCodedStringLiteral
|
||||
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
|
||||
}
|
||||
} catch (DatabaseException e) {
|
||||
//ignore error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userDefinedTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
|
||||
return databaseFunction.toString();
|
||||
}
|
||||
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
|
||||
SequenceCurrentValueFunction)) {
|
||||
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
|
||||
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
|
||||
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
|
||||
|
||||
}
|
||||
|
||||
return super.generateDatabaseFunctionValue(databaseFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationErrors validate() {
|
||||
ValidationErrors errors = super.validate();
|
||||
DatabaseConnection connection = getConnection();
|
||||
if ((connection == null) || (connection instanceof OfflineConnection)) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (!canAccessDbaRecycleBin()) {
|
||||
errors.addWarning(getDbaRecycleBinWarning());
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
}
|
||||
|
||||
public String getDbaRecycleBinWarning() {
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
|
||||
// HardCodedStringLiteral
|
||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
||||
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
|
||||
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
|
||||
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
|
||||
" issue.\n" +
|
||||
"\n" +
|
||||
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
|
||||
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
|
||||
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
|
||||
"\n" +
|
||||
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
|
||||
}
|
||||
|
||||
public boolean canAccessDbaRecycleBin() {
|
||||
if (canAccessDbaRecycleBin == null) {
|
||||
DatabaseConnection connection = getConnection();
|
||||
if ((connection == null) || (connection instanceof OfflineConnection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Statement statement = null;
|
||||
try {
|
||||
statement = ((JdbcConnection) connection).createStatement();
|
||||
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
|
||||
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
|
||||
this.canAccessDbaRecycleBin = true;
|
||||
} catch (Exception e) {
|
||||
//noinspection HardCodedStringLiteral
|
||||
if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
|
||||
this.canAccessDbaRecycleBin = false;
|
||||
} else {
|
||||
//noinspection HardCodedStringLiteral
|
||||
Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
|
||||
this.canAccessDbaRecycleBin = false;
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.close(null, statement);
|
||||
}
|
||||
}
|
||||
|
||||
return canAccessDbaRecycleBin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsNotNullConstraintNames() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
|
||||
* the following form (case-insensitive comparison):
|
||||
* 1st character: A-Z
|
||||
* 2..n characters: A-Z0-9$_#
|
||||
* The maximum length of an identifier differs by Oracle version and object type.
|
||||
*/
|
||||
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
|
||||
if ((identifier == null) || (identifier.length() < 1))
|
||||
return false;
|
||||
|
||||
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
|
||||
* we do know seem to be supported as 12cR2 long identifiers, so:
|
||||
*/
|
||||
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
|
||||
* is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
|
||||
* object types).
|
||||
*
|
||||
* @return the maximum length of an object identifier, in bytes
|
||||
*/
|
||||
public int getIdentifierMaximumLength() {
|
||||
try {
|
||||
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
|
||||
return SHORT_IDENTIFIERS_LENGTH;
|
||||
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
|
||||
return SHORT_IDENTIFIERS_LENGTH;
|
||||
} else {
|
||||
return LONG_IDENTIFIERS_LEGNTH;
|
||||
}
|
||||
} catch (DatabaseException ex) {
|
||||
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
package liquibase.datatype.core;
|
||||
|
||||
import liquibase.change.core.LoadDataChange;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.core.*;
|
||||
import liquibase.datatype.DataTypeInfo;
|
||||
import liquibase.datatype.DatabaseDataType;
|
||||
import liquibase.datatype.LiquibaseDataType;
|
||||
import liquibase.exception.UnexpectedLiquibaseException;
|
||||
import liquibase.statement.DatabaseFunction;
|
||||
import liquibase.util.StringUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
|
||||
public class BooleanType extends LiquibaseDataType {
|
||||
|
||||
@Override
|
||||
public DatabaseDataType toDatabaseDataType(Database database) {
|
||||
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
|
||||
if ((database instanceof Firebird3Database)) {
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
}
|
||||
|
||||
if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
|
||||
return new DatabaseDataType("SMALLINT");
|
||||
} else if (database instanceof MSSQLDatabase) {
|
||||
return new DatabaseDataType(database.escapeDataTypeName("bit"));
|
||||
} else if (database instanceof MySQLDatabase) {
|
||||
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
|
||||
return new DatabaseDataType("BIT", getParameters());
|
||||
}
|
||||
return new DatabaseDataType("BIT", 1);
|
||||
} else if (database instanceof OracleDatabase) {
|
||||
return new DatabaseDataType("NUMBER", 1);
|
||||
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
|
||||
return new DatabaseDataType("BIT");
|
||||
} else if (database instanceof DerbyDatabase) {
|
||||
if (((DerbyDatabase) database).supportsBooleanDataType()) {
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
} else {
|
||||
return new DatabaseDataType("SMALLINT");
|
||||
}
|
||||
} else if (database instanceof DB2Database) {
|
||||
if (((DB2Database) database).supportsBooleanDataType())
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
else
|
||||
return new DatabaseDataType("SMALLINT");
|
||||
} else if (database instanceof HsqlDatabase) {
|
||||
return new DatabaseDataType("BOOLEAN");
|
||||
} else if (database instanceof PostgresDatabase) {
|
||||
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
|
||||
return new DatabaseDataType("BIT", getParameters());
|
||||
}
|
||||
} else if (database instanceof DmDatabase) { // dhb52: DM Support
|
||||
return new DatabaseDataType("bit");
|
||||
}
|
||||
|
||||
return super.toDatabaseDataType(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String objectToSql(Object value, Database database) {
|
||||
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String returnValue;
|
||||
if (value instanceof String) {
|
||||
value = ((String) value).replaceAll("'", "");
|
||||
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
|
||||
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
} else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
|
||||
returnValue = "b'"
|
||||
+ value.toString()
|
||||
.replace("b", "")
|
||||
.replace("\"", "")
|
||||
.replace("::it", "")
|
||||
+ "'::\"bit\"";
|
||||
} else {
|
||||
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
|
||||
}
|
||||
} else if (value instanceof Long) {
|
||||
if (Long.valueOf(1).equals(value)) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
}
|
||||
} else if (value instanceof Number) {
|
||||
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
}
|
||||
} else if (value instanceof DatabaseFunction) {
|
||||
return value.toString();
|
||||
} else if (value instanceof Boolean) {
|
||||
if (((Boolean) value)) {
|
||||
returnValue = this.getTrueBooleanValue(database);
|
||||
} else {
|
||||
returnValue = this.getFalseBooleanValue(database);
|
||||
}
|
||||
} else {
|
||||
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
protected boolean isNumericBoolean(Database database) {
|
||||
if (database instanceof Firebird3Database) {
|
||||
return false;
|
||||
}
|
||||
if (database instanceof DerbyDatabase) {
|
||||
return !((DerbyDatabase) database).supportsBooleanDataType();
|
||||
} else if (database instanceof DB2Database) {
|
||||
return !((DB2Database) database).supportsBooleanDataType();
|
||||
}
|
||||
return (database instanceof Db2zDatabase)
|
||||
|| (database instanceof FirebirdDatabase)
|
||||
|| (database instanceof MSSQLDatabase)
|
||||
|| (database instanceof MySQLDatabase)
|
||||
|| (database instanceof OracleDatabase)
|
||||
|| (database instanceof SQLiteDatabase)
|
||||
|| (database instanceof SybaseASADatabase)
|
||||
|| (database instanceof SybaseDatabase)
|
||||
|| (database instanceof DmDatabase); // dhb52: DM Support
|
||||
}
|
||||
|
||||
/**
|
||||
* The database-specific value to use for "false" "boolean" columns.
|
||||
*/
|
||||
public String getFalseBooleanValue(Database database) {
|
||||
if (isNumericBoolean(database)) {
|
||||
return "0";
|
||||
}
|
||||
if (database instanceof InformixDatabase) {
|
||||
return "'f'";
|
||||
}
|
||||
return "FALSE";
|
||||
}
|
||||
|
||||
/**
|
||||
* The database-specific value to use for "true" "boolean" columns.
|
||||
*/
|
||||
public String getTrueBooleanValue(Database database) {
|
||||
if (isNumericBoolean(database)) {
|
||||
return "1";
|
||||
}
|
||||
if (database instanceof InformixDatabase) {
|
||||
return "'t'";
|
||||
}
|
||||
return "TRUE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
|
||||
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
|||
防止IDEA将`.`和`/`混为一谈
|
|
@ -1,21 +0,0 @@
|
|||
liquibase.database.core.CockroachDatabase
|
||||
liquibase.database.core.DB2Database
|
||||
liquibase.database.core.Db2zDatabase
|
||||
liquibase.database.core.DerbyDatabase
|
||||
liquibase.database.core.Firebird3Database
|
||||
liquibase.database.core.FirebirdDatabase
|
||||
liquibase.database.core.H2Database
|
||||
liquibase.database.core.HsqlDatabase
|
||||
liquibase.database.core.InformixDatabase
|
||||
liquibase.database.core.Ingres9Database
|
||||
liquibase.database.core.MSSQLDatabase
|
||||
liquibase.database.core.MariaDBDatabase
|
||||
liquibase.database.core.MockDatabase
|
||||
liquibase.database.core.MySQLDatabase
|
||||
liquibase.database.core.OracleDatabase
|
||||
liquibase.database.core.PostgresDatabase
|
||||
liquibase.database.core.SQLiteDatabase
|
||||
liquibase.database.core.SybaseASADatabase
|
||||
liquibase.database.core.SybaseDatabase
|
||||
liquibase.database.core.DmDatabase
|
||||
liquibase.database.core.UnsupportedDatabase
|
|
@ -11,7 +11,7 @@
|
|||
Target Server Version : 80200 (8.2.0)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 30/03/2024 20:42:06
|
||||
Date: 03/04/2024 19:07:31
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
@ -327,9 +327,13 @@ CREATE TABLE `infra_api_access_log` (
|
|||
`application_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名',
|
||||
`request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求方法名',
|
||||
`request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求地址',
|
||||
`request_params` varchar(8000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求参数',
|
||||
`request_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数',
|
||||
`response_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '响应结果',
|
||||
`user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 IP',
|
||||
`user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '浏览器 UA',
|
||||
`operate_module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作模块',
|
||||
`operate_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作名',
|
||||
`operate_type` tinyint NULL DEFAULT 0 COMMENT '操作分类',
|
||||
`begin_time` datetime NOT NULL COMMENT '开始请求时间',
|
||||
`end_time` datetime NOT NULL COMMENT '结束请求时间',
|
||||
`duration` int NOT NULL COMMENT '执行时长',
|
||||
|
@ -343,7 +347,7 @@ CREATE TABLE `infra_api_access_log` (
|
|||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_create_time`(`create_time` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 35832 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 35920 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of infra_api_access_log
|
||||
|
@ -385,7 +389,7 @@ CREATE TABLE `infra_api_error_log` (
|
|||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 16429 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 16462 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of infra_api_error_log
|
||||
|
@ -494,7 +498,7 @@ CREATE TABLE `infra_config` (
|
|||
-- Records of infra_config
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'biz', 1, '用户管理-账号初始密码', 'sys.user.init-password', '123456', b'0', '初始化密码 123456', 'admin', '2021-01-05 17:03:48', '1', '2024-02-28 22:54:14', b'0');
|
||||
INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'biz', 1, '用户管理-账号初始密码', 'sys.user.init-password', '123456', b'0', '初始化密码 123456', 'admin', '2021-01-05 17:03:48', '1', '2024-04-03 17:22:28', b'0');
|
||||
INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (7, 'url', 2, 'MySQL 监控的地址', 'url.druid', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:33:38', b'0');
|
||||
INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8, 'url', 2, 'SkyWalking 监控的地址', 'url.skywalking', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:57:03', b'0');
|
||||
INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (9, 'url', 2, 'Spring Boot Admin 监控的地址', 'url.spring-boot-admin', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:52:07', b'0');
|
||||
|
@ -690,7 +694,7 @@ CREATE TABLE `infra_file` (
|
|||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1301 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of infra_file
|
||||
|
@ -1416,7 +1420,7 @@ CREATE TABLE `system_login_log` (
|
|||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3054 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3066 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_login_log
|
||||
|
@ -2453,7 +2457,7 @@ CREATE TABLE `system_oauth2_access_token` (
|
|||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
|
||||
INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6332 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6366 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_oauth2_access_token
|
||||
|
@ -2575,7 +2579,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
|
|||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1430 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1441 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_oauth2_refresh_token
|
||||
|
@ -2615,7 +2619,7 @@ CREATE TABLE `system_operate_log` (
|
|||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 11964 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 12000 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_operate_log
|
||||
|
@ -5305,7 +5309,7 @@ CREATE TABLE `system_sms_log` (
|
|||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 946 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 947 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_sms_log
|
||||
|
@ -5475,7 +5479,7 @@ INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `c
|
|||
INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (151, '大租户', 126, '土豆大', NULL, 0, 'https://tudou.iocoder.cn', 111, '2023-12-08 00:00:00', 10, '1', '2023-12-02 23:35:05', '1', '2023-12-08 23:39:56', b'0');
|
||||
INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (152, '新租户', 127, '土豆', NULL, 0, 'http://xx.iocoder.cn', 111, '2025-12-31 00:00:00', 50, '1', '2023-12-30 11:43:17', '1', '2023-12-30 11:43:17', b'0');
|
||||
INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (153, '小明的租户', 128, 'xiaoming', '15601691301', 0, 'xiaoming.iocoder.cn', 111, '2025-12-01 00:00:00', 100, '1', '2024-02-27 21:58:25', '1', '2024-02-28 22:53:54', b'0');
|
||||
INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (154, 'hh', 129, 'hh', NULL, 0, 'http://hh.iocoder.cn', 111, '2024-04-30 00:00:00', 123, '1', '2024-03-30 17:52:59', '1', '2024-03-30 17:52:59', b'0');
|
||||
INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (154, 'hh', 129, 'hh', NULL, 0, 'http://hh.iocoder.cn', 111, '2024-04-30 00:00:00', 123, '1', '2024-03-30 17:52:59', '1', '2024-04-03 15:06:42', b'0');
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
|
@ -5619,7 +5623,7 @@ CREATE TABLE `system_users` (
|
|||
-- Records of system_users
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png', 0, '0:0:0:0:0:0:0:1', '2024-03-30 17:18:34', 'admin', '2021-01-05 17:03:47', NULL, '2024-03-30 17:18:34', b'0', 1);
|
||||
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png', 0, '127.0.0.1', '2024-04-03 17:31:00', 'admin', '2021-01-05 17:03:47', NULL, '2024-04-03 17:31:00', b'0', 1);
|
||||
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
|
||||
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-03-18 21:09:04', '', '2021-01-13 23:50:35', NULL, '2024-03-18 21:09:04', b'0', 1);
|
||||
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$KhExCYl7lx6eWWZYKsibKOZ8IBJRyuNuCcEOLQ11RYhJKgHmlSwK.', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-26 07:11:35', '', '2021-01-21 02:13:53', NULL, '2024-03-26 07:11:35', b'0', 1);
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.common.util.spring;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import org.springframework.aop.framework.AdvisedSupport;
|
||||
import org.springframework.aop.framework.AopProxy;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
/**
|
||||
* Spring AOP 工具类
|
||||
*
|
||||
* 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
|
||||
*/
|
||||
public class SpringAopUtils {
|
||||
|
||||
/**
|
||||
* 获取代理的目标对象
|
||||
*
|
||||
* @param proxy 代理对象
|
||||
* @return 目标对象
|
||||
*/
|
||||
public static Object getTarget(Object proxy) throws Exception {
|
||||
// 不是代理对象
|
||||
if (!AopUtils.isAopProxy(proxy)) {
|
||||
return proxy;
|
||||
}
|
||||
// Jdk 代理
|
||||
if (AopUtils.isJdkDynamicProxy(proxy)) {
|
||||
return getJdkDynamicProxyTargetObject(proxy);
|
||||
}
|
||||
// Cglib 代理
|
||||
return getCglibProxyTargetObject(proxy);
|
||||
}
|
||||
|
||||
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
|
||||
Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
|
||||
AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
|
||||
return advisedSupport.getTargetSource().getTarget();
|
||||
}
|
||||
|
||||
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
|
||||
AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
|
||||
AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
|
||||
return advisedSupport.getTargetSource().getTarget();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cn.iocoder.yudao.framework.common.util.spring;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring 工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class SpringUtils extends SpringUtil {
|
||||
|
||||
/**
|
||||
* 是否为生产环境
|
||||
*
|
||||
* @return 是否生产环境
|
||||
*/
|
||||
public static boolean isProd() {
|
||||
String activeProfile = getActiveProfile();
|
||||
return Objects.equals("prod", activeProfile);
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package cn.iocoder.yudao.framework;
|
|
@ -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;
|
||||
|
@ -10,23 +11,26 @@ import cn.iocoder.yudao.framework.web.config.WebProperties;
|
|||
import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
|
||||
import jakarta.servlet.Filter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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 javax.servlet.Filter;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@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 +53,9 @@ public class YudaoApiLogAutoConfiguration {
|
|||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new ApiAccessLogInterceptor());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.annotations;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 访问日志注解
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ApiAccessLog {
|
||||
|
||||
// ========== 开关字段 ==========
|
||||
|
||||
/**
|
||||
* 是否记录访问日志
|
||||
*/
|
||||
boolean enable() default true;
|
||||
/**
|
||||
* 是否记录请求参数
|
||||
*
|
||||
* 默认记录,主要考虑请求数据一般不大。可手动设置为 false 进行关闭
|
||||
*/
|
||||
boolean requestEnable() default true;
|
||||
/**
|
||||
* 是否记录响应结果
|
||||
*
|
||||
* 默认不记录,主要考虑响应数据可能比较大。可手动设置为 true 进行打开
|
||||
*/
|
||||
boolean responseEnable() default false;
|
||||
/**
|
||||
* 敏感参数数组
|
||||
*
|
||||
* 添加后,请求参数、响应结果不会记录该参数
|
||||
*/
|
||||
String[] sanitizeKeys() default {};
|
||||
|
||||
// ========== 模块字段 ==========
|
||||
|
||||
/**
|
||||
* 操作模块
|
||||
*
|
||||
* 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.tags.Tag#name()} 属性
|
||||
*/
|
||||
String operateModule() default "";
|
||||
/**
|
||||
* 操作名
|
||||
*
|
||||
* 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.Operation#summary()} 属性
|
||||
*/
|
||||
String operateName() default "";
|
||||
/**
|
||||
* 操作分类
|
||||
*
|
||||
* 实际并不是数组,因为枚举不能设置 null 作为默认值
|
||||
*/
|
||||
OperateTypeEnum[] operateType() default {};
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 操作日志的操作类型
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OperateTypeEnum {
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
GET(1),
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
CREATE(2),
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
UPDATE(3),
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
DELETE(4),
|
||||
/**
|
||||
* 导出
|
||||
*/
|
||||
EXPORT(5),
|
||||
/**
|
||||
* 导入
|
||||
*/
|
||||
IMPORT(6),
|
||||
/**
|
||||
* 其它
|
||||
*
|
||||
* 在无法归类时,可以选择使用其它。因为还有操作名可以进一步标识
|
||||
*/
|
||||
OTHER(0);
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
|
||||
}
|
|
@ -1,38 +1,56 @@
|
|||
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 jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
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;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
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,166 @@ 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 = RequestMethod.resolve(request.getMethod());
|
||||
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.properties().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
if (ArrayUtil.contains(sanitizeKeys, entry.getKey())
|
||||
|| ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
sanitizeJson(entry.getValue(), sanitizeKeys);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.interceptor;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* API 访问日志 Interceptor
|
||||
*
|
||||
* 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiAccessLogInterceptor implements HandlerInterceptor {
|
||||
|
||||
public static final String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD";
|
||||
|
||||
private static final String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch";
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 记录 HandlerMethod,提供给 ApiAccessLogFilter 使用
|
||||
HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null;
|
||||
if (handlerMethod != null) {
|
||||
request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);
|
||||
}
|
||||
|
||||
// 打印 request 日志
|
||||
if (!SpringUtils.isProd()) {
|
||||
Map<String, String> queryString = ServletUtils.getParamMap(request);
|
||||
String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
|
||||
if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {
|
||||
log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
|
||||
} else {
|
||||
log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
|
||||
StrUtil.nullToDefault(requestBody, queryString.toString()));
|
||||
}
|
||||
// 计时
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 打印 response 日志
|
||||
if (!SpringUtils.isProd()) {
|
||||
StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
|
||||
stopWatch.stop();
|
||||
log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]",
|
||||
request.getRequestURI(), stopWatch.getTotalTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 访问日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class ApiAccessLog {
|
||||
|
||||
/**
|
||||
* 链路追踪编号
|
||||
*/
|
||||
private String traceId;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
@NotNull(message = "应用名不能为空")
|
||||
private String applicationName;
|
||||
|
||||
/**
|
||||
* 请求方法名
|
||||
*/
|
||||
@NotNull(message = "http 请求方法不能为空")
|
||||
private String requestMethod;
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
@NotNull(message = "访问地址不能为空")
|
||||
private String requestUrl;
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
@NotNull(message = "请求参数不能为空")
|
||||
private String requestParams;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotNull(message = "ip 不能为空")
|
||||
private String userIp;
|
||||
/**
|
||||
* 浏览器 UA
|
||||
*/
|
||||
@NotNull(message = "User-Agent 不能为空")
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 开始请求时间
|
||||
*/
|
||||
@NotNull(message = "开始请求时间不能为空")
|
||||
private LocalDateTime beginTime;
|
||||
/**
|
||||
* 结束请求时间
|
||||
*/
|
||||
@NotNull(message = "结束请求时间不能为空")
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 执行时长,单位:毫秒
|
||||
*/
|
||||
@NotNull(message = "执行时长不能为空")
|
||||
private Integer duration;
|
||||
/**
|
||||
* 结果码
|
||||
*/
|
||||
@NotNull(message = "错误码不能为空")
|
||||
private Integer resultCode;
|
||||
/**
|
||||
* 结果提示
|
||||
*/
|
||||
private String resultMsg;
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
|
||||
/**
|
||||
* API 访问日志 Framework Service 接口
|
||||
*
|
||||
|
@ -10,8 +12,8 @@ public interface ApiAccessLogFrameworkService {
|
|||
/**
|
||||
* 创建 API 访问日志
|
||||
*
|
||||
* @param apiAccessLog API 访问日志
|
||||
* @param reqDTO API 访问日志
|
||||
*/
|
||||
void createApiAccessLog(ApiAccessLog apiAccessLog);
|
||||
void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
@ -10,7 +8,7 @@ import org.springframework.scheduling.annotation.Async;
|
|||
/**
|
||||
* API 访问日志 Framework Service 实现类
|
||||
*
|
||||
* 基于 {@link ApiAccessLogApi} 远程服务,记录访问日志
|
||||
* 基于 {@link ApiAccessLogApi} 服务,记录访问日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -21,9 +19,8 @@ public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkSe
|
|||
|
||||
@Override
|
||||
@Async
|
||||
public void createApiAccessLog(ApiAccessLog apiAccessLog) {
|
||||
ApiAccessLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiAccessLog, ApiAccessLogCreateReqDTO.class);
|
||||
apiAccessLogApi.createApiAccessLog(reqDTO).checkError();
|
||||
public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
|
||||
apiAccessLogApi.createApiAccessLog(reqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 错误日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class ApiErrorLog {
|
||||
|
||||
/**
|
||||
* 链路编号
|
||||
*/
|
||||
private String traceId;
|
||||
/**
|
||||
* 账号编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
@NotNull(message = "应用名不能为空")
|
||||
private String applicationName;
|
||||
|
||||
/**
|
||||
* 请求方法名
|
||||
*/
|
||||
@NotNull(message = "http 请求方法不能为空")
|
||||
private String requestMethod;
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
@NotNull(message = "访问地址不能为空")
|
||||
private String requestUrl;
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
@NotNull(message = "请求参数不能为空")
|
||||
private String requestParams;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotNull(message = "ip 不能为空")
|
||||
private String userIp;
|
||||
/**
|
||||
* 浏览器 UA
|
||||
*/
|
||||
@NotNull(message = "User-Agent 不能为空")
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 异常时间
|
||||
*/
|
||||
@NotNull(message = "异常时间不能为空")
|
||||
private LocalDateTime exceptionTime;
|
||||
/**
|
||||
* 异常名
|
||||
*/
|
||||
@NotNull(message = "异常名不能为空")
|
||||
private String exceptionName;
|
||||
/**
|
||||
* 异常发生的类全名
|
||||
*/
|
||||
@NotNull(message = "异常发生的类全名不能为空")
|
||||
private String exceptionClassName;
|
||||
/**
|
||||
* 异常发生的类文件
|
||||
*/
|
||||
@NotNull(message = "异常发生的类文件不能为空")
|
||||
private String exceptionFileName;
|
||||
/**
|
||||
* 异常发生的方法名
|
||||
*/
|
||||
@NotNull(message = "异常发生的方法名不能为空")
|
||||
private String exceptionMethodName;
|
||||
/**
|
||||
* 异常发生的方法所在行
|
||||
*/
|
||||
@NotNull(message = "异常发生的方法所在行不能为空")
|
||||
private Integer exceptionLineNumber;
|
||||
/**
|
||||
* 异常的栈轨迹异常的栈轨迹
|
||||
*/
|
||||
@NotNull(message = "异常的栈轨迹不能为空")
|
||||
private String exceptionStackTrace;
|
||||
/**
|
||||
* 异常导致的根消息
|
||||
*/
|
||||
@NotNull(message = "异常导致的根消息不能为空")
|
||||
private String exceptionRootCauseMessage;
|
||||
/**
|
||||
* 异常导致的消息
|
||||
*/
|
||||
@NotNull(message = "异常导致的消息不能为空")
|
||||
private String exceptionMessage;
|
||||
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
|
||||
/**
|
||||
* API 错误日志 Framework Service 接口
|
||||
*
|
||||
|
@ -10,8 +12,8 @@ public interface ApiErrorLogFrameworkService {
|
|||
/**
|
||||
* 创建 API 错误日志
|
||||
*
|
||||
* @param apiErrorLog API 错误日志
|
||||
* @param reqDTO API 错误日志
|
||||
*/
|
||||
void createApiErrorLog(ApiErrorLog apiErrorLog);
|
||||
void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.apilog.core.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
@ -10,7 +8,7 @@ import org.springframework.scheduling.annotation.Async;
|
|||
/**
|
||||
* API 错误日志 Framework Service 实现类
|
||||
*
|
||||
* 基于 {@link ApiErrorLogApi} 远程服务,记录错误日志
|
||||
* 基于 {@link ApiErrorLogApi} 服务,记录错误日志
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -21,9 +19,8 @@ public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkServ
|
|||
|
||||
@Override
|
||||
@Async
|
||||
public void createApiErrorLog(ApiErrorLog apiErrorLog) {
|
||||
ApiErrorLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiErrorLog, ApiErrorLogCreateReqDTO.class);
|
||||
apiErrorLogApi.createApiErrorLog(reqDTO).checkError();
|
||||
public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
|
||||
apiErrorLogApi.createApiErrorLog(reqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
//package cn.iocoder.yudao.framework.swagger.core;
|
||||
//
|
||||
//import cn.hutool.core.util.ReflectUtil;
|
||||
//import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
//import org.springframework.beans.BeansException;
|
||||
//import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
//import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
//import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
|
||||
//
|
||||
//import java.util.List;
|
||||
//
|
||||
///**
|
||||
// * 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题
|
||||
// * 该问题对应的 issue 为 https://github.com/springfox/springfox/issues/3462
|
||||
// *
|
||||
// * @author 芋道源码
|
||||
// */
|
||||
//public class SpringFoxHandlerProviderBeanPostProcessor implements BeanPostProcessor {
|
||||
//
|
||||
// @Override
|
||||
// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
// if (bean instanceof WebMvcRequestHandlerProvider) {
|
||||
// customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
||||
// }
|
||||
// return bean;
|
||||
// }
|
||||
//
|
||||
// private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
|
||||
// // 移除,只保留 patternParser
|
||||
// List<T> copy = CollectionUtils.filterList(mappings, mapping -> mapping.getPatternParser() == null);
|
||||
// // 添加到 mappings 中
|
||||
// mappings.clear();
|
||||
// mappings.addAll(copy);
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("unchecked")
|
||||
// private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
|
||||
// return (List<RequestMappingInfoHandlerMapping>)
|
||||
// ReflectUtil.getFieldValue(bean, "handlerMappings");
|
||||
// }
|
||||
//
|
||||
//}
|
|
@ -3,8 +3,7 @@ package cn.iocoder.yudao.framework.web.core.handler;
|
|||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLog;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
|
||||
|
@ -12,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
|||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
@ -26,10 +26,10 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -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", JakartaServletUtil.getParamMap(request))
|
||||
.put("body", JakartaServletUtil.getBody(request)).build();
|
||||
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
|
||||
errorLog.setRequestMethod(request.getMethod());
|
||||
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
|
||||
errorLog.setUserIp(ServletUtil.getClientIP(request));
|
||||
errorLog.setUserIp(JakartaServletUtil.getClientIP(request));
|
||||
errorLog.setExceptionTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
|
|
|
@ -81,6 +81,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
|
|
@ -27,9 +27,10 @@ public class ApiAccessLogCreateReqDTO {
|
|||
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/xxx/yyy")
|
||||
@NotNull(message = "访问地址不能为空")
|
||||
private String requestUrl;
|
||||
@Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "请求参数不能为空")
|
||||
@Schema(description = "请求参数")
|
||||
private String requestParams;
|
||||
@Schema(description = "响应结果")
|
||||
private String responseBody;
|
||||
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
|
||||
@NotNull(message = "ip 不能为空")
|
||||
private String userIp;
|
||||
|
@ -37,6 +38,13 @@ public class ApiAccessLogCreateReqDTO {
|
|||
@NotNull(message = "User-Agent 不能为空")
|
||||
private String userAgent;
|
||||
|
||||
@Schema(description = "操作模块", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品模块")
|
||||
private String operateModule;
|
||||
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品新增")
|
||||
private String operateName;
|
||||
@Schema(description = "操作分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer operateType; // 参见 OperateTypeEnum 枚举
|
||||
|
||||
@Schema(description = "开始时间",requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "开始请求时间不能为空")
|
||||
private LocalDateTime beginTime;
|
||||
|
|
|
@ -48,6 +48,10 @@ public class ApiAccessLogRespVO {
|
|||
@ExcelProperty("请求参数")
|
||||
private String requestParams;
|
||||
|
||||
@Schema(description = "响应结果")
|
||||
@ExcelProperty("响应结果")
|
||||
private String responseBody;
|
||||
|
||||
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
|
||||
@ExcelProperty("用户 IP")
|
||||
private String userIp;
|
||||
|
@ -56,6 +60,19 @@ public class ApiAccessLogRespVO {
|
|||
@ExcelProperty("浏览器 UA")
|
||||
private String userAgent;
|
||||
|
||||
@Schema(description = "操作模块", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品模块")
|
||||
@ExcelProperty("操作模块")
|
||||
private String operateModule;
|
||||
|
||||
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建商品")
|
||||
@ExcelProperty("操作名")
|
||||
private String operateName;
|
||||
|
||||
@Schema(description = "操作分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "操作分类", converter = DictConvert.class)
|
||||
@DictFormat(DictTypeConstants.OPERATE_TYPE)
|
||||
private Integer operateType;
|
||||
|
||||
@Schema(description = "开始请求时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("开始请求时间")
|
||||
private LocalDateTime beginTime;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.infra.dal.dataobject.logger;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
@ -70,6 +71,10 @@ public class ApiAccessLogDO extends BaseDO {
|
|||
* body: Quest Body
|
||||
*/
|
||||
private String requestParams;
|
||||
/**
|
||||
* 响应结果
|
||||
*/
|
||||
private String responseBody;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
|
@ -81,6 +86,21 @@ public class ApiAccessLogDO extends BaseDO {
|
|||
|
||||
// ========== 执行相关字段 ==========
|
||||
|
||||
/**
|
||||
* 操作模块
|
||||
*/
|
||||
private String operateModule;
|
||||
/**
|
||||
* 操作名
|
||||
*/
|
||||
private String operateName;
|
||||
/**
|
||||
* 操作分类
|
||||
*
|
||||
* 枚举 {@link OperateTypeEnum}
|
||||
*/
|
||||
private Integer operateType;
|
||||
|
||||
/**
|
||||
* 开始请求时间
|
||||
*/
|
||||
|
@ -93,6 +113,7 @@ public class ApiAccessLogDO extends BaseDO {
|
|||
* 执行时长,单位:毫秒
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 结果码
|
||||
*
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### 消息队列相关 ####################
|
||||
|
|
|
@ -94,8 +94,12 @@ CREATE TABLE IF NOT EXISTS "infra_api_access_log" (
|
|||
"request_method" varchar(16) not null default '',
|
||||
"request_url" varchar(255) not null default '',
|
||||
"request_params" varchar(8000) not null default '',
|
||||
"response_body" varchar(8000) not null default '',
|
||||
"user_ip" varchar(50) not null,
|
||||
"user_agent" varchar(512) not null,
|
||||
`operate_module` varchar(50) NOT NULL,
|
||||
`operate_name` varchar(50) NOT NULL,
|
||||
`operate_type` bigint(4) NOT NULL DEFAULT '0',
|
||||
"begin_time" timestamp not null,
|
||||
"end_time" timestamp not null,
|
||||
"duration" integer not null,
|
||||
|
@ -108,7 +112,7 @@ CREATE TABLE IF NOT EXISTS "infra_api_access_log" (
|
|||
"deleted" bit not null default false,
|
||||
"tenant_id" bigint not null default '0',
|
||||
primary key ("id")
|
||||
) COMMENT 'API 访问日志表';
|
||||
) COMMENT 'API 访问日志表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "infra_api_error_log" (
|
||||
"id" bigint not null GENERATED BY DEFAULT AS IDENTITY,
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
|
|
@ -70,6 +70,11 @@ spring:
|
|||
repositories:
|
||||
enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
|
||||
|
||||
# VO 转换(数据翻译)相关
|
||||
easy-trans:
|
||||
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
|
||||
is-enable-cloud: false # 禁用 TransType.RPC 微服务模式
|
||||
|
||||
--- #################### RPC 远程调用相关配置 ####################
|
||||
|
||||
--- #################### MQ 消息队列相关配置 ####################
|
||||
|
|
|
@ -11,4 +11,4 @@ CREATE TABLE IF NOT EXISTS "report_go_view_project" (
|
|||
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'GoView 项目表';
|
||||
) COMMENT 'GoView 项目表';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.system.controller.admin.notify;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
@ -88,6 +89,7 @@ public class NotifyMessageController {
|
|||
|
||||
@GetMapping("/get-unread-count")
|
||||
@Operation(summary = "获得当前用户的未读站内信数量")
|
||||
@ApiAccessLog(enable = false) // 由于前端会不断轮询该接口,记录日志没有意义
|
||||
public CommonResult<Long> getUnreadNotifyMessageCount() {
|
||||
return success(notifyMessageService.getUnreadNotifyMessageCount(
|
||||
getLoginUserId(), UserTypeEnum.ADMIN.getValue()));
|
||||
|
|
Loading…
Reference in New Issue