Compare commits
58 Commits
20b4329af6
...
d508313565
| Author | SHA1 | Date |
|---|---|---|
|
|
d508313565 | |
|
|
58165ef371 | |
|
|
75c1706b97 | |
|
|
a86db4b43f | |
|
|
3140ec8960 | |
|
|
951663b6b7 | |
|
|
78e69421db | |
|
|
39e7d782a7 | |
|
|
73e4f831f2 | |
|
|
c8ee7adeca | |
|
|
a89ff72a36 | |
|
|
e8b8974a5a | |
|
|
ece20eaa3d | |
|
|
38cc543177 | |
|
|
c3ca4218ee | |
|
|
acfff5a833 | |
|
|
8bcf7498b5 | |
|
|
17aff5c906 | |
|
|
b0dfec0c23 | |
|
|
e13805744e | |
|
|
f185e0163c | |
|
|
bc9f5dfc6a | |
|
|
e36547e18b | |
|
|
f4431d948e | |
|
|
45404a47b2 | |
|
|
a6dc8342f8 | |
|
|
ae147e0bcc | |
|
|
06394b2091 | |
|
|
e0d8a94bf6 | |
|
|
45cc231258 | |
|
|
6331411c30 | |
|
|
f8ccda6009 | |
|
|
79c14095ed | |
|
|
35dd0a8c14 | |
|
|
c2c7eb0084 | |
|
|
8252abe3ef | |
|
|
a36800ba8a | |
|
|
98f1272c0c | |
|
|
050f0c9a22 | |
|
|
205d2ae4a9 | |
|
|
7412900e83 | |
|
|
df7e6a94d5 | |
|
|
9928af52cf | |
|
|
d183e75912 | |
|
|
74b5c48fcb | |
|
|
a47e9ad0f8 | |
|
|
1140a63258 | |
|
|
df4a8d56be | |
|
|
7c4054aaa0 | |
|
|
bc4d108d75 | |
|
|
b332c1d73e | |
|
|
1ef3998cfa | |
|
|
63e3bcce60 | |
|
|
e10d831117 | |
|
|
7c01dedab9 | |
|
|
b141dc7740 | |
|
|
1677a0e763 | |
|
|
00c4243d50 |
8
pom.xml
8
pom.xml
|
|
@ -17,13 +17,13 @@
|
||||||
<module>yudao-module-infra</module>
|
<module>yudao-module-infra</module>
|
||||||
<!-- <module>yudao-module-member</module>-->
|
<!-- <module>yudao-module-member</module>-->
|
||||||
<!-- <module>yudao-module-bpm</module>-->
|
<!-- <module>yudao-module-bpm</module>-->
|
||||||
<!-- <module>yudao-module-report</module>-->
|
<module>yudao-module-report</module>
|
||||||
<!-- <module>yudao-module-mp</module>-->
|
<!-- <module>yudao-module-mp</module>-->
|
||||||
<!-- <module>yudao-module-pay</module>-->
|
<module>yudao-module-pay</module>
|
||||||
<!-- <module>yudao-module-mall</module>-->
|
<!-- <module>yudao-module-mall</module>-->
|
||||||
<!-- <module>yudao-module-crm</module>-->
|
<!-- <module>yudao-module-crm</module>-->
|
||||||
<!-- <module>yudao-module-erp</module>-->
|
<!-- <module>yudao-module-erp</module>-->
|
||||||
<!-- <module>yudao-module-ai</module>-->
|
<module>yudao-module-ai</module>
|
||||||
<!-- <module>yudao-module-iot</module>-->
|
<!-- <module>yudao-module-iot</module>-->
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2.5.0-SNAPSHOT</revision>
|
<revision>2.6.0-SNAPSHOT</revision>
|
||||||
<!-- Maven 相关 -->
|
<!-- Maven 相关 -->
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
|
|
|
||||||
|
|
@ -1,598 +0,0 @@
|
||||||
package liquibase.database.core;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import liquibase.CatalogAndSchema;
|
|
||||||
import liquibase.Scope;
|
|
||||||
import liquibase.database.AbstractJdbcDatabase;
|
|
||||||
import liquibase.database.DatabaseConnection;
|
|
||||||
import liquibase.database.OfflineConnection;
|
|
||||||
import liquibase.database.jvm.JdbcConnection;
|
|
||||||
import liquibase.exception.DatabaseException;
|
|
||||||
import liquibase.exception.UnexpectedLiquibaseException;
|
|
||||||
import liquibase.exception.ValidationErrors;
|
|
||||||
import liquibase.executor.ExecutorService;
|
|
||||||
import liquibase.statement.DatabaseFunction;
|
|
||||||
import liquibase.statement.SequenceCurrentValueFunction;
|
|
||||||
import liquibase.statement.SequenceNextValueFunction;
|
|
||||||
import liquibase.statement.core.RawCallStatement;
|
|
||||||
import liquibase.statement.core.RawSqlStatement;
|
|
||||||
import liquibase.structure.DatabaseObject;
|
|
||||||
import liquibase.structure.core.Catalog;
|
|
||||||
import liquibase.structure.core.Index;
|
|
||||||
import liquibase.structure.core.PrimaryKey;
|
|
||||||
import liquibase.structure.core.Schema;
|
|
||||||
import liquibase.util.JdbcUtils;
|
|
||||||
import liquibase.util.StringUtil;
|
|
||||||
|
|
||||||
public class DmDatabase extends AbstractJdbcDatabase {
|
|
||||||
private static final String PRODUCT_NAME = "DM DBMS";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getDefaultDatabaseProductName() {
|
|
||||||
return PRODUCT_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this AbstractDatabase subclass the correct one to use for the given connection.
|
|
||||||
*
|
|
||||||
* @param conn
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
|
|
||||||
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this database understands the given url, return the default driver class name. Otherwise return null.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getDefaultDriver(String url) {
|
|
||||||
if(url.startsWith("jdbc:dm")) {
|
|
||||||
return "dm.jdbc.driver.DmDriver";
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an all-lower-case short name of the product. Used for end-user selecting of database type
|
|
||||||
* such as the DBMS precondition.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getShortName() {
|
|
||||||
return "dm";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Integer getDefaultPort() {
|
|
||||||
return 5236;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this database support initially deferrable columns.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean supportsInitiallyDeferrableColumns() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTablespaces() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return PRIORITY_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
|
|
||||||
|
|
||||||
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
|
|
||||||
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
|
|
||||||
public static final int ORACLE_12C_MAJOR_VERSION = 12;
|
|
||||||
|
|
||||||
private Set<String> reservedWords = new HashSet<>();
|
|
||||||
private Set<String> userDefinedTypes;
|
|
||||||
private Map<String, String> savedSessionNlsSettings;
|
|
||||||
|
|
||||||
private Boolean canAccessDbaRecycleBin;
|
|
||||||
private Integer databaseMajorVersion;
|
|
||||||
private Integer databaseMinorVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor for an object that represents the Oracle Database DBMS.
|
|
||||||
*/
|
|
||||||
public DmDatabase() {
|
|
||||||
super.unquotedObjectsAreUppercased = true;
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
|
|
||||||
// Setting list of Oracle's native functions
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
dateFunctions.add(new DatabaseFunction("SYSDATE"));
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
super.sequenceNextValueFunction = "%s.nextval";
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
super.sequenceCurrentValueFunction = "%s.currval";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryProxySession(final String url, final Connection con) {
|
|
||||||
Matcher m = PROXY_USER.matcher(url);
|
|
||||||
if (m.matches()) {
|
|
||||||
Properties props = new Properties();
|
|
||||||
props.put("PROXY_USER_NAME", m.group(1));
|
|
||||||
try {
|
|
||||||
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
|
|
||||||
method.setAccessible(true);
|
|
||||||
method.invoke(con, 1, props);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDatabaseMajorVersion() throws DatabaseException {
|
|
||||||
if (databaseMajorVersion == null) {
|
|
||||||
return super.getDatabaseMajorVersion();
|
|
||||||
} else {
|
|
||||||
return databaseMajorVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDatabaseMinorVersion() throws DatabaseException {
|
|
||||||
if (databaseMinorVersion == null) {
|
|
||||||
return super.getDatabaseMinorVersion();
|
|
||||||
} else {
|
|
||||||
return databaseMinorVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJdbcCatalogName(CatalogAndSchema schema) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJdbcSchemaName(CatalogAndSchema schema) {
|
|
||||||
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
|
|
||||||
if (StringUtil.isEmpty(generationType)) {
|
|
||||||
return super.getAutoIncrementClause();
|
|
||||||
}
|
|
||||||
|
|
||||||
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
|
|
||||||
String generationStrategy = generationType;
|
|
||||||
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
|
|
||||||
generationStrategy += " ON NULL";
|
|
||||||
}
|
|
||||||
return String.format(autoIncrementClause, generationStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String generatePrimaryKeyName(String tableName) {
|
|
||||||
if (tableName.length() > 27) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
|
|
||||||
} else {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
return "PK_" + tableName.toUpperCase(Locale.US);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReservedWord(String objectName) {
|
|
||||||
return reservedWords.contains(objectName.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsSequences() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Oracle supports catalogs in liquibase terms
|
|
||||||
*
|
|
||||||
* @return false
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean supportsSchemas() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getConnectionCatalogName() throws DatabaseException {
|
|
||||||
if (getConnection() instanceof OfflineConnection) {
|
|
||||||
return getConnection().getCatalog();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDefaultCatalogName() {//NOPMD
|
|
||||||
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
|
|
||||||
*
|
|
||||||
* <p>Convert an ISO8601 date string to one of the following results:
|
|
||||||
* to_date('1995-05-23', 'YYYY-MM-DD')
|
|
||||||
* to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
|
|
||||||
* <p>
|
|
||||||
* Implementation restriction:<br>
|
|
||||||
* Currently, only the following subsets of ISO8601 are supported:<br>
|
|
||||||
* <ul>
|
|
||||||
* <li>YYYY-MM-DD</li>
|
|
||||||
* <li>YYYY-MM-DDThh:mm:ss</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getDateLiteral(String isoDate) {
|
|
||||||
String normalLiteral = super.getDateLiteral(isoDate);
|
|
||||||
|
|
||||||
if (isDateOnly(isoDate)) {
|
|
||||||
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
|
|
||||||
} else if (isTimeOnly(isoDate)) {
|
|
||||||
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
|
|
||||||
} else if (isTimestamp(isoDate)) {
|
|
||||||
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
|
|
||||||
} else if (isDateTime(isoDate)) {
|
|
||||||
int seppos = normalLiteral.lastIndexOf('.');
|
|
||||||
if (seppos != -1) {
|
|
||||||
normalLiteral = normalLiteral.substring(0, seppos) + "'";
|
|
||||||
}
|
|
||||||
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
|
|
||||||
}
|
|
||||||
return "UNSUPPORTED:" + isoDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSystemObject(DatabaseObject example) {
|
|
||||||
if (example == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isLiquibaseObject(example)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (example instanceof Schema) {
|
|
||||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
|
||||||
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
|
||||||
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (isSystemObject(example.getSchema())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (example instanceof Catalog) {
|
|
||||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
|
||||||
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (example.getName() != null) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("BIN$")) { //oracle deleted table
|
|
||||||
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
|
|
||||||
if (!filteredInOriginalQuery) {
|
|
||||||
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filteredInOriginalQuery) {
|
|
||||||
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
|
|
||||||
liquibase.statement.UniqueConstraint));
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("DR$")) { //oracle index tables
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
|
|
||||||
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
|
|
||||||
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
|
|
||||||
return true;
|
|
||||||
} else //noinspection HardCodedStringLiteral
|
|
||||||
if (example.getName().startsWith("USLOG$")) { //for update materialized view
|
|
||||||
return true;
|
|
||||||
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.isSystemObject(example);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsAutoIncrement() {
|
|
||||||
// Oracle supports Identity beginning with version 12c
|
|
||||||
boolean isAutoIncrementSupported = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (getDatabaseMajorVersion() >= 12) {
|
|
||||||
isAutoIncrementSupported = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returning true will generate create table command with 'IDENTITY' clause, example:
|
|
||||||
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
|
|
||||||
|
|
||||||
// While returning false will continue to generate create table command without 'IDENTITY' clause, example:
|
|
||||||
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
|
|
||||||
|
|
||||||
} catch (DatabaseException ex) {
|
|
||||||
isAutoIncrementSupported = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAutoIncrementSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
|
|
||||||
// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
|
|
||||||
//
|
|
||||||
// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
|
|
||||||
//
|
|
||||||
// UniqueConstraint constraint = null;
|
|
||||||
// for (Map map : maps) {
|
|
||||||
// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
|
|
||||||
// returnSet.add(constraint);
|
|
||||||
// Table table = new Table((String) map.get("TABLE_NAME"));
|
|
||||||
// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (constraint != null) {
|
|
||||||
// returnSet.add(constraint);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return returnSet;
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsRestrictForeignKeys() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDataTypeMaxParameters(String dataTypeName) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return super.getDataTypeMaxParameters(dataTypeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSystemTableWhereClause(String tableNameColumn) {
|
|
||||||
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
|
|
||||||
"AQ$",
|
|
||||||
"DR$",
|
|
||||||
"SYS_IOT_OVER",
|
|
||||||
"MLOG$_",
|
|
||||||
"RUPD$_",
|
|
||||||
"WM$_",
|
|
||||||
"ISEQ$$_",
|
|
||||||
"USLOG$",
|
|
||||||
"SYS_FBA"));
|
|
||||||
|
|
||||||
for (int i = 0;i<clauses.size(); i++) {
|
|
||||||
clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
|
|
||||||
}
|
|
||||||
return "("+ StringUtil.join(clauses, " AND ") + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean jdbcCallsCatalogsSchemas() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getUserDefinedTypes() {
|
|
||||||
if (userDefinedTypes == null) {
|
|
||||||
userDefinedTypes = new HashSet<>();
|
|
||||||
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
|
|
||||||
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
|
|
||||||
}
|
|
||||||
} catch (DatabaseException e) {
|
|
||||||
//ignore error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return userDefinedTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
|
|
||||||
return databaseFunction.toString();
|
|
||||||
}
|
|
||||||
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
|
|
||||||
SequenceCurrentValueFunction)) {
|
|
||||||
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
|
|
||||||
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
|
|
||||||
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.generateDatabaseFunctionValue(databaseFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ValidationErrors validate() {
|
|
||||||
ValidationErrors errors = super.validate();
|
|
||||||
DatabaseConnection connection = getConnection();
|
|
||||||
if ((connection == null) || (connection instanceof OfflineConnection)) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canAccessDbaRecycleBin()) {
|
|
||||||
errors.addWarning(getDbaRecycleBinWarning());
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDbaRecycleBinWarning() {
|
|
||||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
|
|
||||||
// HardCodedStringLiteral
|
|
||||||
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
|
|
||||||
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
|
|
||||||
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
|
|
||||||
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
|
|
||||||
" issue.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
|
|
||||||
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
|
|
||||||
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
|
|
||||||
"\n" +
|
|
||||||
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canAccessDbaRecycleBin() {
|
|
||||||
if (canAccessDbaRecycleBin == null) {
|
|
||||||
DatabaseConnection connection = getConnection();
|
|
||||||
if ((connection == null) || (connection instanceof OfflineConnection)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Statement statement = null;
|
|
||||||
try {
|
|
||||||
statement = ((JdbcConnection) connection).createStatement();
|
|
||||||
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
|
|
||||||
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
|
|
||||||
this.canAccessDbaRecycleBin = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
|
|
||||||
this.canAccessDbaRecycleBin = false;
|
|
||||||
} else {
|
|
||||||
//noinspection HardCodedStringLiteral
|
|
||||||
Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
|
|
||||||
this.canAccessDbaRecycleBin = false;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
JdbcUtils.close(null, statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return canAccessDbaRecycleBin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsNotNullConstraintNames() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
|
|
||||||
* the following form (case-insensitive comparison):
|
|
||||||
* 1st character: A-Z
|
|
||||||
* 2..n characters: A-Z0-9$_#
|
|
||||||
* The maximum length of an identifier differs by Oracle version and object type.
|
|
||||||
*/
|
|
||||||
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
|
|
||||||
if ((identifier == null) || (identifier.length() < 1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
|
|
||||||
* we do know seem to be supported as 12cR2 long identifiers, so:
|
|
||||||
*/
|
|
||||||
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
|
|
||||||
* is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
|
|
||||||
* object types).
|
|
||||||
*
|
|
||||||
* @return the maximum length of an object identifier, in bytes
|
|
||||||
*/
|
|
||||||
public int getIdentifierMaximumLength() {
|
|
||||||
try {
|
|
||||||
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
|
|
||||||
return SHORT_IDENTIFIERS_LENGTH;
|
|
||||||
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
|
|
||||||
return SHORT_IDENTIFIERS_LENGTH;
|
|
||||||
} else {
|
|
||||||
return LONG_IDENTIFIERS_LEGNTH;
|
|
||||||
}
|
|
||||||
} catch (DatabaseException ex) {
|
|
||||||
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
package liquibase.datatype.core;
|
|
||||||
|
|
||||||
import liquibase.change.core.LoadDataChange;
|
|
||||||
import liquibase.database.Database;
|
|
||||||
import liquibase.database.core.*;
|
|
||||||
import liquibase.datatype.DataTypeInfo;
|
|
||||||
import liquibase.datatype.DatabaseDataType;
|
|
||||||
import liquibase.datatype.LiquibaseDataType;
|
|
||||||
import liquibase.exception.UnexpectedLiquibaseException;
|
|
||||||
import liquibase.statement.DatabaseFunction;
|
|
||||||
import liquibase.util.StringUtil;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
|
|
||||||
public class BooleanType extends LiquibaseDataType {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DatabaseDataType toDatabaseDataType(Database database) {
|
|
||||||
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
|
|
||||||
if ((database instanceof Firebird3Database)) {
|
|
||||||
return new DatabaseDataType("BOOLEAN");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
|
|
||||||
return new DatabaseDataType("SMALLINT");
|
|
||||||
} else if (database instanceof MSSQLDatabase) {
|
|
||||||
return new DatabaseDataType(database.escapeDataTypeName("bit"));
|
|
||||||
} else if (database instanceof MySQLDatabase) {
|
|
||||||
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
|
|
||||||
return new DatabaseDataType("BIT", getParameters());
|
|
||||||
}
|
|
||||||
return new DatabaseDataType("BIT", 1);
|
|
||||||
} else if (database instanceof OracleDatabase) {
|
|
||||||
return new DatabaseDataType("NUMBER", 1);
|
|
||||||
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
|
|
||||||
return new DatabaseDataType("BIT");
|
|
||||||
} else if (database instanceof DerbyDatabase) {
|
|
||||||
if (((DerbyDatabase) database).supportsBooleanDataType()) {
|
|
||||||
return new DatabaseDataType("BOOLEAN");
|
|
||||||
} else {
|
|
||||||
return new DatabaseDataType("SMALLINT");
|
|
||||||
}
|
|
||||||
} else if (database instanceof DB2Database) {
|
|
||||||
if (((DB2Database) database).supportsBooleanDataType())
|
|
||||||
return new DatabaseDataType("BOOLEAN");
|
|
||||||
else
|
|
||||||
return new DatabaseDataType("SMALLINT");
|
|
||||||
} else if (database instanceof HsqlDatabase) {
|
|
||||||
return new DatabaseDataType("BOOLEAN");
|
|
||||||
} else if (database instanceof PostgresDatabase) {
|
|
||||||
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
|
|
||||||
return new DatabaseDataType("BIT", getParameters());
|
|
||||||
}
|
|
||||||
} else if (database instanceof DmDatabase) { // dhb52: DM Support
|
|
||||||
return new DatabaseDataType("bit");
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.toDatabaseDataType(database);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String objectToSql(Object value, Database database) {
|
|
||||||
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String returnValue;
|
|
||||||
if (value instanceof String) {
|
|
||||||
value = ((String) value).replaceAll("'", "");
|
|
||||||
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
|
|
||||||
returnValue = this.getTrueBooleanValue(database);
|
|
||||||
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
|
|
||||||
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
|
|
||||||
returnValue = this.getFalseBooleanValue(database);
|
|
||||||
} else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
|
|
||||||
returnValue = "b'"
|
|
||||||
+ value.toString()
|
|
||||||
.replace("b", "")
|
|
||||||
.replace("\"", "")
|
|
||||||
.replace("::it", "")
|
|
||||||
+ "'::\"bit\"";
|
|
||||||
} else {
|
|
||||||
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
|
|
||||||
}
|
|
||||||
} else if (value instanceof Long) {
|
|
||||||
if (Long.valueOf(1).equals(value)) {
|
|
||||||
returnValue = this.getTrueBooleanValue(database);
|
|
||||||
} else {
|
|
||||||
returnValue = this.getFalseBooleanValue(database);
|
|
||||||
}
|
|
||||||
} else if (value instanceof Number) {
|
|
||||||
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
|
|
||||||
returnValue = this.getTrueBooleanValue(database);
|
|
||||||
} else {
|
|
||||||
returnValue = this.getFalseBooleanValue(database);
|
|
||||||
}
|
|
||||||
} else if (value instanceof DatabaseFunction) {
|
|
||||||
return value.toString();
|
|
||||||
} else if (value instanceof Boolean) {
|
|
||||||
if (((Boolean) value)) {
|
|
||||||
returnValue = this.getTrueBooleanValue(database);
|
|
||||||
} else {
|
|
||||||
returnValue = this.getFalseBooleanValue(database);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isNumericBoolean(Database database) {
|
|
||||||
if (database instanceof Firebird3Database) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (database instanceof DerbyDatabase) {
|
|
||||||
return !((DerbyDatabase) database).supportsBooleanDataType();
|
|
||||||
} else if (database instanceof DB2Database) {
|
|
||||||
return !((DB2Database) database).supportsBooleanDataType();
|
|
||||||
}
|
|
||||||
return (database instanceof Db2zDatabase)
|
|
||||||
|| (database instanceof FirebirdDatabase)
|
|
||||||
|| (database instanceof MSSQLDatabase)
|
|
||||||
|| (database instanceof MySQLDatabase)
|
|
||||||
|| (database instanceof OracleDatabase)
|
|
||||||
|| (database instanceof SQLiteDatabase)
|
|
||||||
|| (database instanceof SybaseASADatabase)
|
|
||||||
|| (database instanceof SybaseDatabase)
|
|
||||||
|| (database instanceof DmDatabase); // dhb52: DM Support
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database-specific value to use for "false" "boolean" columns.
|
|
||||||
*/
|
|
||||||
public String getFalseBooleanValue(Database database) {
|
|
||||||
if (isNumericBoolean(database)) {
|
|
||||||
return "0";
|
|
||||||
}
|
|
||||||
if (database instanceof InformixDatabase) {
|
|
||||||
return "'f'";
|
|
||||||
}
|
|
||||||
return "FALSE";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database-specific value to use for "true" "boolean" columns.
|
|
||||||
*/
|
|
||||||
public String getTrueBooleanValue(Database database) {
|
|
||||||
if (isNumericBoolean(database)) {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
if (database instanceof InformixDatabase) {
|
|
||||||
return "'t'";
|
|
||||||
}
|
|
||||||
return "TRUE";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
|
|
||||||
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +0,0 @@
|
||||||
防止IDEA将`.`和`/`混为一谈
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
liquibase.database.core.CockroachDatabase
|
|
||||||
liquibase.database.core.DB2Database
|
|
||||||
liquibase.database.core.Db2zDatabase
|
|
||||||
liquibase.database.core.DerbyDatabase
|
|
||||||
liquibase.database.core.Firebird3Database
|
|
||||||
liquibase.database.core.FirebirdDatabase
|
|
||||||
liquibase.database.core.H2Database
|
|
||||||
liquibase.database.core.HsqlDatabase
|
|
||||||
liquibase.database.core.InformixDatabase
|
|
||||||
liquibase.database.core.Ingres9Database
|
|
||||||
liquibase.database.core.MSSQLDatabase
|
|
||||||
liquibase.database.core.MariaDBDatabase
|
|
||||||
liquibase.database.core.MockDatabase
|
|
||||||
liquibase.database.core.MySQLDatabase
|
|
||||||
liquibase.database.core.OracleDatabase
|
|
||||||
liquibase.database.core.PostgresDatabase
|
|
||||||
liquibase.database.core.SQLiteDatabase
|
|
||||||
liquibase.database.core.SybaseASADatabase
|
|
||||||
liquibase.database.core.SybaseDatabase
|
|
||||||
liquibase.database.core.DmDatabase
|
|
||||||
liquibase.database.core.UnsupportedDatabase
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2.5.0-SNAPSHOT</revision>
|
<revision>2.6.0-SNAPSHOT</revision>
|
||||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||||
<!-- 统一依赖管理 -->
|
<!-- 统一依赖管理 -->
|
||||||
<spring.boot.version>3.4.5</spring.boot.version>
|
<spring.boot.version>3.4.5</spring.boot.version>
|
||||||
|
|
@ -377,14 +377,6 @@
|
||||||
<version>${spring-boot-admin.version}</version>
|
<version>${spring-boot-admin.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
<version>${revision}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-inline</artifactId>
|
<artifactId>mockito-inline</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
<module>yudao-spring-boot-starter-mq</module>
|
<module>yudao-spring-boot-starter-mq</module>
|
||||||
|
|
||||||
<module>yudao-spring-boot-starter-excel</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-tenant</module>
|
||||||
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -33,13 +33,6 @@
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,540 +0,0 @@
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -43,12 +43,6 @@
|
||||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
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), "蒙古");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -66,13 +66,6 @@
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 工具类相关 -->
|
<!-- 工具类相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,6 @@
|
||||||
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
||||||
<optional>true</optional> <!-- 设置为 optional,只有在 AreaConvert 的时候使用 -->
|
<optional>true</optional> <!-- 设置为 optional,只有在 AreaConvert 的时候使用 -->
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
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", "狗"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -54,7 +54,7 @@ public abstract class BaseDO implements Serializable, TransPojo {
|
||||||
private Boolean deleted;
|
private Boolean deleted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把 creator、createTime、updateTime、updater 都清空,避免前端直接传递 creator 之类的字段,直接就被更新了。
|
* 把 creator、createTime、updateTime、updater 都清空,避免前端直接传递 creator 之类的字段,直接就被更新了
|
||||||
*/
|
*/
|
||||||
public void clean(){
|
public void clean(){
|
||||||
this.creator = null;
|
this.creator = null;
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,6 @@
|
||||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
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 + "×tamp=" + 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<groupId>cn.iocoder.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>
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
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 {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
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 {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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 {
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
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 {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* 提供单元测试 Unit Test 的基类
|
|
||||||
*/
|
|
||||||
package cn.iocoder.yudao.framework.test.core.ut;
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
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(), "错误提示不匹配");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
// 如果是 type、status 结尾的字段,返回 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 为零的原因,避免 MySQL、H2 存储不到时间戳
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* 测试组件,用于单元测试、集成测试等等
|
|
||||||
*/
|
|
||||||
package cn.iocoder.yudao.framework.test;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<https://www.iocoder.cn/Spring-Boot/Unit-Test/?yudao>
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
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 "*";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -60,12 +60,6 @@
|
||||||
<artifactId>yudao-spring-boot-starter-job</artifactId>
|
<artifactId>yudao-spring-boot-starter-job</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Test 测试相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 工具类相关 -->
|
<!-- 工具类相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
|
||||||
if (CollUtil.isEmpty(list)) {
|
if (CollUtil.isEmpty(list)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chatConversationMapper.deleteBatchIds(convertList(list, AiChatConversationDO::getId));
|
chatConversationMapper.deleteByIds(convertList(list, AiChatConversationDO::getId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -339,7 +339,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||||
throw exception(CHAT_MESSAGE_NOT_EXIST);
|
throw exception(CHAT_MESSAGE_NOT_EXIST);
|
||||||
}
|
}
|
||||||
// 2. 执行删除
|
// 2. 执行删除
|
||||||
chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
|
chatMessageMapper.deleteByIds(convertList(messages, AiChatMessageDO::getId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ public class AiImageServiceImpl implements AiImageService {
|
||||||
if (CollUtil.isEmpty(ids)) {
|
if (CollUtil.isEmpty(ids)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return imageMapper.selectBatchIds(ids);
|
return imageMapper.selectByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ public class AiKnowledgeDocumentServiceImpl implements AiKnowledgeDocumentServic
|
||||||
if (CollUtil.isEmpty(ids)) {
|
if (CollUtil.isEmpty(ids)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return knowledgeDocumentMapper.selectBatchIds(ids);
|
return knowledgeDocumentMapper.selectByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
|
||||||
if (CollUtil.isEmpty(ids)) {
|
if (CollUtil.isEmpty(ids)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return segmentMapper.selectBatchIds(ids);
|
return segmentMapper.selectByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
|
||||||
if (CollUtil.isEmpty(ids)) {
|
if (CollUtil.isEmpty(ids)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return chatRoleMapper.selectBatchIds(ids);
|
return chatRoleMapper.selectByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ public class AiToolServiceImpl implements AiToolService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AiToolDO> getToolList(Collection<Long> ids) {
|
public List<AiToolDO> getToolList(Collection<Long> ids) {
|
||||||
return toolMapper.selectBatchIds(ids);
|
return toolMapper.selectByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -97,4 +97,4 @@ public class AiToolServiceImpl implements AiToolService {
|
||||||
return toolMapper.selectListByStatus(status);
|
return toolMapper.selectListByStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ public class AiUtils {
|
||||||
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens,
|
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens,
|
||||||
Set<String> toolNames, Map<String, Object> toolContext) {
|
Set<String> toolNames, Map<String, Object> toolContext) {
|
||||||
toolNames = ObjUtil.defaultIfNull(toolNames, Collections.emptySet());
|
toolNames = ObjUtil.defaultIfNull(toolNames, Collections.emptySet());
|
||||||
|
toolContext = ObjUtil.defaultIfNull(toolContext, Collections.emptyMap());
|
||||||
// noinspection EnhancedSwitchMigration
|
// noinspection EnhancedSwitchMigration
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case TONG_YI:
|
case TONG_YI:
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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() {
|
|
||||||
// 准备参数
|
|
||||||
// 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
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\nQQ:123456789";
|
|
||||||
}
|
|
||||||
|
|
||||||
@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硕士:软件工程专业,清华大学";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,315 +0,0 @@
|
||||||
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技术改变世界,共创美好未来。
|
|
||||||
""";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,319 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<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>
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* bpm API 包,定义并实现提供给其它模块的 API
|
|
||||||
*/
|
|
||||||
package cn.iocoder.yudao.module.bpm.api;
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
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
|
|
||||||
*
|
|
||||||
* key:taskKey 任务编码
|
|
||||||
* value:审批人的数组
|
|
||||||
* 例如:{ taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
|
|
||||||
*/
|
|
||||||
private Map<String, List<Long>> startUserSelectAssignees;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* 基础包,放一些通用的 VO 类
|
|
||||||
*/
|
|
||||||
package cn.iocoder.yudao.module.bpm.controller.admin.base;
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
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())));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
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 -> // 只返回 id、name 字段
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
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 Map、Dept 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
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 返回,只返回 id、name、key
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
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())));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
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
Loading…
Reference in New Issue