Compare commits

..

No commits in common. "d508313565ea69c9d7f9c763bb06c107f8bf4b21" and "20b4329af65559cd30aef6f8dadc85e3c74694ee" have entirely different histories.

2655 changed files with 197069 additions and 515 deletions

View File

@ -17,13 +17,13 @@
<module>yudao-module-infra</module>
<!-- <module>yudao-module-member</module>-->
<!-- <module>yudao-module-bpm</module>-->
<module>yudao-module-report</module>
<!-- <module>yudao-module-report</module>-->
<!-- <module>yudao-module-mp</module>-->
<module>yudao-module-pay</module>
<!-- <module>yudao-module-pay</module>-->
<!-- <module>yudao-module-mall</module>-->
<!-- <module>yudao-module-crm</module>-->
<!-- <module>yudao-module-erp</module>-->
<module>yudao-module-ai</module>
<!-- <module>yudao-module-ai</module>-->
<!-- <module>yudao-module-iot</module>-->
</modules>
@ -32,7 +32,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>2.6.0-SNAPSHOT</revision>
<revision>2.5.0-SNAPSHOT</revision>
<!-- Maven 相关 -->
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>

View File

@ -0,0 +1,598 @@
package liquibase.database.core;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtil;
public class DmDatabase extends AbstractJdbcDatabase {
private static final String PRODUCT_NAME = "DM DBMS";
@Override
protected String getDefaultDatabaseProductName() {
return PRODUCT_NAME;
}
/**
* Is this AbstractDatabase subclass the correct one to use for the given connection.
*
* @param conn
*/
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
/**
* If this database understands the given url, return the default driver class name. Otherwise return null.
*
* @param url
*/
@Override
public String getDefaultDriver(String url) {
if(url.startsWith("jdbc:dm")) {
return "dm.jdbc.driver.DmDriver";
}
return null;
}
/**
* Returns an all-lower-case short name of the product. Used for end-user selecting of database type
* such as the DBMS precondition.
*/
@Override
public String getShortName() {
return "dm";
}
@Override
public Integer getDefaultPort() {
return 5236;
}
/**
* Returns whether this database support initially deferrable columns.
*/
@Override
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
@Override
public boolean supportsTablespaces() {
return true;
}
@Override
public int getPriority() {
return PRIORITY_DEFAULT;
}
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
public static final int ORACLE_12C_MAJOR_VERSION = 12;
private Set<String> reservedWords = new HashSet<>();
private Set<String> userDefinedTypes;
private Map<String, String> savedSessionNlsSettings;
private Boolean canAccessDbaRecycleBin;
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
/**
* Default constructor for an object that represents the Oracle Database DBMS.
*/
public DmDatabase() {
super.unquotedObjectsAreUppercased = true;
//noinspection HardCodedStringLiteral
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
// Setting list of Oracle's native functions
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSDATE"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
//noinspection HardCodedStringLiteral
super.sequenceNextValueFunction = "%s.nextval";
//noinspection HardCodedStringLiteral
super.sequenceCurrentValueFunction = "%s.currval";
}
private void tryProxySession(final String url, final Connection con) {
Matcher m = PROXY_USER.matcher(url);
if (m.matches()) {
Properties props = new Properties();
props.put("PROXY_USER_NAME", m.group(1));
try {
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
method.setAccessible(true);
method.invoke(con, 1, props);
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
}
}
}
@Override
public int getDatabaseMajorVersion() throws DatabaseException {
if (databaseMajorVersion == null) {
return super.getDatabaseMajorVersion();
} else {
return databaseMajorVersion;
}
}
@Override
public int getDatabaseMinorVersion() throws DatabaseException {
if (databaseMinorVersion == null) {
return super.getDatabaseMinorVersion();
} else {
return databaseMinorVersion;
}
}
@Override
public String getJdbcCatalogName(CatalogAndSchema schema) {
return null;
}
@Override
public String getJdbcSchemaName(CatalogAndSchema schema) {
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
}
@Override
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
if (StringUtil.isEmpty(generationType)) {
return super.getAutoIncrementClause();
}
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
String generationStrategy = generationType;
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
generationStrategy += " ON NULL";
}
return String.format(autoIncrementClause, generationStrategy);
}
@Override
public String generatePrimaryKeyName(String tableName) {
if (tableName.length() > 27) {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
} else {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US);
}
}
@Override
public boolean isReservedWord(String objectName) {
return reservedWords.contains(objectName.toUpperCase());
}
@Override
public boolean supportsSequences() {
return true;
}
/**
* Oracle supports catalogs in liquibase terms
*
* @return false
*/
@Override
public boolean supportsSchemas() {
return false;
}
@Override
protected String getConnectionCatalogName() throws DatabaseException {
if (getConnection() instanceof OfflineConnection) {
return getConnection().getCatalog();
}
try {
//noinspection HardCodedStringLiteral
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
} catch (Exception e) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
}
return null;
}
@Override
public String getDefaultCatalogName() {//NOPMD
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
}
/**
* <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
*
* <p>Convert an ISO8601 date string to one of the following results:
* to_date('1995-05-23', 'YYYY-MM-DD')
* to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
* <p>
* Implementation restriction:<br>
* Currently, only the following subsets of ISO8601 are supported:<br>
* <ul>
* <li>YYYY-MM-DD</li>
* <li>YYYY-MM-DDThh:mm:ss</li>
* </ul>
*/
@Override
public String getDateLiteral(String isoDate) {
String normalLiteral = super.getDateLiteral(isoDate);
if (isDateOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
} else if (isTimeOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
} else if (isTimestamp(isoDate)) {
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
} else if (isDateTime(isoDate)) {
int seppos = normalLiteral.lastIndexOf('.');
if (seppos != -1) {
normalLiteral = normalLiteral.substring(0, seppos) + "'";
}
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
}
return "UNSUPPORTED:" + isoDate;
}
@Override
public boolean isSystemObject(DatabaseObject example) {
if (example == null) {
return false;
}
if (this.isLiquibaseObject(example)) {
return false;
}
if (example instanceof Schema) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
return true;
}
} else if (isSystemObject(example.getSchema())) {
return true;
}
if (example instanceof Catalog) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
return true;
}
} else if (example.getName() != null) {
//noinspection HardCodedStringLiteral
if (example.getName().startsWith("BIN$")) { //oracle deleted table
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
if (!filteredInOriginalQuery) {
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
}
if (filteredInOriginalQuery) {
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
liquibase.statement.UniqueConstraint));
} else {
return true;
}
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("DR$")) { //oracle index tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
return true;
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
return true;
} else //noinspection HardCodedStringLiteral
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
return true;
} else //noinspection HardCodedStringLiteral
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("USLOG$")) { //for update materialized view
return true;
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
return true;
}
}
return super.isSystemObject(example);
}
@Override
public boolean supportsAutoIncrement() {
// Oracle supports Identity beginning with version 12c
boolean isAutoIncrementSupported = false;
try {
if (getDatabaseMajorVersion() >= 12) {
isAutoIncrementSupported = true;
}
// Returning true will generate create table command with 'IDENTITY' clause, example:
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
// While returning false will continue to generate create table command without 'IDENTITY' clause, example:
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
} catch (DatabaseException ex) {
isAutoIncrementSupported = false;
}
return isAutoIncrementSupported;
}
// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
//
// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
//
// UniqueConstraint constraint = null;
// for (Map map : maps) {
// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
// returnSet.add(constraint);
// Table table = new Table((String) map.get("TABLE_NAME"));
// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
// }
// }
// if (constraint != null) {
// returnSet.add(constraint);
// }
//
// return returnSet;
// }
@Override
public boolean supportsRestrictForeignKeys() {
return false;
}
@Override
public int getDataTypeMaxParameters(String dataTypeName) {
//noinspection HardCodedStringLiteral
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
return 0;
}
//noinspection HardCodedStringLiteral
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
return 0;
}
return super.getDataTypeMaxParameters(dataTypeName);
}
public String getSystemTableWhereClause(String tableNameColumn) {
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
"AQ$",
"DR$",
"SYS_IOT_OVER",
"MLOG$_",
"RUPD$_",
"WM$_",
"ISEQ$$_",
"USLOG$",
"SYS_FBA"));
for (int i = 0;i<clauses.size(); i++) {
clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
}
return "("+ StringUtil.join(clauses, " AND ") + ")";
}
@Override
public boolean jdbcCallsCatalogsSchemas() {
return true;
}
public Set<String> getUserDefinedTypes() {
if (userDefinedTypes == null) {
userDefinedTypes = new HashSet<>();
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
try {
try {
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
}
} catch (DatabaseException e) {
//ignore error
}
}
}
return userDefinedTypes;
}
@Override
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
//noinspection HardCodedStringLiteral
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
return databaseFunction.toString();
}
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
SequenceCurrentValueFunction)) {
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
}
return super.generateDatabaseFunctionValue(databaseFunction);
}
@Override
public ValidationErrors validate() {
ValidationErrors errors = super.validate();
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
return errors;
}
if (!canAccessDbaRecycleBin()) {
errors.addWarning(getDbaRecycleBinWarning());
}
return errors;
}
public String getDbaRecycleBinWarning() {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
// HardCodedStringLiteral
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
" issue.\n" +
"\n" +
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
"\n" +
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
}
public boolean canAccessDbaRecycleBin() {
if (canAccessDbaRecycleBin == null) {
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
return false;
}
Statement statement = null;
try {
statement = ((JdbcConnection) connection).createStatement();
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
this.canAccessDbaRecycleBin = true;
} catch (Exception e) {
//noinspection HardCodedStringLiteral
if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
this.canAccessDbaRecycleBin = false;
} else {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
this.canAccessDbaRecycleBin = false;
}
} finally {
JdbcUtils.close(null, statement);
}
}
return canAccessDbaRecycleBin;
}
@Override
public boolean supportsNotNullConstraintNames() {
return true;
}
/**
* Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
* the following form (case-insensitive comparison):
* 1st character: A-Z
* 2..n characters: A-Z0-9$_#
* The maximum length of an identifier differs by Oracle version and object type.
*/
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
if ((identifier == null) || (identifier.length() < 1))
return false;
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
return false;
/*
* @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
* we do know seem to be supported as 12cR2 long identifiers, so:
*/
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
}
/**
* Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
* is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
* object types).
*
* @return the maximum length of an object identifier, in bytes
*/
public int getIdentifierMaximumLength() {
try {
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
return SHORT_IDENTIFIERS_LENGTH;
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
return SHORT_IDENTIFIERS_LENGTH;
} else {
return LONG_IDENTIFIERS_LEGNTH;
}
} catch (DatabaseException ex) {
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
}
}
}

View File

@ -0,0 +1,165 @@
package liquibase.datatype.core;
import liquibase.change.core.LoadDataChange;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
import java.util.Locale;
import java.util.regex.Pattern;
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
public class BooleanType extends LiquibaseDataType {
@Override
public DatabaseDataType toDatabaseDataType(Database database) {
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
if ((database instanceof Firebird3Database)) {
return new DatabaseDataType("BOOLEAN");
}
if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
return new DatabaseDataType("SMALLINT");
} else if (database instanceof MSSQLDatabase) {
return new DatabaseDataType(database.escapeDataTypeName("bit"));
} else if (database instanceof MySQLDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
return new DatabaseDataType("BIT", 1);
} else if (database instanceof OracleDatabase) {
return new DatabaseDataType("NUMBER", 1);
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
return new DatabaseDataType("BIT");
} else if (database instanceof DerbyDatabase) {
if (((DerbyDatabase) database).supportsBooleanDataType()) {
return new DatabaseDataType("BOOLEAN");
} else {
return new DatabaseDataType("SMALLINT");
}
} else if (database instanceof DB2Database) {
if (((DB2Database) database).supportsBooleanDataType())
return new DatabaseDataType("BOOLEAN");
else
return new DatabaseDataType("SMALLINT");
} else if (database instanceof HsqlDatabase) {
return new DatabaseDataType("BOOLEAN");
} else if (database instanceof PostgresDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
} else if (database instanceof DmDatabase) { // dhb52: DM Support
return new DatabaseDataType("bit");
}
return super.toDatabaseDataType(database);
}
@Override
public String objectToSql(Object value, Database database) {
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
return null;
}
String returnValue;
if (value instanceof String) {
value = ((String) value).replaceAll("'", "");
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getTrueBooleanValue(database);
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getFalseBooleanValue(database);
} else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
returnValue = "b'"
+ value.toString()
.replace("b", "")
.replace("\"", "")
.replace("::it", "")
+ "'::\"bit\"";
} else {
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
}
} else if (value instanceof Long) {
if (Long.valueOf(1).equals(value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof Number) {
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof DatabaseFunction) {
return value.toString();
} else if (value instanceof Boolean) {
if (((Boolean) value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else {
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
}
return returnValue;
}
protected boolean isNumericBoolean(Database database) {
if (database instanceof Firebird3Database) {
return false;
}
if (database instanceof DerbyDatabase) {
return !((DerbyDatabase) database).supportsBooleanDataType();
} else if (database instanceof DB2Database) {
return !((DB2Database) database).supportsBooleanDataType();
}
return (database instanceof Db2zDatabase)
|| (database instanceof FirebirdDatabase)
|| (database instanceof MSSQLDatabase)
|| (database instanceof MySQLDatabase)
|| (database instanceof OracleDatabase)
|| (database instanceof SQLiteDatabase)
|| (database instanceof SybaseASADatabase)
|| (database instanceof SybaseDatabase)
|| (database instanceof DmDatabase); // dhb52: DM Support
}
/**
* The database-specific value to use for "false" "boolean" columns.
*/
public String getFalseBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "0";
}
if (database instanceof InformixDatabase) {
return "'f'";
}
return "FALSE";
}
/**
* The database-specific value to use for "true" "boolean" columns.
*/
public String getTrueBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "1";
}
if (database instanceof InformixDatabase) {
return "'t'";
}
return "TRUE";
}
@Override
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
}
}

View File

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

View File

@ -0,0 +1,21 @@
liquibase.database.core.CockroachDatabase
liquibase.database.core.DB2Database
liquibase.database.core.Db2zDatabase
liquibase.database.core.DerbyDatabase
liquibase.database.core.Firebird3Database
liquibase.database.core.FirebirdDatabase
liquibase.database.core.H2Database
liquibase.database.core.HsqlDatabase
liquibase.database.core.InformixDatabase
liquibase.database.core.Ingres9Database
liquibase.database.core.MSSQLDatabase
liquibase.database.core.MariaDBDatabase
liquibase.database.core.MockDatabase
liquibase.database.core.MySQLDatabase
liquibase.database.core.OracleDatabase
liquibase.database.core.PostgresDatabase
liquibase.database.core.SQLiteDatabase
liquibase.database.core.SybaseASADatabase
liquibase.database.core.SybaseDatabase
liquibase.database.core.DmDatabase
liquibase.database.core.UnsupportedDatabase

View File

@ -14,7 +14,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>2.6.0-SNAPSHOT</revision>
<revision>2.5.0-SNAPSHOT</revision>
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<!-- 统一依赖管理 -->
<spring.boot.version>3.4.5</spring.boot.version>
@ -377,6 +377,14 @@
<version>${spring-boot-admin.version}</version>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<version>${revision}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>

View File

@ -23,6 +23,7 @@
<module>yudao-spring-boot-starter-mq</module>
<module>yudao-spring-boot-starter-excel</module>
<module>yudao-spring-boot-starter-test</module>
<module>yudao-spring-boot-starter-biz-tenant</module>
<module>yudao-spring-boot-starter-biz-data-permission</module>

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.framework.common.util.collection;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link CollectionUtils} 的单元测试
*/
public class CollectionUtilsTest {
@Data
@AllArgsConstructor
private static class Dog {
private Integer id;
private String name;
private String code;
}
@Test
public void testDiffList() {
// 准备参数
Collection<Dog> oldList = Arrays.asList(
new Dog(1, "花花", "hh"),
new Dog(2, "旺财", "wc")
);
Collection<Dog> newList = Arrays.asList(
new Dog(null, "花花2", "hh"),
new Dog(null, "小白", "xb")
);
BiFunction<Dog, Dog, Boolean> sameFunc = (oldObj, newObj) -> {
boolean same = oldObj.getCode().equals(newObj.getCode());
// 如果相等的情况下需要设置下 id后续好更新
if (same) {
newObj.setId(oldObj.getId());
}
return same;
};
// 调用
List<List<Dog>> result = CollectionUtils.diffList(oldList, newList, sameFunc);
// 断言
assertEquals(result.size(), 3);
// 断言 create
assertEquals(result.get(0).size(), 1);
assertEquals(result.get(0).get(0), new Dog(null, "小白", "xb"));
// 断言 update
assertEquals(result.get(1).size(), 1);
assertEquals(result.get(1).get(0), new Dog(1, "花花2", "hh"));
// 断言 delete
assertEquals(result.get(2).size(), 1);
assertEquals(result.get(2).get(0), new Dog(2, "旺财", "wc"));
}
}

View File

@ -33,6 +33,13 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,108 @@
package cn.iocoder.yudao.framework.datapermission.core.aop;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
/**
* {@link DataPermissionAnnotationInterceptor} 的单元测试
*
* @author 芋道源码
*/
public class DataPermissionAnnotationInterceptorTest extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionAnnotationInterceptor interceptor;
@Mock
private MethodInvocation methodInvocation;
@BeforeEach
public void setUp() {
interceptor.getDataPermissionCache().clear();
}
@Test // @DataPermission 注解
public void testInvoke_none() throws Throwable {
// 参数
mockMethodInvocation(TestNone.class);
// 调用
Object result = interceptor.invoke(methodInvocation);
// 断言
assertEquals("none", result);
assertEquals(1, interceptor.getDataPermissionCache().size());
assertTrue(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
}
@Test // Method 上有 @DataPermission 注解
public void testInvoke_method() throws Throwable {
// 参数
mockMethodInvocation(TestMethod.class);
// 调用
Object result = interceptor.invoke(methodInvocation);
// 断言
assertEquals("method", result);
assertEquals(1, interceptor.getDataPermissionCache().size());
assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
}
@Test // Class 上有 @DataPermission 注解
public void testInvoke_class() throws Throwable {
// 参数
mockMethodInvocation(TestClass.class);
// 调用
Object result = interceptor.invoke(methodInvocation);
// 断言
assertEquals("class", result);
assertEquals(1, interceptor.getDataPermissionCache().size());
assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
}
private void mockMethodInvocation(Class<?> clazz) throws Throwable {
Object targetObject = clazz.newInstance();
Method method = targetObject.getClass().getMethod("echo");
when(methodInvocation.getThis()).thenReturn(targetObject);
when(methodInvocation.getMethod()).thenReturn(method);
when(methodInvocation.proceed()).then(invocationOnMock -> method.invoke(targetObject));
}
static class TestMethod {
@DataPermission(enable = false)
public String echo() {
return "method";
}
}
@DataPermission(enable = false)
static class TestClass {
public String echo() {
return "class";
}
}
static class TestNone {
public String echo() {
return "none";
}
}
}

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.framework.datapermission.core.aop;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mock;
/**
* {@link DataPermissionContextHolder} 的单元测试
*
* @author 芋道源码
*/
class DataPermissionContextHolderTest {
@BeforeEach
public void setUp() {
DataPermissionContextHolder.clear();
}
@Test
public void testGet() {
// mock 方法
DataPermission dataPermission01 = mock(DataPermission.class);
DataPermissionContextHolder.add(dataPermission01);
DataPermission dataPermission02 = mock(DataPermission.class);
DataPermissionContextHolder.add(dataPermission02);
// 调用
DataPermission result = DataPermissionContextHolder.get();
// 断言
assertSame(result, dataPermission02);
}
@Test
public void testPush() {
// 调用
DataPermission dataPermission01 = mock(DataPermission.class);
DataPermissionContextHolder.add(dataPermission01);
DataPermission dataPermission02 = mock(DataPermission.class);
DataPermissionContextHolder.add(dataPermission02);
// 断言
DataPermission first = DataPermissionContextHolder.getAll().get(0);
DataPermission second = DataPermissionContextHolder.getAll().get(1);
assertSame(dataPermission01, first);
assertSame(dataPermission02, second);
}
@Test
public void testRemove() {
// mock 方法
DataPermission dataPermission01 = mock(DataPermission.class);
DataPermissionContextHolder.add(dataPermission01);
DataPermission dataPermission02 = mock(DataPermission.class);
DataPermissionContextHolder.add(dataPermission02);
// 调用
DataPermission result = DataPermissionContextHolder.remove();
// 断言
assertSame(result, dataPermission02);
assertEquals(1, DataPermissionContextHolder.getAll().size());
}
}

View File

@ -0,0 +1,540 @@
package cn.iocoder.yudao.framework.datapermission.core.db;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Arrays;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
* {@link DataPermissionRuleHandler} 的单元测试
* 主要复用了 MyBatis Plus TenantLineInnerInterceptorTest 的单元测试
* 不过它的单元测试不是很规范考虑到是复用的所以暂时不进行修改~
*
* @author 芋道源码
*/
public class DataPermissionRuleHandlerTest extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionRuleHandler handler;
@Mock
private DataPermissionRuleFactory ruleFactory;
private DataPermissionInterceptor interceptor;
@BeforeEach
public void setUp() {
interceptor = new DataPermissionInterceptor(handler);
// 租户的数据权限规则
DataPermissionRule tenantRule = new DataPermissionRule() {
private static final String COLUMN = "tenant_id";
@Override
public Set<String> getTableNames() {
return asSet("entity", "entity1", "entity2", "entity3", "t1", "t2", "sys_dict_item", // 支持 MyBatis Plus 的单元测试
"t_user", "t_role"); // 满足自己的单元测试
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
LongValue value = new LongValue(1L);
return new EqualsTo(column, value);
}
};
// 部门的数据权限规则
DataPermissionRule deptRule = new DataPermissionRule() {
private static final String COLUMN = "dept_id";
@Override
public Set<String> getTableNames() {
return asSet("t_user"); // 满足自己的单元测试
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
ExpressionList<LongValue> values = new ExpressionList<>(new LongValue(10L),
new LongValue(20L));
return new InExpression(column, new ParenthesedExpressionList((values)));
}
};
// 设置到上下文
when(ruleFactory.getDataPermissionRule(any())).thenReturn(Arrays.asList(tenantRule, deptRule));
}
@Test
void delete() {
assertSql("delete from entity where id = ?",
"DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1");
}
@Test
void update() {
assertSql("update entity set name = ? where id = ?",
"UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1");
}
@Test
void selectSingle() {
// 单表
assertSql("select * from entity where id = ?",
"SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1");
assertSql("select * from entity where id = ? or name = ?",
"SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
assertSql("SELECT * FROM entity WHERE (id = ? OR name = ?)",
"SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1");
/* not */
assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
"SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1");
}
@Test
void selectSubSelectIn() {
/* in */
assertSql("SELECT * FROM entity e WHERE e.id IN (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id IN (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
// 在最前
assertSql("SELECT * FROM entity e WHERE e.id IN " +
"(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?",
"SELECT * FROM entity e WHERE e.id IN " +
"(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1");
// 在最后
assertSql("SELECT * FROM entity e WHERE e.id = ? and e.id IN " +
"(select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id = ? AND e.id IN " +
"(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
// 在中间
assertSql("SELECT * FROM entity e WHERE e.id = ? and e.id IN " +
"(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?",
"SELECT * FROM entity e WHERE e.id = ? AND e.id IN " +
"(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1");
}
@Test
void selectSubSelectEq() {
/* = */
assertSql("SELECT * FROM entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
}
@Test
void selectSubSelectInnerNotEq() {
/* inner not = */
assertSql("SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?))",
"SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1)) AND e.tenant_id = 1");
assertSql("SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?) and e.id = ?)",
"SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ?) AND e.tenant_id = 1");
}
@Test
void selectSubSelectExists() {
/* EXISTS */
assertSql("SELECT * FROM entity e WHERE EXISTS (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
/* NOT EXISTS */
assertSql("SELECT * FROM entity e WHERE NOT EXISTS (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE NOT EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
}
@Test
void selectSubSelect() {
/* >= */
assertSql("SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
/* <= */
assertSql("SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
/* <> */
assertSql("SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)",
"SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1");
}
@Test
void selectFromSelect() {
assertSql("SELECT * FROM (select e.id from entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?))",
"SELECT * FROM (SELECT e.id FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1)");
}
@Test
void selectBodySubSelect() {
assertSql("select t1.col1,(select t2.col2 from t2 t2 where t1.col1=t2.col1) from t1 t1",
"SELECT t1.col1, (SELECT t2.col2 FROM t2 t2 WHERE t1.col1 = t2.col1 AND t2.tenant_id = 1) FROM t1 t1 WHERE t1.tenant_id = 1");
}
@Test
void selectLeftJoin() {
// left join
assertSql("SELECT * FROM entity e " +
"left join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"left join entity1 e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"left join entity1 e1 on e1.id = e.id " +
"left join entity2 e2 on e1.id = e2.id",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
"WHERE e.tenant_id = 1");
}
@Test
void selectRightJoin() {
// right join
assertSql("SELECT * FROM entity e " +
"right join entity1 e1 on e1.id = e.id",
"SELECT * FROM entity e " +
"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
"WHERE e1.tenant_id = 1");
assertSql("SELECT * FROM with_as_1 e " +
"right join entity1 e1 on e1.id = e.id",
"SELECT * FROM with_as_1 e " +
"RIGHT JOIN entity1 e1 ON e1.id = e.id " +
"WHERE e1.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"right join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM entity e " +
"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"right join entity1 e1 on e1.id = e.id " +
"right join entity2 e2 on e1.id = e2.id ",
"SELECT * FROM entity e " +
"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " +
"WHERE e2.tenant_id = 1");
}
@Test
void selectMixJoin() {
assertSql("SELECT * FROM entity e " +
"right join entity1 e1 on e1.id = e.id " +
"left join entity2 e2 on e1.id = e2.id",
"SELECT * FROM entity e " +
"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " +
"LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " +
"WHERE e1.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"left join entity1 e1 on e1.id = e.id " +
"right join entity2 e2 on e1.id = e2.id",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 " +
"WHERE e2.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"left join entity1 e1 on e1.id = e.id " +
"inner join entity2 e2 on e1.id = e2.id",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1");
}
@Test
void selectJoinSubSelect() {
assertSql("select * from (select * from entity) e1 " +
"left join entity2 e2 on e1.id = e2.id",
"SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 " +
"LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1");
assertSql("select * from entity1 e1 " +
"left join (select * from entity2) e2 " +
"on e1.id = e2.id",
"SELECT * FROM entity1 e1 " +
"LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 " +
"ON e1.id = e2.id " +
"WHERE e1.tenant_id = 1");
}
@Test
void selectSubJoin() {
assertSql("select * FROM " +
"(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)",
"SELECT * FROM " +
"(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
"WHERE e2.tenant_id = 1");
assertSql("select * FROM " +
"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)",
"SELECT * FROM " +
"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
"WHERE e1.tenant_id = 1");
assertSql("select * FROM " +
"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) " +
"right join entity3 e3 on e1.id = e3.id",
"SELECT * FROM " +
"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
"RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 " +
"WHERE e3.tenant_id = 1");
assertSql("select * FROM entity e " +
"LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) " +
"on e.id = e2.id",
"SELECT * FROM entity e " +
"LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " +
"ON e.id = e2.id AND e2.tenant_id = 1 " +
"WHERE e.tenant_id = 1");
assertSql("select * FROM entity e " +
"LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
"on e.id = e2.id",
"SELECT * FROM entity e " +
"LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
"ON e.id = e2.id AND e1.tenant_id = 1 " +
"WHERE e.tenant_id = 1");
assertSql("select * FROM entity e " +
"RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " +
"on e.id = e2.id",
"SELECT * FROM entity e " +
"RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " +
"ON e.id = e2.id AND e.tenant_id = 1 " +
"WHERE e1.tenant_id = 1");
}
@Test
void selectLeftJoinMultipleTrailingOn() {
// 多个 on 尾缀的
assertSql("SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 " +
"LEFT JOIN entity2 e2 ON e2.id = e1.id " +
"ON e1.id = e.id " +
"WHERE (e.id = ? OR e.NAME = ?)",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 " +
"LEFT JOIN entity2 e2 ON e2.id = e1.id AND e2.tenant_id = 1 " +
"ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1");
assertSql("SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 " +
"LEFT JOIN with_as_A e2 ON e2.id = e1.id " +
"ON e1.id = e.id " +
"WHERE (e.id = ? OR e.NAME = ?)",
"SELECT * FROM entity e " +
"LEFT JOIN entity1 e1 " +
"LEFT JOIN with_as_A e2 ON e2.id = e1.id " +
"ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1");
}
@Test
void selectInnerJoin() {
// inner join
assertSql("SELECT * FROM entity e " +
"inner join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM entity e " +
"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
"WHERE e.id = ? OR e.name = ?");
assertSql("SELECT * FROM entity e " +
"inner join entity1 e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM entity e " +
"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?)");
// 隐式内连接
assertSql("SELECT * FROM entity,entity1 " +
"WHERE entity.id = entity1.id",
"SELECT * FROM entity, entity1 " +
"WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
// 隐式内连接
assertSql("SELECT * FROM entity a, with_as_entity1 b " +
"WHERE a.id = b.id",
"SELECT * FROM entity a, with_as_entity1 b " +
"WHERE a.id = b.id AND a.tenant_id = 1");
assertSql("SELECT * FROM with_as_entity a, with_as_entity1 b " +
"WHERE a.id = b.id",
"SELECT * FROM with_as_entity a, with_as_entity1 b " +
"WHERE a.id = b.id");
// SubJoin with 隐式内连接
assertSql("SELECT * FROM (entity,entity1) " +
"WHERE entity.id = entity1.id",
"SELECT * FROM (entity, entity1) " +
"WHERE entity.id = entity1.id " +
"AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
assertSql("SELECT * FROM ((entity,entity1),entity2) " +
"WHERE entity.id = entity1.id and entity.id = entity2.id",
"SELECT * FROM ((entity, entity1), entity2) " +
"WHERE entity.id = entity1.id AND entity.id = entity2.id " +
"AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
assertSql("SELECT * FROM (entity,(entity1,entity2)) " +
"WHERE entity.id = entity1.id and entity.id = entity2.id",
"SELECT * FROM (entity, (entity1, entity2)) " +
"WHERE entity.id = entity1.id AND entity.id = entity2.id " +
"AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1");
// 沙雕的括号写法
assertSql("SELECT * FROM (((entity,entity1))) " +
"WHERE entity.id = entity1.id",
"SELECT * FROM (((entity, entity1))) " +
"WHERE entity.id = entity1.id " +
"AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
}
@Test
void selectWithAs() {
assertSql("with with_as_A as (select * from entity) select * from with_as_A",
"WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A");
}
@Test
void selectIgnoreTable() {
assertSql(" SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)",
"SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)");
}
private void assertSql(String sql, String targetSql) {
assertEquals(targetSql, interceptor.parserSingle(sql, null));
}
// ========== 额外的测试 ==========
@Test
public void testSelectSingle() {
// 单表
assertSql("select * from t_user where id = ?",
"SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
assertSql("select * from t_user where id = ? or name = ?",
"SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
assertSql("SELECT * FROM t_user WHERE (id = ? OR name = ?)",
"SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
/* not */
assertSql("SELECT * FROM t_user WHERE not (id = ? OR name = ?)",
"SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)");
}
@Test
public void testSelectLeftJoin() {
// left join
assertSql("SELECT * FROM t_user e " +
"left join t_role e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM t_user e " +
"LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
// 条件 e.id = ? OR e.name = ? 带括号
assertSql("SELECT * FROM t_user e " +
"left join t_role e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM t_user e " +
"LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)");
}
@Test
public void testSelectRightJoin() {
// right join
assertSql("SELECT * FROM t_user e " +
"right join t_role e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM t_user e " +
"RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
"WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
// 条件 e.id = ? OR e.name = ? 带括号
assertSql("SELECT * FROM t_user e " +
"right join t_role e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM t_user e " +
"RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " +
"WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1");
}
@Test
public void testSelectInnerJoin() {
// inner join
assertSql("SELECT * FROM t_user e " +
"inner join entity1 e1 on e1.id = e.id " +
"WHERE e.id = ? OR e.name = ?",
"SELECT * FROM t_user e " +
"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
"WHERE e.id = ? OR e.name = ?");
// 条件 e.id = ? OR e.name = ? 带括号
assertSql("SELECT * FROM t_user e " +
"inner join entity1 e1 on e1.id = e.id " +
"WHERE (e.id = ? OR e.name = ?)",
"SELECT * FROM t_user e " +
"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " +
"WHERE (e.id = ? OR e.name = ?)");
// 没有 On inner join
assertSql("SELECT * FROM entity,entity1 " +
"WHERE entity.id = entity1.id",
"SELECT * FROM entity, entity1 " +
"WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1");
}
}

View File

@ -0,0 +1,145 @@
package cn.iocoder.yudao.framework.datapermission.core.rule;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.springframework.core.annotation.AnnotationUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link DataPermissionRuleFactoryImpl} 单元测试
*
* @author 芋道源码
*/
class DataPermissionRuleFactoryImplTest extends BaseMockitoUnitTest {
@InjectMocks
private DataPermissionRuleFactoryImpl dataPermissionRuleFactory;
@Spy
private List<DataPermissionRule> rules = Arrays.asList(new DataPermissionRule01(),
new DataPermissionRule02());
@BeforeEach
public void setUp() {
DataPermissionContextHolder.clear();
}
@Test
public void testGetDataPermissionRule_02() {
// 准备参数
String mappedStatementId = randomString();
// 调用
List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);
// 断言
assertSame(rules, result);
}
@Test
public void testGetDataPermissionRule_03() {
// 准备参数
String mappedStatementId = randomString();
// mock 方法
DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass03.class, DataPermission.class));
// 调用
List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);
// 断言
assertTrue(result.isEmpty());
}
@Test
public void testGetDataPermissionRule_04() {
// 准备参数
String mappedStatementId = randomString();
// mock 方法
DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass04.class, DataPermission.class));
// 调用
List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);
// 断言
assertEquals(1, result.size());
assertEquals(DataPermissionRule01.class, result.get(0).getClass());
}
@Test
public void testGetDataPermissionRule_05() {
// 准备参数
String mappedStatementId = randomString();
// mock 方法
DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass05.class, DataPermission.class));
// 调用
List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);
// 断言
assertEquals(1, result.size());
assertEquals(DataPermissionRule02.class, result.get(0).getClass());
}
@Test
public void testGetDataPermissionRule_06() {
// 准备参数
String mappedStatementId = randomString();
// mock 方法
DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass06.class, DataPermission.class));
// 调用
List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);
// 断言
assertSame(rules, result);
}
@DataPermission(enable = false)
static class TestClass03 {}
@DataPermission(includeRules = DataPermissionRule01.class)
static class TestClass04 {}
@DataPermission(excludeRules = DataPermissionRule01.class)
static class TestClass05 {}
@DataPermission
static class TestClass06 {}
static class DataPermissionRule01 implements DataPermissionRule {
@Override
public Set<String> getTableNames() {
return null;
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
return null;
}
}
static class DataPermissionRule02 implements DataPermissionRule {
@Override
public Set<String> getTableNames() {
return null;
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
return null;
}
}
}

View File

@ -0,0 +1,238 @@
package cn.iocoder.yudao.framework.datapermission.core.rule.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.util.Map;
import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule.EXPRESSION_NULL;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* {@link DeptDataPermissionRule} 的单元测试
*
* @author 芋道源码
*/
class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
@InjectMocks
private DeptDataPermissionRule rule;
@Mock
private PermissionCommonApi permissionApi;
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
// 清空 rule
rule.getTableNames().clear();
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear();
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear();
}
@Test // LoginUser
public void testGetExpression_noLoginUser() {
// 准备参数
String tableName = randomString();
Alias tableAlias = new Alias(randomString());
// mock 方法
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertNull(expression);
}
@Test // 无数据权限时
public void testGetExpression_noDeptDataPermission() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法permissionApi 返回 null
when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(null);
// 调用
NullPointerException exception = assertThrows(NullPointerException.class,
() -> rule.getExpression(tableName, tableAlias));
// 断言
assertEquals("LoginUser(1) Table(t_user/u) 未返回数据权限", exception.getMessage());
}
}
@Test // 全部数据权限
public void testGetExpression_allDeptDataPermission() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法LoginUser
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法DeptDataPermissionRespDTO
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertNull(expression);
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}
@Test // 即不能查看部门又不能查看自己则说明 100% 无权限
public void testGetExpression_noDept_noSelf() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法LoginUser
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法DeptDataPermissionRespDTO
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO();
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertEquals("null = null", expression.toString());
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}
@Test // 拼接 Dept User 的条件字段都不符合
public void testGetExpression_noDeptColumn_noSelfColumn() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法LoginUser
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法DeptDataPermissionRespDTO
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertSame(EXPRESSION_NULL, expression);
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}
@Test // 拼接 Dept User 的条件self 符合
public void testGetExpression_noDeptColumn_yesSelfColumn() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法LoginUser
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法DeptDataPermissionRespDTO
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setSelf(true);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
// 添加 user 字段配置
rule.addUserColumn("t_user", "id");
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertEquals("u.id = 1", expression.toString());
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}
@Test // 拼接 Dept User 的条件dept 符合
public void testGetExpression_yesDeptColumn_noSelfColumn() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法LoginUser
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法DeptDataPermissionRespDTO
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
// 添加 dept 字段配置
rule.addDeptColumn("t_user", "dept_id");
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertEquals("u.dept_id IN (10, 20)", expression.toString());
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}
@Test // 拼接 Dept User 的条件dept + self 符合
public void testGetExpression_yesDeptColumn_yesSelfColumn() {
try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock
= mockStatic(SecurityFrameworkUtils.class)) {
// 准备参数
String tableName = "t_user";
Alias tableAlias = new Alias("u");
// mock 方法LoginUser
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法DeptDataPermissionRespDTO
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);
// 添加 user 字段配置
rule.addUserColumn("t_user", "id");
// 添加 dept 字段配置
rule.addDeptColumn("t_user", "dept_id");
// 调用
Expression expression = rule.getExpression(tableName, tableAlias);
// 断言
assertEquals("(u.dept_id IN (10, 20) OR u.id = 1)", expression.toString());
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
}
}
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.framework.datapermission.core.util;
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class DataPermissionUtilsTest {
@Test
public void testExecuteIgnore() {
DataPermissionUtils.executeIgnore(() -> assertFalse(DataPermissionContextHolder.get().enable()));
}
}

View File

@ -43,6 +43,12 @@
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.ip.core.utils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link AreaUtils} 的单元测试
*
* @author 芋道源码
*/
public class AreaUtilsTest {
@Test
public void testGetArea() {
// 调用北京
Area area = AreaUtils.getArea(110100);
// 断言
assertEquals(area.getId(), 110100);
assertEquals(area.getName(), "北京市");
assertEquals(area.getType(), AreaTypeEnum.CITY.getType());
assertEquals(area.getParent().getId(), 110000);
assertEquals(area.getChildren().size(), 16);
}
@Test
public void testFormat() {
assertEquals(AreaUtils.format(110105), "北京市 北京市 朝阳区");
assertEquals(AreaUtils.format(1), "中国");
assertEquals(AreaUtils.format(2), "蒙古");
}
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.framework.ip.core.utils;
import cn.iocoder.yudao.framework.ip.core.Area;
import org.junit.jupiter.api.Test;
import org.lionsoul.ip2region.xdb.Searcher;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link IPUtils} 的单元测试
*
* @author wanglhup
*/
public class IPUtilsTest {
@Test
public void testGetAreaId_string() {
// 120.202.4.0|120.202.4.255|420600
Integer areaId = IPUtils.getAreaId("120.202.4.50");
assertEquals(420600, areaId);
}
@Test
public void testGetAreaId_long() throws Exception {
// 120.203.123.0|120.203.133.255|360900
long ip = Searcher.checkIP("120.203.123.250");
Integer areaId = IPUtils.getAreaId(ip);
assertEquals(360900, areaId);
}
@Test
public void testGetArea_string() {
// 120.202.4.0|120.202.4.255|420600
Area area = IPUtils.getArea("120.202.4.50");
assertEquals("襄阳市", area.getName());
}
@Test
public void testGetArea_long() throws Exception {
// 120.203.123.0|120.203.133.255|360900
long ip = Searcher.checkIP("120.203.123.252");
Area area = IPUtils.getArea(ip);
assertEquals("宜春市", area.getName());
}
}

View File

@ -66,6 +66,13 @@
<optional>true</optional>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.google.guava</groupId>

View File

@ -61,6 +61,13 @@
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
<optional>true</optional> <!-- 设置为 optional只有在 AreaConvert 的时候使用 -->
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.framework.dict.core.util;
import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
/**
* {@link DictFrameworkUtils} 的单元测试
*/
public class DictFrameworkUtilsTest extends BaseMockitoUnitTest {
@Mock
private DictDataCommonApi dictDataApi;
@BeforeEach
public void setUp() {
DictFrameworkUtils.init(dictDataApi);
DictFrameworkUtils.clearCache();
}
@Test
public void testParseDictDataLabel() {
// mock 数据
List<DictDataRespDTO> dictDatas = List.of(
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("cat").setLabel("")),
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("dog").setLabel(""))
);
// mock 方法
when(dictDataApi.getDictDataList(eq("animal"))).thenReturn(dictDatas);
// 断言返回值
assertEquals("", DictFrameworkUtils.parseDictDataLabel("animal", "dog"));
}
@Test
public void testParseDictDataValue() {
// mock 数据
List<DictDataRespDTO> dictDatas = List.of(
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("cat").setLabel("")),
randomPojo(DictDataRespDTO.class, o -> o.setDictType("animal").setValue("dog").setLabel(""))
);
// mock 方法
when(dictDataApi.getDictDataList(eq("animal"))).thenReturn(dictDatas);
// 断言返回值
assertEquals("dog", DictFrameworkUtils.parseDictDataValue("animal", ""));
}
}

View File

@ -54,7 +54,7 @@ public abstract class BaseDO implements Serializable, TransPojo {
private Boolean deleted;
/**
* creatorcreateTimeupdateTimeupdater 都清空避免前端直接传递 creator 之类的字段直接就被更新了
* creatorcreateTimeupdateTimeupdater 都清空避免前端直接传递 creator 之类的字段直接就被更新了
*/
public void clean(){
this.creator = null;

View File

@ -35,6 +35,13 @@
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.framework.signature.core;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect;
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link ApiSignatureTest} 的单元测试
*/
@ExtendWith(MockitoExtension.class)
public class ApiSignatureTest {
@InjectMocks
private ApiSignatureAspect apiSignatureAspect;
@Mock
private ApiSignatureRedisDAO signatureRedisDAO;
@Test
public void testSignatureGet() throws IOException {
// 搞一个签名
Long timestamp = System.currentTimeMillis();
String nonce = IdUtil.randomUUID();
String appId = "xxxxxx";
String appSecret = "yyyyyy";
String signString = "k1=v1&v1=k1testappId=xxxxxx&nonce=" + nonce + "&timestamp=" + timestamp + "yyyyyy";
String sign = DigestUtil.sha256Hex(signString);
// 准备参数
ApiSignature apiSignature = mock(ApiSignature.class);
when(apiSignature.appId()).thenReturn("appId");
when(apiSignature.timestamp()).thenReturn("timestamp");
when(apiSignature.nonce()).thenReturn("nonce");
when(apiSignature.sign()).thenReturn("sign");
when(apiSignature.timeout()).thenReturn(60);
when(apiSignature.timeUnit()).thenReturn(TimeUnit.SECONDS);
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(eq("appId"))).thenReturn(appId);
when(request.getHeader(eq("timestamp"))).thenReturn(String.valueOf(timestamp));
when(request.getHeader(eq("nonce"))).thenReturn(nonce);
when(request.getHeader(eq("sign"))).thenReturn(sign);
when(request.getParameterMap()).thenReturn(MapUtil.<String, String[]>builder()
.put("v1", new String[]{"k1"}).put("k1", new String[]{"v1"}).build());
when(request.getContentType()).thenReturn("application/json");
when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
// mock 方法
when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
when(signatureRedisDAO.setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS))).thenReturn(true);
// 调用
boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
// 断言结果
assertTrue(result);
}
}

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>测试组件,用于单元测试、集成测试</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId> <!-- 单元测试,我们采用 H2 作为数据库 -->
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>com.github.fppt</groupId> <!-- 单元测试,我们采用内嵌的 Redis 数据库 -->
<artifactId>jedis-mock</artifactId>
</dependency>
<dependency>
<groupId>uk.co.jemos.podam</groupId> <!-- 单元测试,随机生成 POJO 类 -->
<artifactId>podam</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.framework.test.config;
import com.github.fppt.jedismock.RedisServer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import java.io.IOException;
/**
* Redis 测试 Configuration主要实现内嵌 Redis 的启动
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
@Lazy(false) // 禁止延迟加载
@EnableConfigurationProperties(RedisProperties.class)
public class RedisTestConfiguration {
/**
* 创建模拟的 Redis Server 服务器
*/
@Bean
public RedisServer redisServer(RedisProperties properties) throws IOException {
RedisServer redisServer = new RedisServer(properties.getPort());
// 一次执行多个单元测试时貌似创建多个 spring 容器导致不进行 stop这样就导致端口被占用无法启动
try {
redisServer.start();
} catch (Exception ignore) {}
return redisServer;
}
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.framework.test.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import javax.sql.DataSource;
/**
* SQL 初始化的测试 Configuration
*
* 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration
* 因为我们在单元测试会使用 spring.main.lazy-initialization true开启延迟加载此时会导致 DataSourceInitializationConfiguration 初始化
* 不过呢当前类的实现代码基本是复制 DataSourceInitializationConfiguration 的哈
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass(name = "org.springframework.jdbc.datasource.init.DatabasePopulator")
@Lazy(value = false) // 禁止延迟加载
@EnableConfigurationProperties(SqlInitializationProperties.class)
public class SqlInitializationTestConfiguration {
@Bean
public DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
SqlInitializationProperties initializationProperties) {
DatabaseInitializationSettings settings = createFrom(initializationProperties);
return new DataSourceScriptDatabaseInitializer(dataSource, settings);
}
static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(properties.getSchemaLocations());
settings.setDataLocations(properties.getDataLocations());
settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getEncoding());
settings.setMode(properties.getMode());
return settings;
}
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.framework.test.core.ut;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
/**
* 依赖内存 DB + Redis 的单元测试
*
* 相比 {@link BaseDbUnitTest} 来说额外增加了内存 Redis
*
* @author 芋道源码
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后清理 DB
public class BaseDbAndRedisUnitTest {
@Import({
// DB 配置类
YudaoDataSourceAutoConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
SqlInitializationTestConfiguration.class, // SQL 初始化
// MyBatis 配置类
YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
// Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类用于启动 RedisServer
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedisAutoConfiguration.class, // Spring Redis 自动配置类
RedissonAutoConfiguration.class, // Redisson 自动配置类
// 其它配置类
SpringUtil.class
})
public static class Application {
}
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.framework.test.core.ut;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
/**
* 依赖内存 DB 的单元测试
*
* 注意Service 层同样适用对于 Service 层的单元测试我们针对自己模块的 Mapper 走的是 H2 内存数据库针对别的模块的 Service 走的是 Mock 方法
*
* @author 芋道源码
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后清理 DB
public class BaseDbUnitTest {
@Import({
// DB 配置类
YudaoDataSourceAutoConfiguration.class, // 自己的 DB 配置类
DataSourceAutoConfiguration.class, // Spring DB 自动配置类
DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
DruidDataSourceAutoConfigure.class, // Druid 自动配置类
SqlInitializationTestConfiguration.class, // SQL 初始化
// MyBatis 配置类
YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
MybatisPlusJoinAutoConfiguration.class, // MyBatis 的Join配置类
// 其它配置类
SpringUtil.class
})
public static class Application {
}
}

View File

@ -0,0 +1,13 @@
package cn.iocoder.yudao.framework.test.core.ut;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* Mockito 的单元测试
*
* @author 芋道源码
*/
@ExtendWith(MockitoExtension.class)
public class BaseMockitoUnitTest {
}

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.test.core.ut;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
/**
* 依赖内存 Redis 的单元测试
*
* 相比 {@link BaseDbUnitTest} 来说从内存 DB 改成了内存 Redis
*
* @author 芋道源码
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseRedisUnitTest.Application.class)
@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
public class BaseRedisUnitTest {
@Import({
// Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类用于启动 RedisServer
RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动配置类
// 其它配置类
SpringUtil.class
})
public static class Application {
}
}

View File

@ -0,0 +1,4 @@
/**
* 提供单元测试 Unit Test 的基类
*/
package cn.iocoder.yudao.framework.test.core.ut;

View File

@ -0,0 +1,101 @@
package cn.iocoder.yudao.framework.test.core.util;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.function.Executable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* 单元测试assert 断言工具类
*
* @author 芋道源码
*/
public class AssertUtils {
/**
* 比对两个对象的属性是否一致
*
* 注意如果 expected 存在的属性actual 不存在的时候会进行忽略
*
* @param expected 期望对象
* @param actual 实际对象
* @param ignoreFields 忽略的属性数组
*/
public static void assertPojoEquals(Object expected, Object actual, String... ignoreFields) {
Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
Arrays.stream(expectedFields).forEach(expectedField -> {
// 忽略 jacoco 自动生成的 $jacocoData 属性的情况
if (expectedField.isSynthetic()) {
return;
}
// 如果是忽略的属性则不进行比对
if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
return;
}
// 忽略不存在的属性
Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
if (actualField == null) {
return;
}
// 比对
Assertions.assertEquals(
ReflectUtil.getFieldValue(expected, expectedField),
ReflectUtil.getFieldValue(actual, actualField),
String.format("Field(%s) 不匹配", expectedField.getName())
);
});
}
/**
* 比对两个对象的属性是否一致
*
* 注意如果 expected 存在的属性actual 不存在的时候会进行忽略
*
* @param expected 期望对象
* @param actual 实际对象
* @param ignoreFields 忽略的属性数组
* @return 是否一致
*/
public static boolean isPojoEquals(Object expected, Object actual, String... ignoreFields) {
Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
return Arrays.stream(expectedFields).allMatch(expectedField -> {
// 如果是忽略的属性则不进行比对
if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
return true;
}
// 忽略不存在的属性
Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
if (actualField == null) {
return true;
}
return Objects.equals(ReflectUtil.getFieldValue(expected, expectedField),
ReflectUtil.getFieldValue(actual, actualField));
});
}
/**
* 执行方法校验抛出的 Service 是否符合条件
*
* @param executable 业务异常
* @param errorCode 错误码对象
* @param messageParams 消息参数
*/
public static void assertServiceException(Executable executable, ErrorCode errorCode, Object... messageParams) {
// 调用方法
ServiceException serviceException = assertThrows(ServiceException.class, executable);
// 校验错误码
Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), messageParams);
Assertions.assertEquals(message, serviceException.getMessage(), "错误提示不匹配");
}
}

View File

@ -0,0 +1,146 @@
package cn.iocoder.yudao.framework.test.core.util;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import uk.co.jemos.podam.api.PodamFactory;
import uk.co.jemos.podam.api.PodamFactoryImpl;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 随机工具类
*
* @author 芋道源码
*/
public class RandomUtils {
private static final int RANDOM_STRING_LENGTH = 10;
private static final int TINYINT_MAX = 127;
private static final int RANDOM_DATE_MAX = 30;
private static final int RANDOM_COLLECTION_LENGTH = 5;
private static final PodamFactory PODAM_FACTORY = new PodamFactoryImpl();
static {
// 字符串
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(String.class,
(dataProviderStrategy, attributeMetadata, map) -> randomString());
// Integer
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Integer.class, (dataProviderStrategy, attributeMetadata, map) -> {
// 如果是 status 的字段返回 0 1
if ("status".equals(attributeMetadata.getAttributeName())) {
return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
}
// 如果是 typestatus 结尾的字段返回 tinyint 范围
if (StrUtil.endWithAnyIgnoreCase(attributeMetadata.getAttributeName(),
"type", "status", "category", "scope", "result")) {
return RandomUtil.randomInt(0, TINYINT_MAX + 1);
}
return RandomUtil.randomInt();
});
// LocalDateTime
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(LocalDateTime.class,
(dataProviderStrategy, attributeMetadata, map) -> randomLocalDateTime());
// Boolean
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Boolean.class, (dataProviderStrategy, attributeMetadata, map) -> {
// 如果是 deleted 的字段返回非删除
if ("deleted".equals(attributeMetadata.getAttributeName())) {
return false;
}
return RandomUtil.randomBoolean();
});
}
public static String randomString() {
return RandomUtil.randomString(RANDOM_STRING_LENGTH);
}
public static Long randomLongId() {
return RandomUtil.randomLong(0, Long.MAX_VALUE);
}
public static Integer randomInteger() {
return RandomUtil.randomInt(0, Integer.MAX_VALUE);
}
public static Date randomDate() {
return RandomUtil.randomDay(0, RANDOM_DATE_MAX);
}
public static LocalDateTime randomLocalDateTime() {
// 设置 Nano 为零的原因避免 MySQLH2 存储不到时间戳
return LocalDateTimeUtil.of(randomDate()).withNano(0);
}
public static Short randomShort() {
return (short) RandomUtil.randomInt(0, Short.MAX_VALUE);
}
public static <T> Set<T> randomSet(Class<T> clazz) {
return Stream.iterate(0, i -> i).limit(RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH))
.map(i -> randomPojo(clazz)).collect(Collectors.toSet());
}
public static Integer randomCommonStatus() {
return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
}
public static String randomEmail() {
return randomString() + "@qq.com";
}
public static String randomMobile() {
return "13800138" + RandomUtil.randomNumbers(3);
}
public static String randomURL() {
return "https://www.iocoder.cn/" + randomString();
}
@SafeVarargs
public static <T> T randomPojo(Class<T> clazz, Consumer<T>... consumers) {
T pojo = PODAM_FACTORY.manufacturePojo(clazz);
// 非空时回调逻辑通过它可以实现 Pojo 的进一步处理
if (ArrayUtil.isNotEmpty(consumers)) {
Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));
}
return pojo;
}
@SafeVarargs
public static <T> T randomPojo(Class<T> clazz, Type type, Consumer<T>... consumers) {
T pojo = PODAM_FACTORY.manufacturePojo(clazz, type);
// 非空时回调逻辑通过它可以实现 Pojo 的进一步处理
if (ArrayUtil.isNotEmpty(consumers)) {
Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));
}
return pojo;
}
@SafeVarargs
public static <T> List<T> randomPojoList(Class<T> clazz, Consumer<T>... consumers) {
int size = RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH);
return randomPojoList(clazz, size, consumers);
}
@SafeVarargs
public static <T> List<T> randomPojoList(Class<T> clazz, int size, Consumer<T>... consumers) {
return Stream.iterate(0, i -> i).limit(size).map(o -> randomPojo(clazz, consumers))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,4 @@
/**
* 测试组件用于单元测试集成测试等等
*/
package cn.iocoder.yudao.framework.test;

View File

@ -0,0 +1 @@
<https://www.iocoder.cn/Spring-Boot/Unit-Test/?yudao>

View File

@ -0,0 +1,94 @@
package cn.iocoder.yudao.framework.desensitize.core;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.*;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* {@link DesensitizeTest} 的单元测试
*/
@ExtendWith(MockitoExtension.class)
public class DesensitizeTest {
@Test
public void test() {
// 准备参数
DesensitizeDemo desensitizeDemo = new DesensitizeDemo();
desensitizeDemo.setNickname("芋道源码");
desensitizeDemo.setBankCard("9988002866797031");
desensitizeDemo.setCarLicense("粤A66666");
desensitizeDemo.setFixedPhone("01086551122");
desensitizeDemo.setIdCard("530321199204074611");
desensitizeDemo.setPassword("123456");
desensitizeDemo.setPhoneNumber("13248765917");
desensitizeDemo.setSlider1("ABCDEFG");
desensitizeDemo.setSlider2("ABCDEFG");
desensitizeDemo.setSlider3("ABCDEFG");
desensitizeDemo.setEmail("1@email.com");
desensitizeDemo.setRegex("你好,我是芋道源码");
desensitizeDemo.setAddress("北京市海淀区上地十街10号");
desensitizeDemo.setOrigin("芋道源码");
// 调用
DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
// 断言
assertNotNull(d);
assertEquals("芋***", d.getNickname());
assertEquals("998800********31", d.getBankCard());
assertEquals("粤A6***6", d.getCarLicense());
assertEquals("0108*****22", d.getFixedPhone());
assertEquals("530321**********11", d.getIdCard());
assertEquals("******", d.getPassword());
assertEquals("132****5917", d.getPhoneNumber());
assertEquals("#######", d.getSlider1());
assertEquals("ABC*EFG", d.getSlider2());
assertEquals("*******", d.getSlider3());
assertEquals("1****@email.com", d.getEmail());
assertEquals("你好,我是*", d.getRegex());
assertEquals("北京市海淀区上地十街10号*", d.getAddress());
assertEquals("芋道源码", d.getOrigin());
}
@Data
public static class DesensitizeDemo {
@ChineseNameDesensitize
private String nickname;
@BankCardDesensitize
private String bankCard;
@CarLicenseDesensitize
private String carLicense;
@FixedPhoneDesensitize
private String fixedPhone;
@IdCardDesensitize
private String idCard;
@PasswordDesensitize
private String password;
@MobileDesensitize
private String phoneNumber;
@SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = "#")
private String slider1;
@SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
private String slider2;
@SliderDesensitize(prefixKeep = 10)
private String slider3;
@EmailDesensitize
private String email;
@RegexDesensitize(regex = "芋道源码", replacer = "*")
private String regex;
@Address
private String address;
private String origin;
}
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.framework.desensitize.core.annotation;
import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
import cn.iocoder.yudao.framework.desensitize.core.handler.AddressHandler;
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 地址
*
* 用于 {@link DesensitizeTest} 测试使用
*
* @author gaibu
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = AddressHandler.class)
public @interface Address {
String replacer() default "*";
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.framework.desensitize.core.handler;
import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
/**
* {@link Address} 的脱敏处理器
*
* 用于 {@link DesensitizeTest} 测试使用
*/
public class AddressHandler implements DesensitizationHandler<Address> {
@Override
public String desensitize(String origin, Address annotation) {
return origin + annotation.replacer();
}
}

View File

@ -60,6 +60,12 @@
<artifactId>yudao-spring-boot-starter-job</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -162,7 +162,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
if (CollUtil.isEmpty(list)) {
return;
}
chatConversationMapper.deleteByIds(convertList(list, AiChatConversationDO::getId));
chatConversationMapper.deleteBatchIds(convertList(list, AiChatConversationDO::getId));
}
@Override

View File

@ -339,7 +339,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
throw exception(CHAT_MESSAGE_NOT_EXIST);
}
// 2. 执行删除
chatMessageMapper.deleteByIds(convertList(messages, AiChatMessageDO::getId));
chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
}
@Override

View File

@ -89,7 +89,7 @@ public class AiImageServiceImpl implements AiImageService {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return imageMapper.selectByIds(ids);
return imageMapper.selectBatchIds(ids);
}
@Override

View File

@ -200,7 +200,7 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return knowledgeDocumentMapper.selectByIds(ids);
return knowledgeDocumentMapper.selectBatchIds(ids);
}
@Override

View File

@ -351,7 +351,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return segmentMapper.selectByIds(ids);
return segmentMapper.selectBatchIds(ids);
}
}

View File

@ -163,7 +163,7 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return chatRoleMapper.selectByIds(ids);
return chatRoleMapper.selectBatchIds(ids);
}
@Override

View File

@ -84,7 +84,7 @@ public class AiToolServiceImpl implements AiToolService {
@Override
public List<AiToolDO> getToolList(Collection<Long> ids) {
return toolMapper.selectByIds(ids);
return toolMapper.selectBatchIds(ids);
}
@Override
@ -97,4 +97,4 @@ public class AiToolServiceImpl implements AiToolService {
return toolMapper.selectListByStatus(status);
}
}
}

View File

@ -38,7 +38,6 @@ public class AiUtils {
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens,
Set<String> toolNames, Map<String, Object> toolContext) {
toolNames = ObjUtil.defaultIfNull(toolNames, Collections.emptySet());
toolContext = ObjUtil.defaultIfNull(toolContext, Collections.emptyMap());
// noinspection EnhancedSwitchMigration
switch (platform) {
case TONG_YI:

View File

@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.util.ClientOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
/**
* {@link AzureOpenAiChatModel} 集成测试
*
* @author 芋道源码
*/
public class AzureOpenAIChatModelTests {
// TODO @芋艿晚点在调整
private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
.endpoint("https://eastusprejade.openai.azure.com")
.credential(new AzureKeyCredential("xxx"))
.clientOptions((new ClientOptions()).setApplicationId("spring-ai"));
private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,
AzureOpenAiChatOptions.builder().deploymentName(DEFAULT_DEPLOYMENT_NAME).build());
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link BaiChuanChatModel} 集成测试
*
* @author 芋道源码
*/
public class BaiChuanChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(BaiChuanChatModel.BASE_URL)
.apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("Baichuan4-Turbo") // 模型https://platform.baichuan-ai.com/docs/api
.temperature(0.7)
.build())
.build();
private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* 基于 {@link OpenAiChatModel} 集成 Coze 测试
*
* @author 芋道源码
*/
public class CozeChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl("http://127.0.0.1:3000")
.apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link DeepSeekChatModel} 集成测试
*
* @author 芋道源码
*/
public class DeepSeekChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(DeepSeekChatModel.BASE_URL)
.apiKey("sk-e52047409b144d97b791a6a46a2d") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("deepseek-chat") // 模型
.temperature(0.7)
.build())
.build();
private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* 基于 {@link OpenAiChatModel} 集成 Dify 测试
*
* @author 芋道源码
*/
public class DifyChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl("http://127.0.0.1:3000")
.apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link DouBaoChatModel} 集成测试
*
* @author 芋道源码
*/
public class DouBaoChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(DouBaoChatModel.BASE_URL)
.apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("doubao-1-5-lite-32k-250115") // 模型doubao
// .model("deepseek-r1-250120") // 模型deepseek
.temperature(0.7)
.build())
.build();
private final DouBaoChatModel chatModel = new DouBaoChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
// TODO @芋艿因为使用的是 v1 api导致 deepseek-r1-250120 不返回 think 过程后续需要优化
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* 基于 {@link OpenAiChatModel} 集成 FastGPT 测试
*
* @author 芋道源码
*/
public class FastGPTChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl("https://cloud.fastgpt.cn/api")
.apiKey("fastgpt-aqcc61kFtF8CeaglnGAfQOCIDWwjGdJVJHv6hIlMo28otFlva2aZNK") // apiKey
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link HunYuanChatModel} 集成测试
*
* @author 芋道源码
*/
public class HunYuanChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(HunYuanChatModel.BASE_URL)
.apiKey("sk-bcd") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(HunYuanChatModel.MODEL_DEFAULT) // 模型
.temperature(0.7)
.build())
.build();
private final HunYuanChatModel chatModel = new HunYuanChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
private final OpenAiChatModel deepSeekOpenAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(HunYuanChatModel.DEEP_SEEK_BASE_URL)
.apiKey("sk-abc") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
// .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型"deepseek-v3"
.model("deepseek-r1") // 模型"deepseek-r1"
.temperature(0.7)
.build())
.build();
private final HunYuanChatModel deepSeekChatModel = new HunYuanChatModel(deepSeekOpenAiChatModel);
@Test
@Disabled
public void testCall_deepseek() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = deepSeekChatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream_deekseek() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link OllamaChatModel} 集成测试
*
* @author 芋道源码
*/
public class LlamaChatModelTests {
private final OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
.defaultOptions(OllamaOptions.builder()
.model(OllamaModel.LLAMA3.getName()) // 模型
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.minimax.MiniMaxChatModel;
import org.springframework.ai.minimax.MiniMaxChatOptions;
import org.springframework.ai.minimax.api.MiniMaxApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link MiniMaxChatModel} 的集成测试
*
* @author 芋道源码
*/
public class MiniMaxChatModelTests {
private final MiniMaxChatModel chatModel = new MiniMaxChatModel(
new MiniMaxApi("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLnjovmlofmlowiLCJVc2VyTmFtZSI6IueOi-aWh-aWjCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODk3Mjg3MjQ5NDU2ODA4MzQ2IiwiUGhvbmUiOiIxNTYwMTY5MTM5OSIsIkdyb3VwSUQiOiIxODk3Mjg3MjQ5NDQ4NDE5NzM4IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDMtMTEgMTI6NTI6MDIiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aAuB7gWW_oA4IYhh-CF7c9MfWWxKN49B_HK-DYjXaDwwffhiG-H1571z1WQhp9QytWG-DqgLejneeSxkiq1wQIe3FsEP2wz4BmGBct31LehbJu8ehLxg_vg75Uod1nFAHbm5mZz6JSVLNIlSo87Xr3UtSzJhAXlapEkcqlA4YOzOpKrZ8l5_OJPTORTCmHWZYgJcRS-faNiH62ZnUEHUozesTFhubJHo5GfJCw_edlnmfSUocERV1BjWvenhZ9My-aYXNktcW9WaSj9l6gayV7A0Ium_PL55T9ln1PcI8gayiVUKJGJDoqNyF1AF9_aF9NOKtTnQzwNqnZdlTYH6hw"), // 密钥
MiniMaxChatOptions.builder()
.model(MiniMaxApi.ChatModel.ABAB_6_5_G_Chat.getValue()) // 模型
.build());
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.moonshot.MoonshotChatModel;
import org.springframework.ai.moonshot.MoonshotChatOptions;
import org.springframework.ai.moonshot.api.MoonshotApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link MoonshotChatModel} 的集成测试
*
* @author 芋道源码
*/
public class MoonshotChatModelTests {
private final MoonshotChatModel chatModel = new MoonshotChatModel(
new MoonshotApi("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA"), // 密钥
MoonshotChatOptions.builder()
.model("moonshot-v1-8k") // 模型
.build());
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link OllamaChatModel} 集成测试
*
* @author 芋道源码
*/
public class OllamaChatModelTests {
private final OllamaChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
.defaultOptions(OllamaOptions.builder()
// .model("qwen") // 模型https://ollama.com/library/qwen
.model("deepseek-r1") // 模型https://ollama.com/library/deepseek-r1
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link OpenAiChatModel} 集成测试
*
* @author 芋道源码
*/
public class OpenAIChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl("https://api.holdai.top")
.apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(OpenAiApi.ChatModel.GPT_4_O) // 模型
.temperature(0.7)
.build())
.build();
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link SiliconFlowChatModel} 集成测试
*
* @author 芋道源码
*/
public class SiliconFlowChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
.apiKey("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(SiliconFlowApiConstants.MODEL_DEFAULT) // 模型
// .model("deepseek-ai/DeepSeek-R1") // 模型deepseek-ai/DeepSeek-R1可用赠费
// .model("Pro/deepseek-ai/DeepSeek-R1") // 模型Pro/deepseek-ai/DeepSeek-R1需要付费
.temperature(0.7)
.build())
.build();
private final SiliconFlowChatModel chatModel = new SiliconFlowChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link DashScopeChatModel} 集成测试类
*
* @author fansili
*/
public class TongYiChatModelTests {
private final DashScopeChatModel chatModel = new DashScopeChatModel(
new DashScopeApi("sk-7d903764249848cfa912733146da12d1"),
DashScopeChatOptions.builder()
.withModel("qwen1.5-72b-chat") // 模型
// .withModel("deepseek-r1") // 模型deepseek-r1
// .withModel("deepseek-v3") // 模型deepseek-v3
// .withModel("deepseek-r1-distill-qwen-1.5b") // 模型deepseek-r1-distill-qwen-1.5b
.build());
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link XingHuoChatModel} 集成测试
*
* @author fansili
*/
public class XingHuoChatModelTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(XingHuoChatModel.BASE_URL)
.apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("generalv3.5") // 模型
.temperature(0.7)
.build())
.build();
private final XingHuoChatModel chatModel = new XingHuoChatModel(openAiChatModel);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.qianfan.QianFanChatModel;
import org.springframework.ai.qianfan.QianFanChatOptions;
import org.springframework.ai.qianfan.api.QianFanApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
// TODO @芋艿百度千帆 API 提供了 V2 版本目前 Spring AI 不兼容可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展
/**
* {@link QianFanChatModel} 的集成测试
*
* @author fansili
*/
public class YiYanChatModelTests {
private final QianFanChatModel chatModel = new QianFanChatModel(
new QianFanApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"), // 密钥
QianFanChatOptions.builder()
.model(QianFanApi.ChatModel.ERNIE_4_0_8K_Preview.getValue())
.build()
);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
// TODO @芋艿文心一言只要带上 system message 就报错已经各种测试很莫名
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
// TODO @芋艿文心一言只要带上 system message 就报错已经各种测试很莫名
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(System.out::println).then().block();
}
}

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* {@link ZhiPuAiChatModel} 的集成测试
*
* @author 芋道源码
*/
public class ZhiPuAiChatModelTests {
private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(
new ZhiPuAiApi("32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs"), // 密钥
ZhiPuAiChatOptions.builder()
.model(ZhiPuAiApi.ChatModel.GLM_4.getName()) // 模型
.build()
);
@Test
@Disabled
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = chatModel.call(new Prompt(messages));
// 打印结果
System.out.println(response);
System.out.println(response.getResult().getOutput());
}
@Test
@Disabled
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
// 打印结果
flux.doOnNext(response -> {
// System.out.println(response);
System.out.println(response.getResult().getOutput());
}).then().block();
}
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
/**
* {@link MidjourneyApi} 集成测试
*
* @author 芋道源码
*/
public class MidjourneyApiTests {
private final MidjourneyApi midjourneyApi = new MidjourneyApi(
"https://api.holdai.top/mj", // 链接
"sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17", // 密钥
null);
@Test
@Disabled
public void testImagine() {
// 准备参数
MidjourneyApi.ImagineRequest request = new MidjourneyApi.ImagineRequest(null,
"生成一个小猫,可爱的", null,
MidjourneyApi.ImagineRequest.buildState(512, 512, "6.0", MidjourneyApi.ModelEnum.MIDJOURNEY.getModel()));
// 方法调用
MidjourneyApi.SubmitResponse response = midjourneyApi.imagine(request);
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testAction() {
// 准备参数
MidjourneyApi.ActionRequest request = new MidjourneyApi.ActionRequest("1720277033455953",
"MJ::JOB::upsample::1::ee267661-ee52-4ced-a530-0343ba95af3b", null);
// 方法调用
MidjourneyApi.SubmitResponse response = midjourneyApi.action(request);
// 打印结果
System.out.println(response);
}
@Test
@Disabled
public void testGetTaskList() {
// 准备参数该参数可以通过 MidjourneyApi.SubmitResponse result 获取
// String taskId = "1720277033455953";
String taskId = "1720277214045971";
// 方法调用
List<MidjourneyApi.Notify> taskList = midjourneyApi.getTaskList(Collections.singletonList(taskId));
// 打印结果
System.out.println(taskList);
}
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.openai.api.OpenAiImageApi;
/**
* {@link OpenAiImageModel} 集成测试类
*
* @author fansili
*/
public class OpenAiImageModelTests {
private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
.baseUrl("https://api.holdai.top") // apiKey
.apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17")
.build());
@Test
@Disabled
public void testCall() {
// 准备参数
ImageOptions options = OpenAiImageOptions.builder()
.withModel(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
.withHeight(256).withWidth(256)
.build();
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
System.out.println(response);
}
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.qianfan.QianFanImageModel;
import org.springframework.ai.qianfan.QianFanImageOptions;
import org.springframework.ai.qianfan.api.QianFanImageApi;
import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;
// TODO @芋艿百度千帆 API 提供了 V2 版本目前 Spring AI 不兼容可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展
/**
* {@link QianFanImageModel} 集成测试类
*/
public class QianFanImageTests {
private final QianFanImageModel imageModel = new QianFanImageModel(
new QianFanImageApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e")); // 密钥
@Test
@Disabled
public void testCall() {
// 准备参数
// 只支持 1024x1024768x768768x10241024x768576x10241024x576
QianFanImageOptions imageOptions = QianFanImageOptions.builder()
.model(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())
.width(1024).height(1024)
.N(1)
.build();
ImagePrompt prompt = new ImagePrompt("good", imageOptions);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
String b64Json = response.getResult().getOutput().getB64Json();
System.out.println(response);
viewImage(b64Json);
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
/**
* {@link SiliconFlowImageModel} 集成测试
*/
public class SiliconFlowImageModelTests {
private final SiliconFlowImageModel imageModel = new SiliconFlowImageModel(
new SiliconFlowImageApi("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // 密钥
);
@Test
@Disabled
public void testCall() {
// 准备参数
SiliconFlowImageOptions imageOptions = SiliconFlowImageOptions.builder()
.model("Kwai-Kolors/Kolors")
.build();
ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
System.out.println(response);
}
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.thread.ThreadUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.stabilityai.StabilityAiImageModel;
import org.springframework.ai.stabilityai.api.StabilityAiApi;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.TimeUnit;
/**
* {@link StabilityAiImageModel} 集成测试类
*
* @author fansili
*/
public class StabilityAiImageModelTests {
private final StabilityAiImageModel imageModel = new StabilityAiImageModel(
new StabilityAiApi("sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx") // 密钥
);
@Test
@Disabled
public void testCall() {
// 准备参数
ImageOptions options = OpenAiImageOptions.builder()
.withModel("stable-diffusion-v1-6")
.withHeight(320).withWidth(320)
.build();
ImagePrompt prompt = new ImagePrompt("great wall", options);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
String b64Json = response.getResult().getOutput().getB64Json();
System.out.println(response);
viewImage(b64Json);
}
public static void viewImage(String b64Json) {
// 创建一个 JFrame
JFrame frame = new JFrame("Byte Image Display");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
// 创建一个 JLabel 来显示图片
byte[] imageBytes = Base64.decode(b64Json);
JLabel label = new JLabel(new ImageIcon(imageBytes));
// JLabel 添加到 JFrame
frame.getContentPane().add(label, BorderLayout.CENTER);
// 显示 JFrame
frame.setVisible(true);
ThreadUtil.sleep(1, TimeUnit.HOURS);
}
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
/**
* {@link DashScopeImageModel} 集成测试类
*
* @author fansili
*/
public class TongYiImagesModelTest {
private final DashScopeImageModel imageModel = new DashScopeImageModel(
new DashScopeImageApi("sk-7d903764249848cfa912733146da12d1"));
@Test
@Disabled
public void imageCallTest() {
// 准备参数
ImageOptions options = DashScopeImageOptions.builder()
.withModel("wanx-v1")
.withHeight(256).withWidth(256)
.build();
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
System.out.println(response);
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
/**
* {@link ZhiPuAiImageModel} 集成测试
*/
public class ZhiPuAiImageModelTests {
private final ZhiPuAiImageModel imageModel = new ZhiPuAiImageModel(
new ZhiPuAiImageApi("78d3228c1d9e5e342a3e1ab349e2dd7b.VXLoq5vrwK2ofboy") // 密钥
);
@Test
@Disabled
public void testCall() {
// 准备参数
ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder()
.model(ZhiPuAiImageApi.ImageModel.CogView_3.getValue())
.build();
ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions);
// 方法调用
ImageResponse response = imageModel.call(prompt);
// 打印结果
System.out.println(response);
}
}

View File

@ -0,0 +1,124 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.mcp;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
@Disabled
public class DouBaoMcpTests {
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(DouBaoChatModel.BASE_URL)
.apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("doubao-1-5-lite-32k-250115") // 模型doubao
.temperature(0.7)
.build())
.build();
private final DouBaoChatModel chatModel = new DouBaoChatModel(openAiChatModel);
private final MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
.toolObjects(new UserService())
.build();
private final ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(provider)
.build();
@Test
public void testMcpGetUserInfo() {
// 打印结果
System.out.println(chatClient.prompt()
.user("目前有哪些工具可以使用")
.call()
.content());
System.out.println("====================================");
// 打印结果
System.out.println(chatClient.prompt()
.user("小新的年龄是多少")
.call()
.content());
System.out.println("====================================");
// 打印结果
System.out.println(chatClient.prompt()
.user("获取小新的基本信息")
.call()
.content());
System.out.println("====================================");
// 打印结果
System.out.println(chatClient.prompt()
.user("小新是什么职业的")
.call()
.content());
System.out.println("====================================");
// 打印结果
System.out.println(chatClient.prompt()
.user("小新的教育背景")
.call()
.content());
System.out.println("====================================");
// 打印结果
System.out.println(chatClient.prompt()
.user("小新的兴趣爱好是什么")
.call()
.content());
System.out.println("====================================");
}
static class UserService {
@Tool(name = "getUserAge", description = "获取用户年龄")
public String getUserAge(String userName) {
return "" + userName + "》的年龄为18";
}
@Tool(name = "getUserSex", description = "获取用户性别")
public String getUserSex(String userName) {
return "" + userName + "》的性别为:男";
}
@Tool(name = "getUserBasicInfo", description = "获取用户基本信息,包括姓名、年龄、性别等")
public String getUserBasicInfo(String userName) {
return "" + userName + "》的基本信息:\n姓名" + userName + "\n年龄18\n性别\n身高175cm\n体重65kg";
}
@Tool(name = "getUserContact", description = "获取用户联系方式,包括电话、邮箱等")
public String getUserContact(String userName) {
return "" + userName + "》的联系方式:\n电话138****1234\n邮箱" + userName.toLowerCase() + "@example.com\nQQ123456789";
}
@Tool(name = "getUserAddress", description = "获取用户地址信息")
public String getUserAddress(String userName) {
return "" + userName + "》的地址信息北京市朝阳区科技园区88号";
}
@Tool(name = "getUserJob", description = "获取用户职业信息")
public String getUserJob(String userName) {
return "" + userName + "》的职业信息软件工程师就职于ABC科技有限公司工作年限5年";
}
@Tool(name = "getUserHobbies", description = "获取用户兴趣爱好")
public String getUserHobbies(String userName) {
return "" + userName + "》的兴趣爱好:编程、阅读、旅游、摄影、打篮球";
}
@Tool(name = "getUserEducation", description = "获取用户教育背景")
public String getUserEducation(String userName) {
return "" + userName + "》的教育背景:\n本科计算机科学与技术专业北京大学\n硕士软件工程专业清华大学";
}
}
}

View File

@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.music;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* {@link SunoApi} 集成测试
*
* @author xiaoxin
*/
public class SunoApiTests {
private final SunoApi sunoApi = new SunoApi("https://suno-3tah0ycyt-status2xxs-projects.vercel.app");
// private final SunoApi sunoApi = new SunoApi("http://127.0.0.1:3001");
@Test // 描述模式
@Disabled
public void testGenerate() {
// 准备参数
SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
"happy music",
"chirp-v3-5",
false);
// 调用方法
List<SunoApi.MusicData> musicList = sunoApi.generate(generateRequest);
// 打印结果
System.out.println(musicList);
}
@Test // 歌词模式
@Disabled
public void testCustomGenerate() {
// 准备参数
SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
"创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。",
"Happy",
"Happy Song",
"chirp-v3.5",
false,
false);
// 调用方法
List<SunoApi.MusicData> musicList = sunoApi.customGenerate(generateRequest);
// 打印结果
System.out.println(musicList);
}
@Test
@Disabled
public void testGenerateLyrics() {
// 调用方法
SunoApi.LyricsData lyricsData = sunoApi.generateLyrics("A soothing lullaby");
// 打印结果
System.out.println(lyricsData);
}
@Test
@Disabled
public void testGetMusicList() {
// 准备参数
// String id = "d460ddda-7c87-4f34-b751-419b08a590ca";
String id = "584729e5-0fe9-4157-86da-1b4803ff42bf";
// 调用方法
List<SunoApi.MusicData> musicList = sunoApi.getMusicList(ListUtil.of(id));
// 打印结果
System.out.println(musicList);
}
@Test
@Disabled
public void testGetLimitUsage() {
// 调用方法
SunoApi.LimitUsageData limitUsageData = sunoApi.getLimitUsage();
// 打印结果
System.out.println(limitUsageData);
}
}

View File

@ -0,0 +1,315 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.ppt.wdd;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.wenduoduo.api.WenDuoDuoPptApi;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import java.util.Map;
import java.util.Objects;
/**
* {@link WenDuoDuoPptApi} 集成测试
*
* @author xiaoxin
*/
@Disabled
public class WenDuoDuoPptApiTests {
private final String token = ""; // API Token
private final WenDuoDuoPptApi wenDuoDuoPptApi = new WenDuoDuoPptApi(token);
@Test
@Disabled
public void testCreateApiToken() {
// 准备参数
String apiKey = "";
WenDuoDuoPptApi.CreateTokenRequest request = new WenDuoDuoPptApi.CreateTokenRequest(apiKey);
// 调用方法
String token = wenDuoDuoPptApi.createApiToken(request);
// 打印结果
System.out.println(token);
}
/**
* 创建任务
*/
@Test
@Disabled
public void testCreateTask() {
WenDuoDuoPptApi.ApiResponse apiResponse = wenDuoDuoPptApi.createTask(1, "dify 介绍", null);
System.out.println(apiResponse);
}
@Test // 创建大纲
@Disabled
public void testGenerateOutlineRequest() {
WenDuoDuoPptApi.CreateOutlineRequest request = new WenDuoDuoPptApi.CreateOutlineRequest(
"1901539019628613632", "medium", null, null, null, null);
// 调用
Flux<Map<String, Object>> flux = wenDuoDuoPptApi.createOutline(request);
StringBuffer contentBuffer = new StringBuffer();
flux.doOnNext(chunk -> {
contentBuffer.append(chunk.get("text"));
if (Objects.equals(Integer.parseInt(String.valueOf(chunk.get("status"))), 4)) {
// status 4最终 markdown 结构树
System.out.println(JsonUtils.toJsonString(chunk.get("result")));
System.out.println(" ########################################################################");
}
}).then().block();
// 打印结果
System.out.println(contentBuffer);
}
/**
* 修改大纲
*/
@Test
@Disabled
public void testUpdateOutlineRequest() {
WenDuoDuoPptApi.UpdateOutlineRequest request = new WenDuoDuoPptApi.UpdateOutlineRequest(
"1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可");
// 调用
Flux<Map<String, Object>> flux = wenDuoDuoPptApi.updateOutline(request);
StringBuffer contentBuffer = new StringBuffer();
flux.doOnNext(chunk -> {
contentBuffer.append(chunk.get("text"));
if (Objects.equals(Integer.parseInt(String.valueOf(chunk.get("status"))), 4)) {
// status 4最终 markdown 结构树
System.out.println(JsonUtils.toJsonString(chunk.get("result")));
System.out.println(" ########################################################################");
}
}).then().block();
// 打印结果
System.out.println(contentBuffer);
}
/**
* 获取 PPT 模版分页
*/
@Test
@Disabled
public void testGetPptTemplatePage() {
// 准备参数
WenDuoDuoPptApi.TemplateQueryRequest.Filter filter = new WenDuoDuoPptApi.TemplateQueryRequest.Filter(
1, null, null, null);
WenDuoDuoPptApi.TemplateQueryRequest request = new WenDuoDuoPptApi.TemplateQueryRequest(1, 10, filter);
// 调用
WenDuoDuoPptApi.PagePptTemplateInfo pptTemplatePage = wenDuoDuoPptApi.getTemplatePage(request);
// 打印结果
System.out.println(pptTemplatePage);
}
/**
* 生成 PPT
*/
@Test
@Disabled
public void testGeneratePptx() {
// 准备参数
WenDuoDuoPptApi.PptCreateRequest request = new WenDuoDuoPptApi.PptCreateRequest("1901539019628613632", "1805081814809960448", TEST_OUT_LINE_CONTENT);
// 调用
WenDuoDuoPptApi.PptInfo pptInfo = wenDuoDuoPptApi.create(request);
// 打印结果
System.out.println(pptInfo);
}
private final String TEST_OUT_LINE_CONTENT = """
# Dify新一代AI应用开发平台
## 1 什么是Dify
### 1.1 Dify定义AI应用开发平台
#### 1.1.1 低代码开发
Dify是一个低代码AI应用开发平台旨在简化AI应用的构建过程让开发者无需编写大量代码即可快速创建各种智能应用
#### 1.1.2 核心功能
Dify的核心功能包括数据集成模型选择流程编排和应用部署提供一站式解决方案加速AI应用的落地和迭代
#### 1.1.3 开源与商业
Dify提供开源版本和商业版本满足不同用户的需求开源版本适合个人开发者和小型团队商业版本则提供更强大的功能和技术支持
### 1.2 Dify解决的问题AI开发痛点
#### 1.2.1 开发周期长
传统AI应用开发周期长需要大量的人力和时间投入Dify通过可视化界面和预置组件大幅缩短开发周期
#### 1.2.2 技术门槛高
AI技术门槛高需要专业的知识和技能Dify降低技术门槛让更多开发者能够参与到AI应用的开发中来
#### 1.2.3 部署和维护复杂
AI应用的部署和维护复杂需要专业的运维团队Dify提供自动化的部署和维护工具简化流程降低成本
### 1.3 Dify发展历程
#### 1.3.1 早期探索
Dify的早期版本主要关注于自然语言处理领域的应用通过集成各种NLP模型提供文本分类情感分析等功能
#### 1.3.2 功能扩展
随着用户需求的不断增长Dify的功能逐渐扩展到图像识别语音识别等领域支持更多类型的AI应用
#### 1.3.3 生态建设
Dify积极建设开发者生态提供丰富的文档教程和案例帮助开发者更好地使用Dify平台共同推动AI技术的发展
## 2 Dify的核心功能
### 2.1 数据集成连接各种数据源
#### 2.1.1 支持多种数据源
Dify支持连接各种数据源包括关系型数据库NoSQL数据库文件系统云存储等满足不同场景的数据需求
#### 2.1.2 数据转换和清洗
Dify提供数据转换和清洗功能可以将不同格式的数据转换为统一的格式并去除无效数据提高数据质量
#### 2.1.3 数据安全
Dify注重数据安全采用各种安全措施保护用户的数据包括数据加密访问控制权限管理等
### 2.2 模型选择丰富的AI模型库
#### 2.2.1 预置模型
Dify预置了丰富的AI模型包括自然语言处理图像识别语音识别等领域的模型开发者可以直接使用这些模型无需自行训练极大的简化了开发流程
#### 2.2.2 自定义模型
Dify支持开发者上传自定义模型满足个性化的需求开发者可以将自己训练的模型部署到Dify平台上与其他开发者共享
#### 2.2.3 模型评估
Dify提供模型评估功能可以对不同模型进行评估选择最优的模型提高应用性能
### 2.3 流程编排可视化流程设计器
#### 2.3.1 可视化界面
Dify提供可视化的流程设计器开发者可以通过拖拽组件的方式设计AI应用的流程无需编写代码简单高效
#### 2.3.2 灵活的流程控制
Dify支持灵活的流程控制可以根据不同的条件执行不同的分支实现复杂的业务逻辑
#### 2.3.3 实时调试
Dify提供实时调试功能可以在设计流程的过程中实时查看流程的执行结果及时发现和解决问题
### 2.4 应用部署一键部署和管理
#### 2.4.1 快速部署
Dify提供一键部署功能可以将AI应用快速部署到各种环境包括本地环境云环境容器环境等
#### 2.4.2 自动伸缩
Dify支持自动伸缩可以根据应用的负载自动调整资源保证应用的稳定性和性能
#### 2.4.3 监控和告警
Dify提供监控和告警功能可以实时监控应用的状态并在出现问题时及时告警方便运维人员进行处理
## 3 Dify的特点和优势
### 3.1 低代码降低开发门槛
#### 3.1.1 可视化开发
Dify采用可视化开发模式开发者无需编写大量代码只需通过拖拽组件即可完成AI应用的开发降低了开发门槛
#### 3.1.2 预置组件
Dify预置了丰富的组件包括数据源组件模型组件流程控制组件等开发者可以直接使用这些组件提高开发效率
#### 3.1.3 减少代码量
Dify可以显著减少代码量降低开发难度让更多开发者能够参与到AI应用的开发中来
### 3.2 灵活满足不同场景需求
#### 3.2.1 支持多种数据源
Dify支持多种数据源可以连接各种数据源满足不同场景的数据需求
#### 3.2.2 支持自定义模型
Dify支持自定义模型开发者可以将自己训练的模型部署到Dify平台上满足个性化的需求
#### 3.2.3 灵活的流程控制
Dify支持灵活的流程控制可以根据不同的条件执行不同的分支实现复杂的业务逻辑
### 3.3 高效加速应用落地
#### 3.3.1 快速开发
Dify通过可视化界面和预置组件大幅缩短开发周期加速AI应用的落地
#### 3.3.2 快速部署
Dify提供一键部署功能可以将AI应用快速部署到各种环境提高部署效率
#### 3.3.3 自动化运维
Dify提供自动化的运维工具简化运维流程降低运维成本
### 3.4 开放构建繁荣生态
#### 3.4.1 开源社区
Dify拥有活跃的开源社区开发者可以在社区中交流经验分享资源共同推动Dify的发展
#### 3.4.2 丰富的文档
Dify提供丰富的文档教程和案例帮助开发者更好地使用Dify平台
#### 3.4.3 API支持
Dify提供API支持开发者可以通过API将Dify集成到自己的系统中扩展Dify的功能
## 4 Dify的使用场景
### 4.1 智能客服提升客户服务质量
#### 4.1.1 自动回复
Dify可以用于构建智能客服系统实现自动回复客户的常见问题提高客户服务效率
#### 4.1.2 情感分析
Dify可以对客户的语音或文本进行情感分析判断客户的情绪并根据情绪提供个性化的服务
#### 4.1.3 知识库问答
Dify可以构建知识库问答系统让客户通过提问的方式获取所需的信息提高客户满意度
### 4.2 金融风控提高风险识别能力
#### 4.2.1 欺诈检测
Dify可以用于构建金融风控系统实现欺诈检测识别可疑交易降低风险
#### 4.2.2 信用评估
Dify可以对用户的信用进行评估并根据评估结果提供不同的金融服务
#### 4.2.3 反洗钱
Dify可以用于反洗钱识别可疑资金流动防止犯罪行为
### 4.3 智慧医疗提升医疗服务水平
#### 4.3.1 疾病诊断
Dify可以用于辅助疾病诊断提高诊断准确率缩短诊断时间
#### 4.3.2 药物研发
Dify可以用于药物研发加速新药的发现和开发
#### 4.3.3 智能健康管理
Dify可以构建智能健康管理系统为用户提供个性化的健康建议和服务
### 4.4 智慧城市提升城市管理效率
#### 4.4.1 交通优化
Dify可以用于交通优化提高交通效率缓解交通拥堵
#### 4.4.2 环境监测
Dify可以用于环境监测实时监测空气质量水质等环境指标及时发现和解决环境问题
#### 4.4.3 智能安防
Dify可以用于智能安防提高城市安全水平预防犯罪行为
## 5 Dify的成功案例
### 5.1 Case 1某电商平台的智能客服
#### 5.1.1 项目背景
该电商平台客户服务压力大人工客服成本高需要一种智能化的解决方案
#### 5.1.2 解决方案
使用Dify构建智能客服系统实现自动回复客户的常见问题并根据客户的情绪提供个性化的服务
#### 5.1.3 效果
客户服务效率提高50%客户满意度提高20%人工客服成本降低30%
### 5.2 Case 2某银行的金融风控系统
#### 5.2.1 项目背景
该银行面临日益增长的金融风险需要一种更有效的风险识别和控制手段
#### 5.2.2 解决方案
使用Dify构建金融风控系统实现欺诈检测信用评估和反洗钱等功能提高风险识别能力
#### 5.2.3 效果
欺诈交易识别率提高40%信用评估准确率提高30%洗钱风险降低25%
### 5.3 Case 3某医院的辅助疾病诊断系统
#### 5.3.1 项目背景
该医院医生工作压力大疾病诊断准确率有待提高需要一种辅助诊断工具
#### 5.3.2 解决方案
使用Dify构建辅助疾病诊断系统根据患者的病历和症状提供诊断建议提高诊断准确率
#### 5.3.3 效果
疾病诊断准确率提高20%诊断时间缩短15%医生工作效率提高10%
## 6 Dify的未来展望
### 6.1 技术升级
#### 6.1.1 模型优化
Dify将不断优化预置模型提高模型性能并支持更多类型的AI模型
#### 6.1.2 流程引擎升级
Dify将升级流程引擎提高流程的灵活性和可扩展性支持更复杂的业务逻辑
#### 6.1.3 平台性能优化
Dify将不断优化平台性能提高平台的稳定性和可靠性满足大规模应用的需求
### 6.2 生态建设
#### 6.2.1 社区建设
Dify将继续加强开源社区建设吸引更多开发者参与共同推动Dify的发展
#### 6.2.2 合作伙伴拓展
Dify将拓展合作伙伴与更多的企业和机构合作共同推动AI技术的应用
#### 6.2.3 应用商店
Dify将构建应用商店让开发者可以分享自己的应用用户可以购买和使用这些应用构建繁荣的生态系统
### 6.3 应用领域拓展
#### 6.3.1 智能制造
Dify将拓展到智能制造领域为企业提供智能化的生产管理和质量控制解决方案
#### 6.3.2 智慧农业
Dify将拓展到智慧农业领域为农民提供智能化的种植和养殖管理解决方案
#### 6.3.3 更多领域
Dify将拓展到更多领域为各行各业提供智能化的解决方案推动社会发展
## 7 总结
### 7.1 Dify的价值
#### 7.1.1 降低AI开发门槛
Dify通过低代码的方式让更多开发者能够参与到AI应用的开发中来
#### 7.1.2 加速AI应用落地
Dify提供一站式解决方案加速AI应用的落地和迭代
#### 7.1.3 构建繁荣的AI生态
Dify通过开源社区和应用商店构建繁荣的AI生态系统
### 7.2 共同发展
#### 7.2.1 欢迎加入Dify社区
欢迎更多开发者加入Dify社区共同推动Dify的发展
#### 7.2.2 合作共赢
期待与更多的企业和机构合作共同推动AI技术的应用
#### 7.2.3 共创未来
让我们一起用AI技术改变世界共创美好未来
""";
}

View File

@ -0,0 +1,319 @@
package cn.iocoder.yudao.module.ai.framework.ai.core.model.ppt.xunfei;
import cn.hutool.core.io.FileUtil;
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.api.XunFeiPptApi;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
/**
* {@link XunFeiPptApi} 集成测试
*
* @author xiaoxin
*/
public class XunFeiPptApiTests {
// 讯飞 API 配置信息实际使用时请替换为您的应用信息
private static final String APP_ID = "6c8ac023";
private static final String API_SECRET = "Y2RjM2Q1MWJjZTdkYmFiODc0OGE5NmRk";
private final XunFeiPptApi xunfeiPptApi = new XunFeiPptApi(APP_ID, API_SECRET);
/**
* 获取 PPT 模板列表
*/
@Test
@Disabled
public void testGetTemplatePage() {
// 调用方法
XunFeiPptApi.TemplatePageResponse response = xunfeiPptApi.getTemplatePage("商务", 10);
// 打印结果
System.out.println("模板列表响应:" + JsonUtils.toJsonString(response));
if (response != null && response.data() != null && response.data().records() != null) {
System.out.println("模板总数:" + response.data().total());
System.out.println("当前页码:" + response.data().pageNum());
System.out.println("模板数量:" + response.data().records().size());
// 打印第一个模板的信息如果存在
if (!response.data().records().isEmpty()) {
XunFeiPptApi.TemplateInfo firstTemplate = response.data().records().get(0);
System.out.println("模板ID" + firstTemplate.templateIndexId());
System.out.println("模板风格:" + firstTemplate.style());
System.out.println("模板颜色:" + firstTemplate.color());
System.out.println("模板行业:" + firstTemplate.industry());
}
}
}
/**
* 创建大纲通过文本
*/
@Test
@Disabled
public void testCreateOutline() {
XunFeiPptApi.CreateResponse response = getCreateResponse();
// 打印结果
System.out.println("创建大纲响应:" + JsonUtils.toJsonString(response));
// 保存 sid outline 用于后续测试
if (response != null && response.data() != null) {
System.out.println("sid: " + response.data().sid());
if (response.data().outline() != null) {
// 使用 OutlineData toJsonString 方法
System.out.println("outline: " + response.data().outline().toJsonString());
// outline 对象转换为 JSON 字符串用于后续 createPptByOutline 测试
String outlineJson = response.data().outline().toJsonString();
System.out.println("可用于 createPptByOutline 的 outline 字符串: " + outlineJson);
}
}
}
/**
* 创建大纲通过文本
*
* @return 创建大纲响应
*/
private XunFeiPptApi.CreateResponse getCreateResponse() {
String param = "智能体平台 Dify 介绍";
return xunfeiPptApi.createOutline(param);
}
/**
* 通过大纲创建 PPT完整参数
*/
@Test
@Disabled
public void testCreatePptByOutlineWithFullParams() {
// 创建大纲对象
XunFeiPptApi.CreateResponse createResponse = getCreateResponse();
// 调用方法
XunFeiPptApi.CreateResponse response = xunfeiPptApi.createPptByOutline(createResponse.data().outline(), "精简一些不要超过6个章节");
// 打印结果
System.out.println("通过大纲创建 PPT 响应:" + JsonUtils.toJsonString(response));
// 保存sid用于后续进度查询
if (response != null && response.data() != null) {
System.out.println("sid: " + response.data().sid());
if (response.data().coverImgSrc() != null) {
System.out.println("封面图片: " + response.data().coverImgSrc());
}
}
}
/**
* 检查 PPT 生成进度
*/
@Test
@Disabled
public void testCheckProgress() {
// 准备参数 - 使用之前创建 PPT 时返回的 sid
String sid = "e96dac09f2ec4ee289f029a5fb874ecd"; // 替换为实际的sid
// 调用方法
XunFeiPptApi.ProgressResponse response = xunfeiPptApi.checkProgress(sid);
// 打印结果
System.out.println("检查进度响应:" + JsonUtils.toJsonString(response));
// 安全地访问响应数据
if (response != null && response.data() != null) {
XunFeiPptApi.ProgressResponseData data = response.data();
// 打印PPT生成状态
System.out.println("PPT 构建状态: " + data.pptStatus());
System.out.println("AI 配图状态: " + data.aiImageStatus());
System.out.println("演讲备注状态: " + data.cardNoteStatus());
// 打印进度信息
if (data.totalPages() != null && data.donePages() != null) {
System.out.println("总页数: " + data.totalPages());
System.out.println("已完成页数: " + data.donePages());
System.out.println("完成进度: " + data.getProgressPercent() + "%");
} else {
System.out.println("进度: " + data.process() + "%");
}
// 检查是否完成
if (data.isAllDone()) {
System.out.println("PPT 生成已完成!");
System.out.println("PPT 下载链接: " + data.pptUrl());
}
// 检查是否失败
else if (data.isFailed()) {
System.out.println("PPT 生成失败!");
System.out.println("错误信息: " + data.errMsg());
}
// 正在进行中
else {
System.out.println("PPT 生成中,请稍后再查询...");
}
}
}
/**
* 轮询检查 PPT 生成进度直到完成
*/
@Test
@Disabled
public void testPollCheckProgress() throws InterruptedException {
// 准备参数 - 使用之前创建 PP T时返回的 sid
String sid = "1690ef6ee0344e72b5c5434f403b8eaa"; // 替换为实际的sid
// 最大轮询次数
int maxPolls = 20;
// 轮询间隔毫秒- 讯飞 API 限流为 3 秒一次
long pollInterval = 3500;
for (int i = 0; i < maxPolls; i++) {
System.out.println("" + (i + 1) + "次查询进度...");
// 调用方法
XunFeiPptApi.ProgressResponse response = xunfeiPptApi.checkProgress(sid);
// 安全地访问响应数据
if (response != null && response.data() != null) {
XunFeiPptApi.ProgressResponseData data = response.data();
// 打印进度信息
System.out.println("PPT 构建状态: " + data.pptStatus());
if (data.totalPages() != null && data.donePages() != null) {
System.out.println("完成进度: " + data.donePages() + "/" + data.totalPages()
+ " (" + data.getProgressPercent() + "%)");
}
// 检查是否完成
if (data.isAllDone()) {
System.out.println("PPT 生成已完成!");
System.out.println("PPT 下载链接: " + data.pptUrl());
break;
}
// 检查是否失败
else if (data.isFailed()) {
System.out.println("PPT 生成失败!");
System.out.println("错误信息: " + data.errMsg());
break;
}
// 正在进行中继续轮询
else {
System.out.println("PPT 生成中,等待" + (pollInterval / 1000) + "秒后继续查询...");
Thread.sleep(pollInterval);
}
} else {
System.out.println("查询失败,等待" + (pollInterval / 1000) + "秒后重试...");
Thread.sleep(pollInterval);
}
}
}
/**
* 直接创建 PPT通过文本
*/
@Test
@Disabled
public void testCreatePptByText() {
// 准备参数
String query = "合肥天气趋势分析包括近5年的气温变化、降水量变化、极端天气事件以及对城市生活的影响";
// 调用方法
XunFeiPptApi.CreateResponse response = xunfeiPptApi.create(query);
// 打印结果
System.out.println("直接创建 PPT 响应:" + JsonUtils.toJsonString(response));
// 保存 sid 用于后续进度查询
if (response != null && response.data() != null) {
System.out.println("sid: " + response.data().sid());
if (response.data().coverImgSrc() != null) {
System.out.println("封面图片: " + response.data().coverImgSrc());
}
System.out.println("标题: " + response.data().title());
System.out.println("副标题: " + response.data().subTitle());
}
}
/**
* 直接创建 PPT通过文件
*/
@Test
@Disabled
public void testCreatePptByFile() {
// 准备参数
File file = new File("src/test/resources/test.txt"); // 请确保此文件存在
MultipartFile multipartFile = convertFileToMultipartFile(file);
// 调用方法
XunFeiPptApi.CreateResponse response = xunfeiPptApi.create(multipartFile, file.getName());
// 打印结果
System.out.println("通过文件创建PPT响应" + JsonUtils.toJsonString(response));
// 保存 sid 用于后续进度查询
if (response != null && response.data() != null) {
System.out.println("sid: " + response.data().sid());
if (response.data().coverImgSrc() != null) {
System.out.println("封面图片: " + response.data().coverImgSrc());
}
System.out.println("标题: " + response.data().title());
System.out.println("副标题: " + response.data().subTitle());
}
}
/**
* 直接创建 PPT完整参数
*/
@Test
@Disabled
public void testCreatePptWithFullParams() {
// 准备参数
String query = "合肥天气趋势分析,包括近 5 年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响";
// 创建请求对象
XunFeiPptApi.CreatePptRequest request = XunFeiPptApi.CreatePptRequest.builder()
.query(query)
.language("cn")
.isCardNote(true)
.search(true)
.isFigure(true)
.aiImage("advanced")
.author("测试用户")
.build();
// 调用方法
XunFeiPptApi.CreateResponse response = xunfeiPptApi.create(request);
// 打印结果
System.out.println("使用完整参数创建 PPT 响应:" + JsonUtils.toJsonString(response));
// 保存 sid 用于后续进度查询
if (response != null && response.data() != null) {
String sid = response.data().sid();
System.out.println("sid: " + sid);
if (response.data().coverImgSrc() != null) {
System.out.println("封面图片: " + response.data().coverImgSrc());
}
System.out.println("标题: " + response.data().title());
System.out.println("副标题: " + response.data().subTitle());
// 立即查询一次进度
System.out.println("立即查询进度...");
XunFeiPptApi.ProgressResponse progressResponse = xunfeiPptApi.checkProgress(sid);
if (progressResponse != null && progressResponse.data() != null) {
XunFeiPptApi.ProgressResponseData progressData = progressResponse.data();
System.out.println("PPT 构建状态: " + progressData.pptStatus());
if (progressData.totalPages() != null && progressData.donePages() != null) {
System.out.println("完成进度: " + progressData.donePages() + "/" + progressData.totalPages()
+ " (" + progressData.getProgressPercent() + "%)");
}
}
}
}
/**
* File 转换为 MultipartFile
*/
private MultipartFile convertFileToMultipartFile(File file) {
return new MockMultipartFile("file", file.getName(), "text/plain", FileUtil.readBytes(file));
}
}

79
yudao-module-bpm/pom.xml Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
bpm 包下业务流程管理Business Process Management我们放工作流的功能。
例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
bpm 解释https://baike.baidu.com/item/BPM/1933
工作流基于 Flowable 6 实现,分成流程定义、流程表单、流程实例、流程任务等功能模块。
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<!-- Flowable 工作流相关 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.bpm.api.event;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
/**
* 流程实例的状态结果发生变化的 Event
*
* @author 芋道源码
*/
@SuppressWarnings("ALL")
@Data
public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
/**
* 流程实例的编号
*/
@NotNull(message = "流程实例的编号不能为空")
private String id;
/**
* 流程实例的 key
*/
@NotNull(message = "流程实例的 key 不能为空")
private String processDefinitionKey;
/**
* 流程实例的结果
*/
@NotNull(message = "流程实例的状态不能为空")
private Integer status;
/**
* 流程实例对应的业务标识
* 例如说请假
*/
private String businessKey;
public BpmProcessInstanceStatusEvent(Object source) {
super(source);
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.bpm.api.event;
import cn.hutool.core.util.StrUtil;
import org.springframework.context.ApplicationListener;
/**
* {@link BpmProcessInstanceStatusEvent} 的监听器
*
* @author 芋道源码
*/
public abstract class BpmProcessInstanceStatusEventListener
implements ApplicationListener<BpmProcessInstanceStatusEvent> {
@Override
public final void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
if (!StrUtil.equals(event.getProcessDefinitionKey(), getProcessDefinitionKey())) {
return;
}
onEvent(event);
}
/**
* @return 返回监听的流程定义 Key
*/
protected abstract String getProcessDefinitionKey();
/**
* 处理事件
*
* @param event 事件
*/
protected abstract void onEvent(BpmProcessInstanceStatusEvent event);
}

View File

@ -0,0 +1,4 @@
/**
* bpm API 定义并实现提供给其它模块的 API
*/
package cn.iocoder.yudao.module.bpm.api;

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import jakarta.validation.Valid;
/**
* 流程实例 Api 接口
*
* @author 芋道源码
*/
public interface BpmProcessInstanceApi {
/**
* 创建流程实例提供给内部
*
* @param userId 用户编号
* @param reqDTO 创建信息
* @return 实例的编号
*/
String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO);
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
/**
* Flowable 流程实例 Api 实现类
*
* @author 芋道源码
* @author jason
*/
@Service
@Validated
public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
@Resource
private BpmProcessInstanceService processInstanceService;
@Override
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) {
return processInstanceService.createProcessInstance(userId, reqDTO);
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.bpm.api.task;
import jakarta.validation.constraints.NotEmpty;
/**
* 流程任务 Api 接口
*
* @author jason
*/
public interface BpmProcessTaskApi {
/**
* 触发流程任务的执行
*
* @param processInstanceId 流程实例编号
* @param taskDefineKey 任务 Key
*/
void triggerTask(@NotEmpty(message = "流程实例的编号不能为空") String processInstanceId,
@NotEmpty(message = "任务 Key 不能为空") String taskDefineKey);
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
/**
* 流程任务 Api 实现类
*
* @author jason
*/
@Service
@Validated
public class BpmProcessTaskApiImpl implements BpmProcessTaskApi {
@Resource
private BpmTaskService bpmTaskService;
@Override
public void triggerTask(String processInstanceId, String taskDefineKey) {
bpmTaskService.triggerTask(processInstanceId, taskDefineKey);
}
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.bpm.api.task.dto;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Map;
/**
* 流程实例的创建 Request DTO
*
* @author 芋道源码
*/
@Data
public class BpmProcessInstanceCreateReqDTO {
/**
* 流程定义的标识
*/
@NotEmpty(message = "流程定义的标识不能为空")
private String processDefinitionKey;
/**
* 变量实例动态表单
*/
private Map<String, Object> variables;
/**
* 业务的唯一标识
*
* 例如说请假申请的编号通过它可以查询到对应的实例
*/
@NotEmpty(message = "业务的唯一标识")
private String businessKey;
/**
* 发起人自选审批人 Map
*
* keytaskKey 任务编码
* value审批人的数组
* 例如{ taskKey1 :[1, 2] }则表示 taskKey1 这个任务提前设定了 userId 1,2 的用户进行审批
*/
private Map<String, List<Long>> startUserSelectAssignees;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.bpm.controller.admin.base.dept;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "部门精简信息 VO")
@Data
public class DeptSimpleBaseVO {
@Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部")
private String name;
}

View File

@ -0,0 +1,4 @@
/**
* 基础包放一些通用的 VO
*/
package cn.iocoder.yudao.module.bpm.controller.admin.base;

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.bpm.controller.admin.base.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户精简信息 VO")
@Data
public class UserSimpleBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
@Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long deptId;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
private String deptName;
}

View File

@ -0,0 +1,95 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Comparator;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - BPM 流程分类")
@RestController
@RequestMapping("/bpm/category")
@Validated
public class BpmCategoryController {
@Resource
private BpmCategoryService categoryService;
@PostMapping("/create")
@Operation(summary = "创建流程分类")
@PreAuthorize("@ss.hasPermission('bpm:category:create')")
public CommonResult<Long> createCategory(@Valid @RequestBody BpmCategorySaveReqVO createReqVO) {
return success(categoryService.createCategory(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新流程分类")
@PreAuthorize("@ss.hasPermission('bpm:category:update')")
public CommonResult<Boolean> updateCategory(@Valid @RequestBody BpmCategorySaveReqVO updateReqVO) {
categoryService.updateCategory(updateReqVO);
return success(true);
}
@PutMapping("/update-sort-batch")
@Operation(summary = "批量更新流程分类的排序")
@Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3")
@PreAuthorize("@ss.hasPermission('bpm:category:update')")
public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) {
categoryService.updateCategorySortBatch(ids);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除流程分类")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:category:delete')")
public CommonResult<Boolean> deleteCategory(@RequestParam("id") Long id) {
categoryService.deleteCategory(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得流程分类")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:category:query')")
public CommonResult<BpmCategoryRespVO> getCategory(@RequestParam("id") Long id) {
BpmCategoryDO category = categoryService.getCategory(id);
return success(BeanUtils.toBean(category, BpmCategoryRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得流程分类分页")
@PreAuthorize("@ss.hasPermission('bpm:category:query')")
public CommonResult<PageResult<BpmCategoryRespVO>> getCategoryPage(@Valid BpmCategoryPageReqVO pageReqVO) {
PageResult<BpmCategoryDO> pageResult = categoryService.getCategoryPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, BpmCategoryRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项")
public CommonResult<List<BpmCategoryRespVO>> getCategorySimpleList() {
List<BpmCategoryDO> list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus());
list.sort(Comparator.comparingInt(BpmCategoryDO::getSort));
return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId())
.setName(category.getName()).setCode(category.getCode())));
}
}

View File

@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 动态表单")
@RestController
@RequestMapping("/bpm/form")
@Validated
public class BpmFormController {
@Resource
private BpmFormService formService;
@PostMapping("/create")
@Operation(summary = "创建动态表单")
@PreAuthorize("@ss.hasPermission('bpm:form:create')")
public CommonResult<Long> createForm(@Valid @RequestBody BpmFormSaveReqVO createReqVO) {
return success(formService.createForm(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新动态表单")
@PreAuthorize("@ss.hasPermission('bpm:form:update')")
public CommonResult<Boolean> updateForm(@Valid @RequestBody BpmFormSaveReqVO updateReqVO) {
formService.updateForm(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除动态表单")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:form:delete')")
public CommonResult<Boolean> deleteForm(@RequestParam("id") Long id) {
formService.deleteForm(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得动态表单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:form:query')")
public CommonResult<BpmFormRespVO> getForm(@RequestParam("id") Long id) {
BpmFormDO form = formService.getForm(id);
return success(BeanUtils.toBean(form, BpmFormRespVO.class));
}
@GetMapping({"/list-all-simple", "/simple-list"})
@Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
public CommonResult<List<BpmFormRespVO>> getFormSimpleList() {
List<BpmFormDO> list = formService.getFormList();
return success(convertList(list, formDO -> // 只返回 idname 字段
new BpmFormRespVO().setId(formDO.getId()).setName(formDO.getName())));
}
@GetMapping("/page")
@Operation(summary = "获得动态表单分页")
@PreAuthorize("@ss.hasPermission('bpm:form:query')")
public CommonResult<PageResult<BpmFormRespVO>> getFormPage(@Valid BpmFormPageReqVO pageVO) {
PageResult<BpmFormDO> pageResult = formService.getFormPage(pageVO);
return success(BeanUtils.toBean(pageResult, BpmFormRespVO.class));
}
}

View File

@ -0,0 +1,200 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程模型")
@RestController
@RequestMapping("/bpm/model")
@Validated
public class BpmModelController {
@Resource
private BpmModelService modelService;
@Resource
private BpmFormService formService;
@Resource
private BpmCategoryService categoryService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@GetMapping("/list")
@Operation(summary = "获得模型分页")
@Parameter(name = "name", description = "模型名称", example = "芋艿")
public CommonResult<List<BpmModelRespVO>> getModelList(@RequestParam(value = "name", required = false) String name) {
List<Model> list = modelService.getModelList(name);
if (CollUtil.isEmpty(list)) {
return success(Collections.emptyList());
}
// 获得 Form 表单
Set<Long> formIds = convertSet(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null ? metaInfo.getFormId() : null;
});
Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
// 获得 Category Map
Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
convertSet(list, Model::getCategory));
// 获得 Deployment Map
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
convertSet(list, Model::getDeploymentId));
// 获得 ProcessDefinition Map
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
deploymentMap.keySet());
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
// 获得 User MapDept Map
Set<Long> userIds = convertSetByFlatMap(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
});
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Set<Long> deptIds = convertSetByFlatMap(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty();
});
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
return success(BpmModelConvert.INSTANCE.buildModelList(list,
formMap, categoryMap, deploymentMap, processDefinitionMap, userMap, deptMap));
}
@GetMapping("/get")
@Operation(summary = "获得模型")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:model:query')")
public CommonResult<BpmModelRespVO> getModel(@RequestParam("id") String id) {
Model model = modelService.getModel(id);
if (model == null) {
return null;
}
byte[] bpmnBytes = modelService.getModelBpmnXML(id);
BpmSimpleModelNodeVO simpleModel = modelService.getSimpleModel(id);
return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel));
}
@PostMapping("/create")
@Operation(summary = "新建模型")
@PreAuthorize("@ss.hasPermission('bpm:model:create')")
public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) {
return success(modelService.createModel(createRetVO));
}
@PutMapping("/update")
@Operation(summary = "修改模型")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) {
modelService.updateModel(getLoginUserId(), modelVO);
return success(true);
}
@PutMapping("/update-sort-batch")
@Operation(summary = "批量修改模型排序")
@Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) {
modelService.updateModelSortBatch(getLoginUserId(), ids);
return success(true);
}
@PostMapping("/deploy")
@Operation(summary = "部署模型")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
public CommonResult<Boolean> deployModel(@RequestParam("id") String id) {
modelService.deployModel(getLoginUserId(), id);
return success(true);
}
@PutMapping("/update-state")
@Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState());
return success(true);
}
@Deprecated
@PutMapping("/update-bpmn")
@Operation(summary = "修改模型的 BPMN")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) {
modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除模型")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:model:delete')")
public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
modelService.deleteModel(getLoginUserId(), id);
return success(true);
}
@DeleteMapping("/clean")
@Operation(summary = "清理模型")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:model:clean')")
public CommonResult<Boolean> cleanModel(@RequestParam("id") String id) {
modelService.cleanModel(getLoginUserId(), id);
return success(true);
}
// ========== 仿钉钉/飞书的精简模型 =========
@GetMapping("/simple/get")
@Operation(summary = "获得仿钉钉流程设计模型")
@Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){
return success(modelService.getSimpleModel(modelId));
}
@Deprecated
@PostMapping("/simple/update")
@Operation(summary = "保存仿钉钉流程设计模型")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) {
modelService.updateSimpleModel(getLoginUserId(), reqVO);
return success(Boolean.TRUE);
}
}

View File

@ -0,0 +1,133 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程定义")
@RestController
@RequestMapping("/bpm/process-definition")
@Validated
public class BpmProcessDefinitionController {
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private BpmFormService formService;
@Resource
private BpmCategoryService categoryService;
@GetMapping("/page")
@Operation(summary = "获得流程定义分页")
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
public CommonResult<PageResult<BpmProcessDefinitionRespVO>> getProcessDefinitionPage(
BpmProcessDefinitionPageReqVO pageReqVO) {
PageResult<ProcessDefinition> pageResult = processDefinitionService.getProcessDefinitionPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 获得 Category Map
Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
convertSet(pageResult.getList(), ProcessDefinition::getCategory));
// 获得 Deployment Map
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
convertSet(pageResult.getList(), ProcessDefinition::getDeploymentId));
// 获得 BpmProcessDefinitionInfoDO Map
Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(pageResult.getList(), ProcessDefinition::getId));
// 获得 Form Map
Map<Long, BpmFormDO> formMap = formService.getFormMap(
convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId));
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage(
pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap));
}
@GetMapping ("/list")
@Operation(summary = "获得流程定义列表")
@Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举
public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
@RequestParam("suspensionState") Integer suspensionState) {
// 1.1 获得开启的流程定义
List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
if (CollUtil.isEmpty(list)) {
return success(Collections.emptyList());
}
// 1.2 移除不可见的流程定义
Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(list, ProcessDefinition::getId));
Long userId = getLoginUserId();
list.removeIf(processDefinition -> {
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId());
return processDefinitionInfo == null // 不存在
|| Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见
|| !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起
});
// 2. 拼接 VO 返回
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
list, null, processDefinitionMap, null, null));
}
@GetMapping("/simple-list")
@Operation(summary = "获得流程定义精简列表", description = "只包含未挂起的流程,主要用于前端的下拉选项")
public CommonResult<List<BpmProcessDefinitionRespVO>> getSimpleProcessDefinitionList() {
// 只查询未挂起的流程
List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(
SuspensionState.ACTIVE.getStateCode());
// 拼接 VO 返回只返回 idnamekey
return success(convertList(list, definition -> new BpmProcessDefinitionRespVO()
.setId(definition.getId()).setName(definition.getName()).setKey(definition.getKey())));
}
@GetMapping ("/get")
@Operation(summary = "获得流程定义")
@Parameter(name = "id", description = "流程编号", required = true, example = "1024")
@Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
public CommonResult<BpmProcessDefinitionRespVO> getProcessDefinition(
@RequestParam(value = "id", required = false) String id,
@RequestParam(value = "key", required = false) String key) {
ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id)
: processDefinitionService.getActiveProcessDefinition(key);
if (processDefinition == null) {
return success(null);
}
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
processDefinition, null, processDefinitionInfo, null, null, bpmnModel));
}
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessExpressionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - BPM 流程表达式")
@RestController
@RequestMapping("/bpm/process-expression")
@Validated
public class BpmProcessExpressionController {
@Resource
private BpmProcessExpressionService processExpressionService;
@PostMapping("/create")
@Operation(summary = "创建流程表达式")
@PreAuthorize("@ss.hasPermission('bpm:process-expression:create')")
public CommonResult<Long> createProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO createReqVO) {
return success(processExpressionService.createProcessExpression(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新流程表达式")
@PreAuthorize("@ss.hasPermission('bpm:process-expression:update')")
public CommonResult<Boolean> updateProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO updateReqVO) {
processExpressionService.updateProcessExpression(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除流程表达式")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:process-expression:delete')")
public CommonResult<Boolean> deleteProcessExpression(@RequestParam("id") Long id) {
processExpressionService.deleteProcessExpression(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得流程表达式")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:process-expression:query')")
public CommonResult<BpmProcessExpressionRespVO> getProcessExpression(@RequestParam("id") Long id) {
BpmProcessExpressionDO processExpression = processExpressionService.getProcessExpression(id);
return success(BeanUtils.toBean(processExpression, BpmProcessExpressionRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得流程表达式分页")
@PreAuthorize("@ss.hasPermission('bpm:process-expression:query')")
public CommonResult<PageResult<BpmProcessExpressionRespVO>> getProcessExpressionPage(
@Valid BpmProcessExpressionPageReqVO pageReqVO) {
PageResult<BpmProcessExpressionDO> pageResult = processExpressionService.getProcessExpressionPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class));
}
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessListenerDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessListenerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - BPM 流程监听器")
@RestController
@RequestMapping("/bpm/process-listener")
@Validated
public class BpmProcessListenerController {
@Resource
private BpmProcessListenerService processListenerService;
@PostMapping("/create")
@Operation(summary = "创建流程监听器")
@PreAuthorize("@ss.hasPermission('bpm:process-listener:create')")
public CommonResult<Long> createProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO createReqVO) {
return success(processListenerService.createProcessListener(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新流程监听器")
@PreAuthorize("@ss.hasPermission('bpm:process-listener:update')")
public CommonResult<Boolean> updateProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO updateReqVO) {
processListenerService.updateProcessListener(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除流程监听器")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:process-listener:delete')")
public CommonResult<Boolean> deleteProcessListener(@RequestParam("id") Long id) {
processListenerService.deleteProcessListener(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得流程监听器")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:process-listener:query')")
public CommonResult<BpmProcessListenerRespVO> getProcessListener(@RequestParam("id") Long id) {
BpmProcessListenerDO processListener = processListenerService.getProcessListener(id);
return success(BeanUtils.toBean(processListener, BpmProcessListenerRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得流程监听器分页")
@PreAuthorize("@ss.hasPermission('bpm:process-listener:query')")
public CommonResult<PageResult<BpmProcessListenerRespVO>> getProcessListenerPage(
@Valid BpmProcessListenerPageReqVO pageReqVO) {
PageResult<BpmProcessListenerDO> pageResult = processListenerService.getProcessListenerPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, BpmProcessListenerRespVO.class));
}
}

View File

@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 用户组")
@RestController
@RequestMapping("/bpm/user-group")
@Validated
public class BpmUserGroupController {
@Resource
private BpmUserGroupService userGroupService;
@PostMapping("/create")
@Operation(summary = "创建用户组")
@PreAuthorize("@ss.hasPermission('bpm:user-group:create')")
public CommonResult<Long> createUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO createReqVO) {
return success(userGroupService.createUserGroup(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新用户组")
@PreAuthorize("@ss.hasPermission('bpm:user-group:update')")
public CommonResult<Boolean> updateUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO updateReqVO) {
userGroupService.updateUserGroup(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除用户组")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:user-group:delete')")
public CommonResult<Boolean> deleteUserGroup(@RequestParam("id") Long id) {
userGroupService.deleteUserGroup(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得用户组")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
public CommonResult<BpmUserGroupRespVO> getUserGroup(@RequestParam("id") Long id) {
BpmUserGroupDO userGroup = userGroupService.getUserGroup(id);
return success(BeanUtils.toBean(userGroup, BpmUserGroupRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得用户组分页")
@PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
public CommonResult<PageResult<BpmUserGroupRespVO>> getUserGroupPage(@Valid BpmUserGroupPageReqVO pageVO) {
PageResult<BpmUserGroupDO> pageResult = userGroupService.getUserGroupPage(pageVO);
return success(BeanUtils.toBean(pageResult, BpmUserGroupRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获取用户组精简信息列表", description = "只包含被开启的用户组,主要用于前端的下拉选项")
public CommonResult<List<BpmUserGroupRespVO>> getUserGroupSimpleList() {
List<BpmUserGroupDO> list = userGroupService.getUserGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, group -> new BpmUserGroupRespVO().setId(group.getId()).setName(group.getName())));
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - BPM 流程分类分页 Request VO")
@Data
public class BpmCategoryPageReqVO extends PageParam {
@Schema(description = "分类名", example = "王五")
private String name;
@Schema(description = "分类标志", example = "OA")
private String code;
@Schema(description = "分类状态", example = "1")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - BPM 流程分类 Response VO")
@Data
public class BpmCategoryRespVO {
@Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
private Long id;
@Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
private String name;
@Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
private String code;
@Schema(description = "分类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
private String description;
@Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer sort;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - BPM 流程分类新增/修改 Request VO")
@Data
public class BpmCategorySaveReqVO {
@Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
private Long id;
@Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@NotEmpty(message = "分类名不能为空")
private String name;
@Schema(description = "分类描述", example = "你猜")
private String description;
@Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
@NotEmpty(message = "分类标志不能为空")
private String code;
@Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "分类状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "分类排序不能为空")
private Integer sort;
}

Some files were not shown because too many files have changed in this diff Show More