Compare commits
68 Commits
d2dd2f10f5
...
bf6875adf6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf6875adf6 | ||
|
|
50eb97dd17 | ||
|
|
eb246a2df4 | ||
|
|
0eed2fa74c | ||
|
|
3accf80700 | ||
|
|
9fe59e8543 | ||
|
|
9519ca969c | ||
|
|
36708447ff | ||
|
|
0cbced1cef | ||
|
|
ab8e68d3a2 | ||
|
|
dca1682386 | ||
|
|
07055e7076 | ||
|
|
2bc0df6819 | ||
|
|
33de41b4da | ||
|
|
3ae3075f25 | ||
|
|
aa6048de94 | ||
|
|
342a8177a1 | ||
|
|
179cec535c | ||
|
|
8ee31a9bf9 | ||
|
|
58165ef371 | ||
|
|
75c1706b97 | ||
|
|
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 |
2
pom.xml
2
pom.xml
@ -32,7 +32,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2025.11-SNAPSHOT</revision>
|
||||
<revision>2025.12-SNAPSHOT</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>17</java.version>
|
||||
<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
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2025.11-SNAPSHOT</revision>
|
||||
<revision>2025.12-SNAPSHOT</revision>
|
||||
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>3.5.9</spring.boot.version>
|
||||
@ -75,6 +75,7 @@
|
||||
<jimureport.version>2.1.3</jimureport.version>
|
||||
<jimubi.version>2.3.0</jimubi.version>
|
||||
<weixin-java.version>4.7.9-20251224.161447</weixin-java.version>
|
||||
<alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -382,14 +383,6 @@
|
||||
<version>${spring-boot-admin.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<version>${revision}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
@ -579,6 +572,18 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-sdk-java</artifactId>
|
||||
<version>${alipay-sdk-java.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
<module>yudao-spring-boot-starter-mq</module>
|
||||
|
||||
<module>yudao-spring-boot-starter-excel</module>
|
||||
<module>yudao-spring-boot-starter-test</module>
|
||||
|
||||
<module>yudao-spring-boot-starter-biz-tenant</module>
|
||||
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
||||
|
||||
@ -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>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -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,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</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>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@ -62,13 +62,6 @@
|
||||
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
||||
<optional>true</optional> <!-- 设置为 optional,只有在 AreaConvert 的时候使用 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -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", "狗"));
|
||||
}
|
||||
|
||||
}
|
||||
@ -35,13 +35,6 @@
|
||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.encrypt;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import cn.hutool.crypto.asymmetric.RSA;
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 各种 API 加解密的测试类:不是单测,而是方便大家生成密钥、加密、解密等操作。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@SuppressWarnings("ConstantValue")
|
||||
public class ApiEncryptTest {
|
||||
|
||||
@Test
|
||||
public void testGenerateAsymmetric() {
|
||||
String asymmetricAlgorithm = AsymmetricAlgorithm.RSA.getValue();
|
||||
// String asymmetricAlgorithm = "SM2";
|
||||
// String asymmetricAlgorithm = SM4.ALGORITHM_NAME;
|
||||
// String asymmetricAlgorithm = SymmetricAlgorithm.AES.getValue();
|
||||
String requestClientKey = null;
|
||||
String requestServerKey = null;
|
||||
String responseClientKey = null;
|
||||
String responseServerKey = null;
|
||||
if (Objects.equals(asymmetricAlgorithm, AsymmetricAlgorithm.RSA.getValue())) {
|
||||
// 请求的密钥
|
||||
RSA requestRsa = SecureUtil.rsa();
|
||||
requestClientKey = requestRsa.getPublicKeyBase64();
|
||||
requestServerKey = requestRsa.getPrivateKeyBase64();
|
||||
// 响应的密钥
|
||||
RSA responseRsa = new RSA();
|
||||
responseClientKey = responseRsa.getPrivateKeyBase64();
|
||||
responseServerKey = responseRsa.getPublicKeyBase64();
|
||||
} else if (Objects.equals(asymmetricAlgorithm, SymmetricAlgorithm.AES.getValue())) {
|
||||
// AES 密钥可选 32、24、16 位
|
||||
// 请求的密钥(前后端密钥一致)
|
||||
requestClientKey = RandomUtil.randomNumbers(32);
|
||||
requestServerKey = requestClientKey;
|
||||
// 响应的密钥(前后端密钥一致)
|
||||
responseClientKey = RandomUtil.randomNumbers(32);
|
||||
responseServerKey = responseClientKey;
|
||||
}
|
||||
|
||||
// 打印结果
|
||||
System.out.println("requestClientKey = " + requestClientKey);
|
||||
System.out.println("requestServerKey = " + requestServerKey);
|
||||
System.out.println("responseClientKey = " + responseClientKey);
|
||||
System.out.println("responseServerKey = " + responseServerKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncrypt_aes() {
|
||||
String key = "52549111389893486934626385991395";
|
||||
String body = "{\n" +
|
||||
" \"username\": \"admin\",\n" +
|
||||
" \"password\": \"admin123\",\n" +
|
||||
" \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" +
|
||||
" \"code\": \"1024\"\n" +
|
||||
"}";
|
||||
String encrypt = SecureUtil.aes(StrUtil.utf8Bytes(key))
|
||||
.encryptBase64(body);
|
||||
System.out.println("encrypt = " + encrypt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncrypt_rsa() {
|
||||
String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB";
|
||||
String body = "{\n" +
|
||||
" \"username\": \"admin\",\n" +
|
||||
" \"password\": \"admin123\",\n" +
|
||||
" \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" +
|
||||
" \"code\": \"1024\"\n" +
|
||||
"}";
|
||||
String encrypt = SecureUtil.rsa(null, key)
|
||||
.encryptBase64(body, KeyType.PublicKey);
|
||||
System.out.println("encrypt = " + encrypt);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,257 +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</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>yudao-module-ai</artifactId>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。
|
||||
目前已接入各种模型,不限于:
|
||||
国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek
|
||||
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
||||
</description>
|
||||
<properties>
|
||||
<spring-ai.version>1.1.2</spring-ai.version>
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
|
||||
<alibaba-ai.version>1.1.0.0-RC2</alibaba-ai.version>
|
||||
<tinyflow.version>1.2.6</tinyflow.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-infra</artifactId>
|
||||
<version>${revision}</version>
|
||||
</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-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-job</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring AI Model 模型接入 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-azure-openai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-anthropic</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-stability-ai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 智谱 GLM -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-minimax</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 通义千问 -->
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||
<version>${alibaba-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 文心一言 -->
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>qianfan-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 月之暗面 -->
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>moonshot-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
|
||||
<dependency>
|
||||
<!-- Qdrant:https://qdrant.tech/ -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- Milvus:https://milvus.io/ -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<exclusions>
|
||||
<!-- 解决和 logback 的日志冲突 -->
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-reload4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- Tika:负责内容的解析 -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-tika-document-reader</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<!-- TODO 芋艿:boot 项目里,不引入 cloud 依赖!!!另外,这样也是为了解决启动报错的问题! -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-cloud-function-context</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>spring-cloud-function-core</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- MCP 相关 -->
|
||||
<!--
|
||||
特殊说明:不能使用 spring-ai-starter-mcp-server-webflux 或 spring-ai-starter-mcp-client-webflux !!!
|
||||
原因:项目使用了 SpringMVC,而不是 WebFlux。引入上述 2 个,会导致 SSE Server 失效。
|
||||
-->
|
||||
<dependency>
|
||||
<!-- 服务端 -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 客户端 -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-client</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TinyFlow:AI 工作流 -->
|
||||
<dependency>
|
||||
<groupId>dev.tinyflow</groupId>
|
||||
<artifactId>tinyflow-java-core</artifactId>
|
||||
<version>${tinyflow.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.jfinal</groupId>
|
||||
<artifactId>enjoy</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<!-- 解决 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1318/ 问题 -->
|
||||
<groupId>com.agentsflex</groupId>
|
||||
<artifactId>agents-flex-store-elasticsearch</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<!-- 解决 https://t.zsxq.com/pCBZC 问题 -->
|
||||
<groupId>com.agentsflex</groupId>
|
||||
<artifactId>agents-flex-search-engine-es</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<!-- TODO @芋艿:暂时移除 groovy,和 iot 冲突 -->
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
<!-- 解决和 logback 的日志冲突 -->
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-reload4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -1,118 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
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.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
|
||||
import com.fhs.core.trans.anno.TransMethodResult;
|
||||
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 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.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - AI 聊天对话")
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat/conversation")
|
||||
@Validated
|
||||
public class AiChatConversationController {
|
||||
|
||||
@Resource
|
||||
private AiChatConversationService chatConversationService;
|
||||
@Resource
|
||||
private AiChatMessageService chatMessageService;
|
||||
|
||||
@PostMapping("/create-my")
|
||||
@Operation(summary = "创建【我的】聊天对话")
|
||||
public CommonResult<Long> createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) {
|
||||
return success(chatConversationService.createChatConversationMy(createReqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@PutMapping("/update-my")
|
||||
@Operation(summary = "更新【我的】聊天对话")
|
||||
public CommonResult<Boolean> updateChatConversationMy(@RequestBody @Valid AiChatConversationUpdateMyReqVO updateReqVO) {
|
||||
chatConversationService.updateChatConversationMy(updateReqVO, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/my-list")
|
||||
@Operation(summary = "获得【我的】聊天对话列表")
|
||||
@TransMethodResult
|
||||
public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() {
|
||||
List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
|
||||
return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-my")
|
||||
@Operation(summary = "获得【我的】聊天对话")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
@TransMethodResult
|
||||
public CommonResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
|
||||
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
|
||||
if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
|
||||
conversation = null;
|
||||
}
|
||||
return success(BeanUtils.toBean(conversation, AiChatConversationRespVO.class));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-my")
|
||||
@Operation(summary = "删除聊天对话")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
|
||||
chatConversationService.deleteChatConversationMy(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-by-unpinned")
|
||||
@Operation(summary = "删除未置顶的聊天对话")
|
||||
public CommonResult<Boolean> deleteChatConversationMyByUnpinned() {
|
||||
chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ========== 对话管理 ==========
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
|
||||
@TransMethodResult
|
||||
public CommonResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
|
||||
PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty());
|
||||
}
|
||||
// 拼接关联数据
|
||||
Map<Long, Integer> messageCountMap = chatMessageService.getChatMessageCountMap(
|
||||
convertList(pageResult.getList(), AiChatConversationDO::getId));
|
||||
return success(BeanUtils.toBean(pageResult, AiChatConversationRespVO.class,
|
||||
conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0))));
|
||||
}
|
||||
|
||||
@Operation(summary = "管理员删除对话")
|
||||
@DeleteMapping("/delete-by-admin")
|
||||
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:delete')")
|
||||
public CommonResult<Boolean> deleteChatConversationByAdmin(@RequestParam("id") Long id) {
|
||||
chatConversationService.deleteChatConversationByAdmin(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
### 发送消息(段式)
|
||||
POST {{baseUrl}}/ai/chat/message/send
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"conversationId": "1781604279872581724",
|
||||
"content": "你是 OpenAI 么?"
|
||||
}
|
||||
|
||||
### 发送消息(流式)
|
||||
POST {{baseUrl}}/ai/chat/message/send-stream
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"conversationId": "1781604279872581724",
|
||||
"content": "1+1=?"
|
||||
}
|
||||
|
||||
### 发送消息(流式)【带文件】
|
||||
POST {{baseUrl}}/ai/chat/message/send-stream
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"conversationId": "1781604279872581797",
|
||||
"content": "图片里有什么?",
|
||||
"attachmentUrls": ["http://test.yudao.iocoder.cn/1755531278.jpeg"]
|
||||
}
|
||||
|
||||
### 发送消息(流式)【追问带文件】
|
||||
POST {{baseUrl}}/ai/chat/message/send-stream
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"conversationId": "1781604279872581799",
|
||||
"content": "说下图片里,有哪些字?",
|
||||
"useContext": true
|
||||
}
|
||||
|
||||
### 发送消息(流式)【联网搜索】
|
||||
POST {{baseUrl}}/ai/chat/message/send-stream
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"conversationId": "1781604279872581799",
|
||||
"content": "今天是周几?",
|
||||
"useSearch": true
|
||||
}
|
||||
|
||||
### 获得指定对话的消息列表
|
||||
GET {{baseUrl}}/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581799
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
### 删除消息
|
||||
DELETE {{baseUrl}}/ai/chat/message/delete?id=50
|
||||
Authorization: {{token}}
|
||||
@ -1,157 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
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.*;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - 聊天消息")
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat/message")
|
||||
@Slf4j
|
||||
public class AiChatMessageController {
|
||||
|
||||
@Resource
|
||||
private AiChatMessageService chatMessageService;
|
||||
@Resource
|
||||
private AiChatConversationService chatConversationService;
|
||||
@Resource
|
||||
private AiChatRoleService chatRoleService;
|
||||
@Resource
|
||||
private AiKnowledgeSegmentService knowledgeSegmentService;
|
||||
@Resource
|
||||
private AiKnowledgeDocumentService knowledgeDocumentService;
|
||||
|
||||
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
|
||||
@PostMapping("/send")
|
||||
public CommonResult<AiChatMessageSendRespVO> sendMessage(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
|
||||
return success(chatMessageService.sendMessage(sendReqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
|
||||
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
|
||||
return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
|
||||
}
|
||||
|
||||
@Operation(summary = "获得指定对话的消息列表")
|
||||
@GetMapping("/list-by-conversation-id")
|
||||
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
|
||||
public CommonResult<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
|
||||
@RequestParam("conversationId") Long conversationId) {
|
||||
AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
|
||||
if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 1. 获取消息列表
|
||||
List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
|
||||
if (CollUtil.isEmpty(messageList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// 2. 拼接数据,主要是知识库段落信息
|
||||
Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
|
||||
message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
|
||||
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
|
||||
convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
|
||||
List<AiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AiChatMessageRespVO.class);
|
||||
for (int i = 0; i < messageList.size(); i++) {
|
||||
AiChatMessageDO message = messageList.get(i);
|
||||
if (CollUtil.isEmpty(message.getSegmentIds())) {
|
||||
continue;
|
||||
}
|
||||
// 设置知识库段落信息
|
||||
messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
|
||||
AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
|
||||
if (segment == null) {
|
||||
return null;
|
||||
}
|
||||
AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
return new AiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
|
||||
.setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
|
||||
}));
|
||||
}
|
||||
return success(messageVOList);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除消息")
|
||||
@DeleteMapping("/delete")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
|
||||
chatMessageService.deleteChatMessage(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除指定对话的消息")
|
||||
@DeleteMapping("/delete-by-conversation-id")
|
||||
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
|
||||
chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ========== 对话管理 ==========
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得消息分页", description = "用于【对话管理】菜单")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
|
||||
public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) {
|
||||
PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty());
|
||||
}
|
||||
// 拼接数据
|
||||
Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(
|
||||
convertSet(pageResult.getList(), AiChatMessageDO::getRoleId));
|
||||
return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class,
|
||||
respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(),
|
||||
role -> respVO.setRoleName(role.getName()))));
|
||||
}
|
||||
|
||||
@Operation(summary = "管理员删除消息")
|
||||
@DeleteMapping("/delete-by-admin")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-message:delete')")
|
||||
public CommonResult<Boolean> deleteChatMessageByAdmin(@RequestParam("id") Long id) {
|
||||
chatMessageService.deleteChatMessageByAdmin(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天对话创建【我的】 Request VO")
|
||||
@Data
|
||||
public class AiChatConversationCreateMyReqVO {
|
||||
|
||||
@Schema(description = "聊天角色编号", example = "666")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "知识库编号", example = "1204")
|
||||
private Long knowledgeId;
|
||||
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天对话的分页 Request VO")
|
||||
@Data
|
||||
public class AiChatConversationPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "用户编号", example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "对话标题", example = "你好")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天对话 Response VO")
|
||||
@Data
|
||||
public class AiChatConversationRespVO implements VO {
|
||||
|
||||
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "角色编号", example = "1")
|
||||
@Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"})
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "角色设定", example = "一个快乐的程序员")
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "温度参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
|
||||
private Double temperature;
|
||||
|
||||
@Schema(description = "单条回复的最大 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
|
||||
private Integer maxTokens;
|
||||
|
||||
@Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer maxContexts;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// ========== 关联 role 信息 ==========
|
||||
|
||||
@Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png")
|
||||
private String roleAvatar;
|
||||
|
||||
@Schema(description = "角色名字", example = "小黄")
|
||||
private String roleName;
|
||||
|
||||
// ========== 仅在【对话管理】时加载 ==========
|
||||
|
||||
@Schema(description = "消息数量", example = "20")
|
||||
private Integer messageCount;
|
||||
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天对话更新【我的】 Request VO")
|
||||
@Data
|
||||
public class AiChatConversationUpdateMyReqVO {
|
||||
|
||||
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "对话编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "对话标题", example = "我是一个标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "是否置顶", example = "true")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(description = "模型编号", example = "1")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "知识库编号", example = "1")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "角色设定", example = "一个快乐的程序员")
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "温度参数", example = "0.8")
|
||||
private Double temperature;
|
||||
|
||||
@Schema(description = "单条回复的最大 Token 数量", example = "4096")
|
||||
private Integer maxTokens;
|
||||
|
||||
@Schema(description = "上下文的最大 Message 数量", example = "10")
|
||||
private Integer maxContexts;
|
||||
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天消息的分页 Request VO")
|
||||
@Data
|
||||
public class AiChatMessagePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "对话编号", example = "2048")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(description = "用户编号", example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "消息内容", example = "你好")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天消息 Response VO")
|
||||
@Data
|
||||
public class AiChatMessageRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(description = "回复消息编号", example = "1024")
|
||||
private Long replyId;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
|
||||
private String type; // 参见 MessageType 枚举类
|
||||
|
||||
@Schema(description = "用户编号", example = "4096")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "角色编号", example = "888")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
|
||||
private String reasoningContent;
|
||||
|
||||
@Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean useContext;
|
||||
|
||||
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
|
||||
private List<Long> segmentIds;
|
||||
|
||||
@Schema(description = "知识库段落数组")
|
||||
private List<KnowledgeSegment> segments;
|
||||
|
||||
@Schema(description = "联网搜索的网页内容数组")
|
||||
private List<AiWebSearchResponse.WebPage> webSearchPages;
|
||||
|
||||
@Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png")
|
||||
private List<String> attachmentUrls;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// ========== 仅在【对话管理】时加载 ==========
|
||||
|
||||
@Schema(description = "角色名字", example = "小黄")
|
||||
private String roleName;
|
||||
|
||||
@Schema(description = "知识库段落", example = "Java 开发手册")
|
||||
@Data
|
||||
public static class KnowledgeSegment {
|
||||
|
||||
@Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
|
||||
private String documentName;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天消息发送 Request VO")
|
||||
@Data
|
||||
public class AiChatMessageSendReqVO {
|
||||
|
||||
@Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "聊天对话编号不能为空")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法")
|
||||
@NotEmpty(message = "聊天内容不能为空")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "是否携带上下文", example = "true")
|
||||
private Boolean useContext;
|
||||
|
||||
@Schema(description = "是否联网搜索", example = "true")
|
||||
private Boolean useSearch;
|
||||
|
||||
@Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png")
|
||||
private List<String> attachmentUrls;
|
||||
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天消息发送 Response VO")
|
||||
@Data
|
||||
public class AiChatMessageSendRespVO {
|
||||
|
||||
@Schema(description = "发送消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Message send;
|
||||
|
||||
@Schema(description = "接收消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Message receive;
|
||||
|
||||
@Schema(description = "消息")
|
||||
@Data
|
||||
public static class Message {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
|
||||
private String type; // 参见 MessageType 枚举类
|
||||
|
||||
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "推理内容", example = "要达到这个目标,你需要...")
|
||||
private String reasoningContent;
|
||||
|
||||
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
|
||||
private List<Long> segmentIds;
|
||||
|
||||
@Schema(description = "知识库段落数组")
|
||||
private List<AiChatMessageRespVO.KnowledgeSegment> segments;
|
||||
|
||||
@Schema(description = "联网搜索的网页内容数组")
|
||||
private List<AiWebSearchResponse.WebPage> webSearchPages;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
### 生成图片:OpenAI(DALL)
|
||||
POST {{baseUrl}}/ai/image/draw
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"platform": "OpenAI",
|
||||
"prompt": "可爱的小喵星人",
|
||||
"model": "dall-e-3",
|
||||
"height": "1024",
|
||||
"width": "1024",
|
||||
"options": {
|
||||
"style": "vivid"
|
||||
}
|
||||
}
|
||||
|
||||
### 生成图片:StableDiffusion
|
||||
POST {{baseUrl}}/ai/image/draw
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"platform": "StableDiffusion",
|
||||
"prompt": "中国长城",
|
||||
"model": "stable-diffusion-v1-6",
|
||||
"height": "1024",
|
||||
"width": "1024",
|
||||
"style": "vivid"
|
||||
}
|
||||
|
||||
### 生成图片:生成图片(Midjourney)
|
||||
POST {{baseUrl}}/ai/image/midjourney/imagine
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"prompt": "中国旗袍",
|
||||
"model": "midjourney",
|
||||
"width": "1",
|
||||
"height": "1",
|
||||
"version": "6.0"
|
||||
}
|
||||
@ -1,139 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
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.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
|
||||
import cn.iocoder.yudao.module.ai.service.image.AiImageService;
|
||||
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.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - AI 绘画")
|
||||
@RestController
|
||||
@RequestMapping("/ai/image")
|
||||
@Slf4j
|
||||
public class AiImageController {
|
||||
|
||||
@Resource
|
||||
private AiImageService imageService;
|
||||
|
||||
@GetMapping("/my-page")
|
||||
@Operation(summary = "获取【我的】绘图分页")
|
||||
public CommonResult<PageResult<AiImageRespVO>> getImagePageMy(@Validated AiImagePageReqVO pageReqVO) {
|
||||
PageResult<AiImageDO> pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/public-page")
|
||||
@Operation(summary = "获取公开的绘图分页")
|
||||
public CommonResult<PageResult<AiImageRespVO>> getImagePagePublic(AiImagePublicPageReqVO pageReqVO) {
|
||||
PageResult<AiImageDO> pageResult = imageService.getImagePagePublic(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-my")
|
||||
@Operation(summary = "获取【我的】绘图记录")
|
||||
@Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
|
||||
public CommonResult<AiImageRespVO> getImageMy(@RequestParam("id") Long id) {
|
||||
AiImageDO image = imageService.getImage(id);
|
||||
if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) {
|
||||
return success(null);
|
||||
}
|
||||
return success(BeanUtils.toBean(image, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/my-list-by-ids")
|
||||
@Operation(summary = "获取【我的】绘图记录列表")
|
||||
@Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048")
|
||||
public CommonResult<List<AiImageRespVO>> getImageListMyByIds(@RequestParam("ids") List<Long> ids) {
|
||||
List<AiImageDO> imageList = imageService.getImageList(ids);
|
||||
imageList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId()));
|
||||
return success(BeanUtils.toBean(imageList, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@Operation(summary = "生成图片")
|
||||
@PostMapping("/draw")
|
||||
public CommonResult<Long> drawImage(@Valid @RequestBody AiImageDrawReqVO drawReqVO) {
|
||||
return success(imageService.drawImage(getLoginUserId(), drawReqVO));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除【我的】绘画记录")
|
||||
@DeleteMapping("/delete-my")
|
||||
@Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteImageMy(@RequestParam("id") Long id) {
|
||||
imageService.deleteImageMy(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// ================ midjourney 专属 ================
|
||||
|
||||
@Operation(summary = "【Midjourney】生成图片")
|
||||
@PostMapping("/midjourney/imagine")
|
||||
public CommonResult<Long> midjourneyImagine(@Valid @RequestBody AiMidjourneyImagineReqVO reqVO) {
|
||||
Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO);
|
||||
return success(imageId);
|
||||
}
|
||||
|
||||
@Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调")
|
||||
@PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错
|
||||
@PermitAll
|
||||
@TenantIgnore
|
||||
public CommonResult<Boolean> midjourneyNotify(@Valid @RequestBody MidjourneyApi.Notify notify) {
|
||||
imageService.midjourneyNotify(notify);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@Operation(summary = "【Midjourney】Action 操作(二次生成图片)", description = "例如说:放大、缩小、U1、U2 等")
|
||||
@PostMapping("/midjourney/action")
|
||||
public CommonResult<Long> midjourneyAction(@Valid @RequestBody AiMidjourneyActionReqVO reqVO) {
|
||||
Long imageId = imageService.midjourneyAction(getLoginUserId(), reqVO);
|
||||
return success(imageId);
|
||||
}
|
||||
|
||||
// ================ 绘图管理 ================
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得绘画分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:image:query')")
|
||||
public CommonResult<PageResult<AiImageRespVO>> getImagePage(@Valid AiImagePageReqVO pageReqVO) {
|
||||
PageResult<AiImageDO> pageResult = imageService.getImagePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新绘画")
|
||||
@PreAuthorize("@ss.hasPermission('ai:image:update')")
|
||||
public CommonResult<Boolean> updateImage(@Valid @RequestBody AiImageUpdateReqVO updateReqVO) {
|
||||
imageService.updateImage(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除绘画")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:image:delete')")
|
||||
public CommonResult<Boolean> deleteImage(@RequestParam("id") Long id) {
|
||||
imageService.deleteImage(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画 Request VO")
|
||||
@Data
|
||||
public class AiImageDrawReqVO {
|
||||
|
||||
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "模型编号不能为空")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城")
|
||||
@NotEmpty(message = "提示词不能为空")
|
||||
@Size(max = 1200, message = "提示词最大 1200")
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 1. dall-e-2 模型:256x256、512x512、1024x1024
|
||||
* 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792
|
||||
*/
|
||||
@Schema(description = "图片高度")
|
||||
@NotNull(message = "图片高度不能为空")
|
||||
private Integer height;
|
||||
|
||||
@Schema(description = "图片宽度")
|
||||
@NotNull(message = "图片宽度不能为空")
|
||||
private Integer width;
|
||||
|
||||
// ========== 各平台绘画的拓展参数 ==========
|
||||
|
||||
/**
|
||||
* 绘制参数,不同 platform 的不同参数
|
||||
*
|
||||
* 1. {@link OpenAiImageOptions}
|
||||
* 2. {@link StabilityAiImageOptions}
|
||||
*/
|
||||
@Schema(description = "绘制参数")
|
||||
private Map<String, String> options;
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画分页 Request VO")
|
||||
@Data
|
||||
public class AiImagePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "用户编号", example = "28987")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "平台", example = "OpenAI")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "提示词", example = "1")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "绘画状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否发布", example = "1")
|
||||
private Boolean publicStatus;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画公开的分页 Request VO")
|
||||
@Data
|
||||
public class AiImagePublicPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "提示词")
|
||||
private String prompt;
|
||||
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画 Response VO")
|
||||
@Data
|
||||
public class AiImageRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
|
||||
private String platform; // 参见 AiPlatformEnum 枚举
|
||||
|
||||
@Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "南极的小企鹅")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Integer width;
|
||||
|
||||
@Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Integer height;
|
||||
|
||||
@Schema(description = "绘画状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "public")
|
||||
private Boolean publicStatus;
|
||||
|
||||
@Schema(description = "图片地址", example = "https://www.iocoder.cn/1.png")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description = "绘画错误信息", example = "图片错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
@Schema(description = "绘制参数")
|
||||
private Map<String, String> options;
|
||||
|
||||
@Schema(description = "mj buttons 按钮")
|
||||
private List<MidjourneyApi.Button> buttons;
|
||||
|
||||
@Schema(description = "完成时间")
|
||||
private LocalDateTime finishTime;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画修改 Request VO")
|
||||
@Data
|
||||
public class AiImageUpdateReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "是否发布", example = "true")
|
||||
private Boolean publicStatus;
|
||||
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘图操作(Midjourney) Request VO")
|
||||
@Data
|
||||
public class AiMidjourneyActionReqVO {
|
||||
|
||||
@Schema(description = "图片编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "图片编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "操作按钮编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "MJ::JOB::variation::4::06aa3e66-0e97-49cc-8201-e0295d883de4")
|
||||
@NotEmpty(message = "操作按钮编号不能为空")
|
||||
private String customId;
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 绘画生成(Midjourney) Request VO")
|
||||
@Data
|
||||
public class AiMidjourneyImagineReqVO {
|
||||
|
||||
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙")
|
||||
@NotEmpty(message = "提示词不能为空!")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "模型编号不能为空")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "图片宽度不能为空")
|
||||
private Integer width;
|
||||
|
||||
@Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "图片高度不能为空")
|
||||
private Integer height;
|
||||
|
||||
@Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6.0")
|
||||
@NotEmpty(message = "版本号不能为空")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "参考图", example = "https://www.iocoder.cn/x.png")
|
||||
private String referImageUrl;
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
### 创建知识库
|
||||
POST {{baseUrl}}/ai/knowledge/create
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"name": "测试标题",
|
||||
"description": "测试描述",
|
||||
"embeddingModelId": 30,
|
||||
"topK": 3,
|
||||
"similarityThreshold": 0.5,
|
||||
"status": 0
|
||||
}
|
||||
|
||||
### 更新知识库
|
||||
PUT {{baseUrl}}/ai/knowledge/update
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "测试标题(更新)",
|
||||
"description": "测试描述",
|
||||
"embeddingModelId": 30,
|
||||
"topK": 5,
|
||||
"similarityThreshold": 0.6,
|
||||
"status": 0
|
||||
}
|
||||
|
||||
### 获取知识库分页
|
||||
GET {{baseUrl}}/ai/knowledge/page?pageNo=1&pageSize=10
|
||||
Authorization: {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
@ -1,84 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
|
||||
|
||||
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.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
|
||||
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 = "管理后台 - AI 知识库")
|
||||
@RestController
|
||||
@RequestMapping("/ai/knowledge")
|
||||
@Validated
|
||||
public class AiKnowledgeController {
|
||||
|
||||
@Resource
|
||||
private AiKnowledgeService knowledgeService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获取知识库分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<PageResult<AiKnowledgeRespVO>> getKnowledgePage(@Valid AiKnowledgePageReqVO pageReqVO) {
|
||||
PageResult<AiKnowledgeDO> pageResult = knowledgeService.getKnowledgePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得知识库")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<AiKnowledgeRespVO> getKnowledge(@RequestParam("id") Long id) {
|
||||
AiKnowledgeDO knowledge = knowledgeService.getKnowledge(id);
|
||||
return success(BeanUtils.toBean(knowledge, AiKnowledgeRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建知识库")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
|
||||
public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO createReqVO) {
|
||||
return success(knowledgeService.createKnowledge(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新知识库")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
|
||||
public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO updateReqVO) {
|
||||
knowledgeService.updateKnowledge(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除知识库")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
|
||||
public CommonResult<Boolean> deleteKnowledge(@RequestParam("id") Long id) {
|
||||
knowledgeService.deleteKnowledge(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得知识库的精简列表")
|
||||
public CommonResult<List<AiKnowledgeRespVO>> getKnowledgeSimpleList() {
|
||||
List<AiKnowledgeDO> list = knowledgeService.getKnowledgeSimpleListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, knowledge -> new AiKnowledgeRespVO()
|
||||
.setId(knowledge.getId()).setName(knowledge.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
### 创建知识文档
|
||||
POST {{baseUrl}}/ai/knowledge/document/create
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"knowledgeId": 2,
|
||||
"name": "测试文档",
|
||||
"url": "https://static.iocoder.cn/README.md",
|
||||
"segmentMaxTokens": 800
|
||||
}
|
||||
|
||||
### 批量创建知识文档
|
||||
POST {{baseUrl}}/ai/knowledge/document/create-list
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
{
|
||||
"knowledgeId": 1,
|
||||
"list": [
|
||||
{
|
||||
"name": "测试文档1",
|
||||
"url": "https://static.iocoder.cn/README.md",
|
||||
"segmentMaxTokens": 800
|
||||
},
|
||||
{
|
||||
"name": "测试文档2",
|
||||
"url": "https://static.iocoder.cn/README_yudao.md",
|
||||
"segmentMaxTokens": 400
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
|
||||
|
||||
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.ai.controller.admin.knowledge.vo.document.*;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
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;
|
||||
|
||||
@Tag(name = "管理后台 - AI 知识库文档")
|
||||
@RestController
|
||||
@RequestMapping("/ai/knowledge/document")
|
||||
@Validated
|
||||
public class AiKnowledgeDocumentController {
|
||||
|
||||
@Resource
|
||||
private AiKnowledgeDocumentService documentService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获取文档分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage(
|
||||
@Valid AiKnowledgeDocumentPageReqVO pageReqVO) {
|
||||
PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取文档详情")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<AiKnowledgeDocumentRespVO> getKnowledgeDocument(@RequestParam("id") Long id) {
|
||||
AiKnowledgeDocumentDO document = documentService.getKnowledgeDocument(id);
|
||||
return success(BeanUtils.toBean(document, AiKnowledgeDocumentRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "新建文档(单个)")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
|
||||
public CommonResult<Long> createKnowledgeDocument(@RequestBody @Valid AiKnowledgeDocumentCreateReqVO reqVO) {
|
||||
Long id = documentService.createKnowledgeDocument(reqVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PostMapping("/create-list")
|
||||
@Operation(summary = "新建文档(多个)")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
|
||||
public CommonResult<List<Long>> createKnowledgeDocumentList(
|
||||
@RequestBody @Valid AiKnowledgeDocumentCreateListReqVO reqVO) {
|
||||
List<Long> ids = documentService.createKnowledgeDocumentList(reqVO);
|
||||
return success(ids);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新文档")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
|
||||
public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) {
|
||||
documentService.updateKnowledgeDocument(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "更新文档状态")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
|
||||
public CommonResult<Boolean> updateKnowledgeDocumentStatus(
|
||||
@Valid @RequestBody AiKnowledgeDocumentUpdateStatusReqVO reqVO) {
|
||||
documentService.updateKnowledgeDocumentStatus(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除文档")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
|
||||
public CommonResult<Boolean> deleteKnowledgeDocument(@RequestParam("id") Long id) {
|
||||
documentService.deleteKnowledgeDocument(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
### 切片内容
|
||||
GET {{baseUrl}}/ai/knowledge/segment/split?url=https://static.iocoder.cn/README_yudao.md&segmentMaxTokens=800
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
### 搜索段落内容
|
||||
GET {{baseUrl}}/ai/knowledge/segment/search?knowledgeId=2&content=如何使用这个产品&topK=5&similarityThreshold=0.1
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
|
||||
### 获取文档处理列表
|
||||
GET {{baseUrl}}/ai/knowledge/segment/get-process-list?documentIds=1,2,3
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
@ -1,130 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
|
||||
import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - AI 知识库段落")
|
||||
@RestController
|
||||
@RequestMapping("/ai/knowledge/segment")
|
||||
@Validated
|
||||
public class AiKnowledgeSegmentController {
|
||||
|
||||
@Resource
|
||||
private AiKnowledgeSegmentService segmentService;
|
||||
@Resource
|
||||
private AiKnowledgeDocumentService documentService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取段落详情")
|
||||
@Parameter(name = "id", description = "段落编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<AiKnowledgeSegmentRespVO> getKnowledgeSegment(@RequestParam("id") Long id) {
|
||||
AiKnowledgeSegmentDO segment = segmentService.getKnowledgeSegment(id);
|
||||
return success(BeanUtils.toBean(segment, AiKnowledgeSegmentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获取段落分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage(
|
||||
@Valid AiKnowledgeSegmentPageReqVO pageReqVO) {
|
||||
PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建段落")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
|
||||
public CommonResult<Long> createKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO createReqVO) {
|
||||
return success(segmentService.createKnowledgeSegment(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新段落内容")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
|
||||
public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO reqVO) {
|
||||
segmentService.updateKnowledgeSegment(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "启禁用段落内容")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
|
||||
public CommonResult<Boolean> updateKnowledgeSegmentStatus(
|
||||
@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
|
||||
segmentService.updateKnowledgeSegmentStatus(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/split")
|
||||
@Operation(summary = "切片内容")
|
||||
@Parameters({
|
||||
@Parameter(name = "url", description = "文档 URL", required = true),
|
||||
@Parameter(name = "segmentMaxTokens", description = "分段的最大 Token 数", required = true)
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<List<AiKnowledgeSegmentRespVO>> splitContent(
|
||||
@RequestParam("url") @URL String url,
|
||||
@RequestParam(value = "segmentMaxTokens") Integer segmentMaxTokens) {
|
||||
List<AiKnowledgeSegmentDO> segments = segmentService.splitContent(url, segmentMaxTokens);
|
||||
return success(BeanUtils.toBean(segments, AiKnowledgeSegmentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-process-list")
|
||||
@Operation(summary = "获取文档处理列表")
|
||||
@Parameter(name = "documentIds", description = "文档编号列表", required = true, example = "1,2,3")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getKnowledgeSegmentProcessList(
|
||||
@RequestParam("documentIds") List<Long> documentIds) {
|
||||
List<AiKnowledgeSegmentProcessRespVO> list = segmentService.getKnowledgeSegmentProcessList(documentIds);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
@Operation(summary = "搜索段落内容")
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:query')")
|
||||
public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchKnowledgeSegment(
|
||||
@Valid AiKnowledgeSegmentSearchReqVO reqVO) {
|
||||
// 1. 搜索段落
|
||||
List<AiKnowledgeSegmentSearchRespBO> segments = segmentService
|
||||
.searchKnowledgeSegment(BeanUtils.toBean(reqVO, AiKnowledgeSegmentSearchReqBO.class));
|
||||
if (CollUtil.isEmpty(segments)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// 2. 拼接 VO
|
||||
Map<Long, AiKnowledgeDocumentDO> documentMap = documentService.getKnowledgeDocumentMap(convertSet(
|
||||
segments, AiKnowledgeSegmentSearchRespBO::getDocumentId));
|
||||
return success(BeanUtils.toBean(segments, AiKnowledgeSegmentSearchRespVO.class,
|
||||
segment -> MapUtils.findAndThen(documentMap, segment.getDocumentId(),
|
||||
document -> segment.setDocumentName(document.getName()))));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档批量创建 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeDocumentCreateListReqVO {
|
||||
|
||||
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
|
||||
@NotNull(message = "知识库编号不能为空")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
|
||||
@NotNull(message = "分段的最大 Token 数不能为空")
|
||||
private Integer segmentMaxTokens;
|
||||
|
||||
@Schema(description = "文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "文档列表不能为空")
|
||||
private List<Document> list;
|
||||
|
||||
@Schema(description = "文档")
|
||||
@Data
|
||||
public static class Document {
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
|
||||
@NotBlank(message = "文档名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
|
||||
@URL(message = "文档 URL 格式不正确")
|
||||
private String url;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档的分页 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeDocumentPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "知识库编号", example = "1")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "文档名称", example = "Java 开发手册")
|
||||
private String name;
|
||||
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档 Response VO")
|
||||
@Data
|
||||
public class AiKnowledgeDocumentRespVO {
|
||||
|
||||
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "文档内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 是一门面向对象的语言.....")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "文档内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Integer contentLength;
|
||||
|
||||
@Schema(description = "文档 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Integer tokens;
|
||||
|
||||
@Schema(description = "分片最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
|
||||
private Integer segmentMaxTokens;
|
||||
|
||||
@Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer retrievalCount;
|
||||
|
||||
@Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档更新 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeDocumentUpdateReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名称", example = "Java 开发手册")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "分片最大 Token 数", example = "1000")
|
||||
private Integer segmentMaxTokens;
|
||||
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
|
||||
|
||||
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.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档更新状态 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeDocumentUpdateStatusReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
|
||||
@NotNull(message = "编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@NotNull(message = "状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档的创建 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeDocumentCreateReqVO {
|
||||
|
||||
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
|
||||
@NotNull(message = "知识库编号不能为空")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
|
||||
@NotBlank(message = "文档名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
|
||||
@URL(message = "文档 URL 格式不正确")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
|
||||
@NotNull(message = "分段的最大 Token 数不能为空")
|
||||
private Integer segmentMaxTokens;
|
||||
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库的分页 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "知识库名称", example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否启用", example = "1")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库 Response VO")
|
||||
@Data
|
||||
public class AiKnowledgeRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "知识库描述", example = "帮助你快速构建系统")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "14")
|
||||
private Long embeddingModelId;
|
||||
|
||||
@Schema(description = "向量模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat")
|
||||
private String embeddingModel;
|
||||
|
||||
@Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
|
||||
private Integer topK;
|
||||
|
||||
@Schema(description = "相似度阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.7")
|
||||
private Double similarityThreshold;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge;
|
||||
|
||||
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.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库新增/修改 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeSaveReqVO {
|
||||
|
||||
@Schema(description = "对话编号", example = "1204")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro 用户指南")
|
||||
@NotBlank(message = "知识库名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "知识库描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "存储 ruoyi-vue-pro 操作文档")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "向量模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "向量模型不能为空")
|
||||
private Long embeddingModelId;
|
||||
|
||||
@Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
|
||||
@NotNull(message = "topK 不能为空")
|
||||
private Integer topK;
|
||||
|
||||
@Schema(description = "相似性阈值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5")
|
||||
@NotNull(message = "相似性阈值不能为空")
|
||||
private Double similarityThreshold;
|
||||
|
||||
@Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "是否启用不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
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;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库分段的分页 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "文档编号", example = "1")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "分段内容关键字", example = "Java 开发")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "分段状态", example = "1")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库段落向量进度 Response VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentProcessRespVO {
|
||||
|
||||
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "总段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Long count;
|
||||
|
||||
@Schema(description = "已向量化段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
|
||||
private Long embeddingCount;
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库文档分片 Response VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "向量库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1858496a-1dde-4edf-a43e-0aed08f37f8c")
|
||||
private String vectorId;
|
||||
|
||||
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "切片内容长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Integer contentLength;
|
||||
|
||||
@Schema(description = "token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Integer tokens;
|
||||
|
||||
@Schema(description = "召回次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer retrievalCount;
|
||||
|
||||
@Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long createTime;
|
||||
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 新增/修改知识库段落 request VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", example = "24790")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "知识库文档编号", example = "1024")
|
||||
private Long documentId;
|
||||
|
||||
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
|
||||
@NotEmpty(message = "切片内容不能为空")
|
||||
private String content;
|
||||
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库段落搜索 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentSearchReqVO {
|
||||
|
||||
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "知识库编号不能为空")
|
||||
private Long knowledgeId;
|
||||
|
||||
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "如何使用这个产品")
|
||||
@NotEmpty(message = "内容不能为空")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "最大返回数量", example = "5")
|
||||
private Integer topK;
|
||||
|
||||
@Schema(description = "相似度阈值", example = "0.7")
|
||||
private Double similarityThreshold;
|
||||
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库段落搜索 Response VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentSearchRespVO extends AiKnowledgeSegmentRespVO {
|
||||
|
||||
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
|
||||
private String documentName;
|
||||
|
||||
@Schema(description = "相似度分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.95")
|
||||
private Double score;
|
||||
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
|
||||
|
||||
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.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Schema(description = "管理后台 - AI 知识库段落的更新状态 Request VO")
|
||||
@Data
|
||||
public class AiKnowledgeSegmentUpdateStatusReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "是否启用不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.mindmap;
|
||||
|
||||
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.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapRespVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO;
|
||||
import cn.iocoder.yudao.module.ai.service.mindmap.AiMindMapService;
|
||||
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.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - AI 思维导图")
|
||||
@RestController
|
||||
@RequestMapping("/ai/mind-map")
|
||||
public class AiMindMapController {
|
||||
|
||||
@Resource
|
||||
private AiMindMapService mindMapService;
|
||||
|
||||
@PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@Operation(summary = "导图生成(流式)", description = "流式返回,响应较快")
|
||||
public Flux<CommonResult<String>> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) {
|
||||
return mindMapService.generateMindMap(generateReqVO, getLoginUserId());
|
||||
}
|
||||
|
||||
// ================ 导图管理 ================
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除思维导图")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:mind-map:delete')")
|
||||
public CommonResult<Boolean> deleteMindMap(@RequestParam("id") Long id) {
|
||||
mindMapService.deleteMindMap(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得思维导图分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:mind-map:query')")
|
||||
public CommonResult<PageResult<AiMindMapRespVO>> getMindMapPage(@Valid AiMindMapPageReqVO pageReqVO) {
|
||||
PageResult<AiMindMapDO> pageResult = mindMapService.getMindMapPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiMindMapRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - AI 思维导图生成 Request VO")
|
||||
@Data
|
||||
public class AiMindMapGenerateReqVO {
|
||||
|
||||
@Schema(description = "思维导图内容提示", example = "Java 学习路线")
|
||||
@NotBlank(message = "思维导图内容提示不能为空")
|
||||
private String prompt;
|
||||
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
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 = "管理后台 - AI 思维导图分页 Request VO")
|
||||
@Data
|
||||
public class AiMindMapPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "用户编号", example = "4325")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "生成内容提示", example = "Java 学习路线")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - AI 思维导图 Response VO")
|
||||
@Data
|
||||
public class AiMindMapRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3373")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4325")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "生成内容提示", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 学习路线")
|
||||
private String prompt;
|
||||
|
||||
@Schema(description = "生成的思维导图内容")
|
||||
private String generatedContent;
|
||||
|
||||
@Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model;
|
||||
|
||||
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.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
|
||||
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 = "管理后台 - AI API 密钥")
|
||||
@RestController
|
||||
@RequestMapping("/ai/api-key")
|
||||
@Validated
|
||||
public class AiApiKeyController {
|
||||
|
||||
@Resource
|
||||
private AiApiKeyService apiKeyService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建 API 密钥")
|
||||
@PreAuthorize("@ss.hasPermission('ai:api-key:create')")
|
||||
public CommonResult<Long> createApiKey(@Valid @RequestBody AiApiKeySaveReqVO createReqVO) {
|
||||
return success(apiKeyService.createApiKey(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新 API 密钥")
|
||||
@PreAuthorize("@ss.hasPermission('ai:api-key:update')")
|
||||
public CommonResult<Boolean> updateApiKey(@Valid @RequestBody AiApiKeySaveReqVO updateReqVO) {
|
||||
apiKeyService.updateApiKey(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除 API 密钥")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:api-key:delete')")
|
||||
public CommonResult<Boolean> deleteApiKey(@RequestParam("id") Long id) {
|
||||
apiKeyService.deleteApiKey(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得 API 密钥")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:api-key:query')")
|
||||
public CommonResult<AiApiKeyRespVO> getApiKey(@RequestParam("id") Long id) {
|
||||
AiApiKeyDO apiKey = apiKeyService.getApiKey(id);
|
||||
return success(BeanUtils.toBean(apiKey, AiApiKeyRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得 API 密钥分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:api-key:query')")
|
||||
public CommonResult<PageResult<AiApiKeyRespVO>> getApiKeyPage(@Valid AiApiKeyPageReqVO pageReqVO) {
|
||||
PageResult<AiApiKeyDO> pageResult = apiKeyService.getApiKeyPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiApiKeyRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得 API 密钥分页列表")
|
||||
public CommonResult<List<AiModelRespVO>> getApiKeySimpleList() {
|
||||
List<AiApiKeyDO> list = apiKeyService.getApiKeyList();
|
||||
return success(convertList(list, key -> new AiModelRespVO().setId(key.getId()).setName(key.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
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.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
|
||||
import com.fhs.core.trans.anno.TransMethodResult;
|
||||
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.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - AI 聊天角色")
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat-role")
|
||||
@Validated
|
||||
public class AiChatRoleController {
|
||||
|
||||
@Resource
|
||||
private AiChatRoleService chatRoleService;
|
||||
|
||||
@GetMapping("/my-page")
|
||||
@Operation(summary = "获得【我的】聊天角色分页")
|
||||
@TransMethodResult
|
||||
public CommonResult<PageResult<AiChatRoleRespVO>> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) {
|
||||
PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
|
||||
return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get-my")
|
||||
@Operation(summary = "获得【我的】聊天角色")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@TransMethodResult
|
||||
public CommonResult<AiChatRoleRespVO> getChatRoleMy(@RequestParam("id") Long id) {
|
||||
AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
|
||||
if (ObjUtil.notEqual(chatRole.getUserId(), getLoginUserId())) {
|
||||
return success(null);
|
||||
}
|
||||
return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/create-my")
|
||||
@Operation(summary = "创建【我的】聊天角色")
|
||||
public CommonResult<Long> createChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO createReqVO) {
|
||||
return success(chatRoleService.createChatRoleMy(createReqVO, getLoginUserId()));
|
||||
}
|
||||
|
||||
@PutMapping("/update-my")
|
||||
@Operation(summary = "更新【我的】聊天角色")
|
||||
public CommonResult<Boolean> updateChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO updateReqVO) {
|
||||
chatRoleService.updateChatRoleMy(updateReqVO, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-my")
|
||||
@Operation(summary = "删除【我的】聊天角色")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
public CommonResult<Boolean> deleteChatRoleMy(@RequestParam("id") Long id) {
|
||||
chatRoleService.deleteChatRoleMy(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/category-list")
|
||||
@Operation(summary = "获得聊天角色的分类列表")
|
||||
public CommonResult<List<String>> getChatRoleCategoryList() {
|
||||
return success(chatRoleService.getChatRoleCategoryList());
|
||||
}
|
||||
|
||||
// ========== 角色管理 ==========
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建聊天角色")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-role:create')")
|
||||
public CommonResult<Long> createChatRole(@Valid @RequestBody AiChatRoleSaveReqVO createReqVO) {
|
||||
return success(chatRoleService.createChatRole(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新聊天角色")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-role:update')")
|
||||
public CommonResult<Boolean> updateChatRole(@Valid @RequestBody AiChatRoleSaveReqVO updateReqVO) {
|
||||
chatRoleService.updateChatRole(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除聊天角色")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-role:delete')")
|
||||
public CommonResult<Boolean> deleteChatRole(@RequestParam("id") Long id) {
|
||||
chatRoleService.deleteChatRole(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得聊天角色")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-role:query')")
|
||||
@TransMethodResult
|
||||
public CommonResult<AiChatRoleRespVO> getChatRole(@RequestParam("id") Long id) {
|
||||
AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
|
||||
return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得聊天角色分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:chat-role:query')")
|
||||
public CommonResult<PageResult<AiChatRoleRespVO>> getChatRolePage(@Valid AiChatRolePageReqVO pageReqVO) {
|
||||
PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRolePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model;
|
||||
|
||||
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.ai.controller.admin.model.vo.model.AiModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiModelService;
|
||||
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 = "管理后台 - AI 模型")
|
||||
@RestController
|
||||
@RequestMapping("/ai/model")
|
||||
@Validated
|
||||
public class AiModelController {
|
||||
|
||||
@Resource
|
||||
private AiModelService modelService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建模型")
|
||||
@PreAuthorize("@ss.hasPermission('ai:model:create')")
|
||||
public CommonResult<Long> createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) {
|
||||
return success(modelService.createModel(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新模型")
|
||||
@PreAuthorize("@ss.hasPermission('ai:model:update')")
|
||||
public CommonResult<Boolean> updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) {
|
||||
modelService.updateModel(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除模型")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:model:delete')")
|
||||
public CommonResult<Boolean> deleteModel(@RequestParam("id") Long id) {
|
||||
modelService.deleteModel(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得模型")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:model:query')")
|
||||
public CommonResult<AiModelRespVO> getModel(@RequestParam("id") Long id) {
|
||||
AiModelDO model = modelService.getModel(id);
|
||||
return success(BeanUtils.toBean(model, AiModelRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得模型分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:model:query')")
|
||||
public CommonResult<PageResult<AiModelRespVO>> getModelPage(@Valid AiModelPageReqVO pageReqVO) {
|
||||
PageResult<AiModelDO> pageResult = modelService.getModelPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiModelRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得模型列表")
|
||||
@Parameter(name = "type", description = "类型", required = true, example = "1")
|
||||
@Parameter(name = "platform", description = "平台", example = "midjourney")
|
||||
public CommonResult<List<AiModelRespVO>> getModelSimpleList(
|
||||
@RequestParam("type") Integer type,
|
||||
@RequestParam(value = "platform", required = false) String platform) {
|
||||
List<AiModelDO> list = modelService.getModelListByStatusAndType(
|
||||
CommonStatusEnum.ENABLE.getStatus(), type, platform);
|
||||
return success(convertList(list, model -> new AiModelRespVO().setId(model.getId())
|
||||
.setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform())));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model;
|
||||
|
||||
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.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiToolService;
|
||||
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 = "管理后台 - AI 工具")
|
||||
@RestController
|
||||
@RequestMapping("/ai/tool")
|
||||
@Validated
|
||||
public class AiToolController {
|
||||
|
||||
@Resource
|
||||
private AiToolService toolService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建工具")
|
||||
@PreAuthorize("@ss.hasPermission('ai:tool:create')")
|
||||
public CommonResult<Long> createTool(@Valid @RequestBody AiToolSaveReqVO createReqVO) {
|
||||
return success(toolService.createTool(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新工具")
|
||||
@PreAuthorize("@ss.hasPermission('ai:tool:update')")
|
||||
public CommonResult<Boolean> updateTool(@Valid @RequestBody AiToolSaveReqVO updateReqVO) {
|
||||
toolService.updateTool(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除工具")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('ai:tool:delete')")
|
||||
public CommonResult<Boolean> deleteTool(@RequestParam("id") Long id) {
|
||||
toolService.deleteTool(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得工具")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('ai:tool:query')")
|
||||
public CommonResult<AiToolRespVO> getTool(@RequestParam("id") Long id) {
|
||||
AiToolDO tool = toolService.getTool(id);
|
||||
return success(BeanUtils.toBean(tool, AiToolRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得工具分页")
|
||||
@PreAuthorize("@ss.hasPermission('ai:tool:query')")
|
||||
public CommonResult<PageResult<AiToolRespVO>> getToolPage(@Valid AiToolPageReqVO pageReqVO) {
|
||||
PageResult<AiToolDO> pageResult = toolService.getToolPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, AiToolRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得工具列表")
|
||||
public CommonResult<List<AiToolRespVO>> getToolSimpleList() {
|
||||
List<AiToolDO> list = toolService.getToolListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, tool -> new AiToolRespVO()
|
||||
.setId(tool.getId()).setName(tool.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
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 = "管理后台 - AI API 密钥分页 Request VO")
|
||||
@Data
|
||||
public class AiApiKeyPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名称", example = "文心一言")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "平台", example = "OpenAI")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
@Schema(description = "管理后台 - AI API 密钥 Response VO")
|
||||
@Data
|
||||
public class AiApiKeyRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "文心一言")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "密钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
|
||||
private String apiKey;
|
||||
|
||||
@Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "自定义 API 地址", example = "https://aip.baidubce.com")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
|
||||
@Schema(description = "管理后台 - AI API 密钥新增/修改 Request VO")
|
||||
@Data
|
||||
public class AiApiKeySaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "文心一言")
|
||||
@NotEmpty(message = "名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "密钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
|
||||
@NotEmpty(message = "密钥不能为空")
|
||||
private String apiKey;
|
||||
|
||||
@Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
|
||||
@NotEmpty(message = "平台不能为空")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "自定义 API 地址", example = "https://aip.baidubce.com")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
|
||||
|
||||
import lombok.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天角色分页 Request VO")
|
||||
@Data
|
||||
public class AiChatRolePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "角色名称", example = "李四")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "角色类别", example = "创作")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "是否公开", example = "1")
|
||||
private Boolean publicStatus;
|
||||
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天角色 Response VO")
|
||||
@Data
|
||||
public class AiChatRoleRespVO implements VO {
|
||||
|
||||
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32746")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9442")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "模型编号", example = "17640")
|
||||
@Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = { "name", "model" }, refs = { "modelName", "model" })
|
||||
private Long modelId;
|
||||
@Schema(description = "模型名字", example = "张三")
|
||||
private String modelName;
|
||||
@Schema(description = "模型标识", example = "gpt-3.5-turbo-0125")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "角色类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "创作")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "角色排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "引用的知识库编号列表", example = "1,2,3")
|
||||
private List<Long> knowledgeIds;
|
||||
|
||||
@Schema(description = "引用的工具编号列表", example = "1,2,3")
|
||||
private List<Long> toolIds;
|
||||
|
||||
@Schema(description = "引用的 MCP Client 名字列表", example = "filesystem")
|
||||
private List<String> mcpClientNames;
|
||||
|
||||
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Boolean publicStatus;
|
||||
|
||||
@Schema(description = "状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天角色新增/修改【我的】 Request VO")
|
||||
@Data
|
||||
public class AiChatRoleSaveMyReqVO {
|
||||
|
||||
@Schema(description = "角色编号", example = "32746")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@NotEmpty(message = "角色名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@NotEmpty(message = "角色头像不能为空")
|
||||
@URL(message = "角色头像必须是 URL 格式")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
|
||||
@NotEmpty(message = "角色描述不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED, example = "现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题")
|
||||
@NotEmpty(message = "角色设定不能为空")
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "引用的知识库编号列表", example = "1,2,3")
|
||||
private List<Long> knowledgeIds;
|
||||
|
||||
@Schema(description = "引用的工具编号列表", example = "1,2,3")
|
||||
private List<Long> toolIds;
|
||||
|
||||
@Schema(description = "引用的 MCP Client 名字列表", example = "filesystem")
|
||||
private List<String> mcpClientNames;
|
||||
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - AI 聊天角色新增/修改 Request VO")
|
||||
@Data
|
||||
public class AiChatRoleSaveReqVO {
|
||||
|
||||
@Schema(description = "角色编号", example = "32746")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "模型编号", example = "17640")
|
||||
private Long modelId;
|
||||
|
||||
@Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@NotEmpty(message = "角色名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@NotEmpty(message = "角色头像不能为空")
|
||||
@URL(message = "角色头像必须是 URL 格式")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "角色类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "创作")
|
||||
@NotEmpty(message = "角色类别不能为空")
|
||||
private String category;
|
||||
|
||||
@Schema(description = "角色排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "角色排序不能为空")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
|
||||
@NotEmpty(message = "角色描述不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED, example = "现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题")
|
||||
@NotEmpty(message = "角色设定不能为空")
|
||||
private String systemMessage;
|
||||
|
||||
@Schema(description = "引用的知识库编号列表", example = "1,2,3")
|
||||
private List<Long> knowledgeIds;
|
||||
|
||||
@Schema(description = "引用的工具编号列表", example = "1,2,3")
|
||||
private List<Long> toolIds;
|
||||
|
||||
@Schema(description = "引用的 MCP Client 名字列表", example = "filesystem")
|
||||
private List<String> mcpClientNames;
|
||||
|
||||
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "是否公开不能为空")
|
||||
private Boolean publicStatus;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model;
|
||||
|
||||
import lombok.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
@Schema(description = "管理后台 - API 模型分页 Request VO")
|
||||
@Data
|
||||
public class AiModelPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "模型名字", example = "张三")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "模型标识", example = "gpt-3.5-turbo-0125")
|
||||
private String model;
|
||||
|
||||
@Schema(description = "模型平台", example = "OpenAI")
|
||||
private String platform;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user