eventRegistryEventConsumers = new HashMap<>();
+
+ // MYBATIS SQL SESSION FACTORY /////////////////////////////////////
+
+ protected boolean isDbHistoryUsed = true;
+ protected DbSqlSessionFactory dbSqlSessionFactory;
+ protected SqlSessionFactory sqlSessionFactory;
+ protected TransactionFactory transactionFactory;
+ protected TransactionContextFactory transactionContextFactory;
+
+ /**
+ * If set to true, enables bulk insert (grouping sql inserts together). Default true.
+ * For some databases (eg DB2+z/OS) needs to be set to false.
+ */
+ protected boolean isBulkInsertEnabled = true;
+
+ /**
+ * Some databases have a limit of how many parameters one sql insert can have (eg SQL Server, 2000 params (!= insert statements) ). Tweak this parameter in case of exceptions indicating too much
+ * is being put into one bulk insert, or make it higher if your database can cope with it and there are inserts with a huge amount of data.
+ *
+ * By default: 100 (55 for mssql server as it has a hard limit of 2000 parameters in a statement)
+ */
+ protected int maxNrOfStatementsInBulkInsert = 100;
+
+ public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 55; // currently Execution has most params (35). 2000 / 35 = 57.
+
+ protected String mybatisMappingFile;
+ protected Set> customMybatisMappers;
+ protected Set customMybatisXMLMappers;
+ protected List customMybatisInterceptors;
+
+ protected Set dependentEngineMyBatisXmlMappers;
+ protected List dependentEngineMybatisTypeAliasConfigs;
+ protected List dependentEngineMybatisTypeHandlerConfigs;
+
+ // SESSION FACTORIES ///////////////////////////////////////////////
+ protected List customSessionFactories;
+ protected Map, SessionFactory> sessionFactories;
+
+ protected boolean enableEventDispatcher = true;
+ protected FlowableEventDispatcher eventDispatcher;
+ protected List eventListeners;
+ protected Map> typedEventListeners;
+ protected List additionalEventDispatchActions;
+
+ protected LoggingListener loggingListener;
+
+ protected boolean transactionsExternallyManaged;
+
+ /**
+ * Flag that can be set to configure or not a relational database is used. This is useful for custom implementations that do not use relational databases at all.
+ *
+ * If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema.
+ *
+ * If false, no validation or schema creation will be done. That means that the database schema must have been created 'manually' before but the engine does not validate whether the schema is
+ * correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used.
+ */
+ protected boolean usingRelationalDatabase = true;
+
+ /**
+ * Flag that can be set to configure whether or not a schema is used. This is useful for custom implementations that do not use relational databases at all.
+ * Setting {@link #usingRelationalDatabase} to true will automatically imply using a schema.
+ */
+ protected boolean usingSchemaMgmt = true;
+
+ /**
+ * Allows configuring a database table prefix which is used for all runtime operations of the process engine. For example, if you specify a prefix named 'PRE1.', Flowable will query for executions
+ * in a table named 'PRE1.ACT_RU_EXECUTION_'.
+ *
+ *
+ * NOTE: the prefix is not respected by automatic database schema management. If you use {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_CREATE_DROP} or
+ * {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_TRUE}, Flowable will create the database tables using the default names, regardless of the prefix configured here.
+ */
+ protected String databaseTablePrefix = "";
+
+ /**
+ * Escape character for doing wildcard searches.
+ *
+ * This will be added at then end of queries that include for example a LIKE clause. For example: SELECT * FROM table WHERE column LIKE '%\%%' ESCAPE '\';
+ */
+ protected String databaseWildcardEscapeCharacter;
+
+ /**
+ * database catalog to use
+ */
+ protected String databaseCatalog = "";
+
+ /**
+ * In some situations you want to set the schema to use for table checks / generation if the database metadata doesn't return that correctly, see https://jira.codehaus.org/browse/ACT-1220,
+ * https://jira.codehaus.org/browse/ACT-1062
+ */
+ protected String databaseSchema;
+
+ /**
+ * Set to true in case the defined databaseTablePrefix is a schema-name, instead of an actual table name prefix. This is relevant for checking if Flowable-tables exist, the databaseTablePrefix
+ * will not be used here - since the schema is taken into account already, adding a prefix for the table-check will result in wrong table-names.
+ */
+ protected boolean tablePrefixIsSchema;
+
+ /**
+ * Set to true if the latest version of a definition should be retrieved, ignoring a possible parent deployment id value
+ */
+ protected boolean alwaysLookupLatestDefinitionVersion;
+
+ /**
+ * Set to true if by default lookups should fallback to the default tenant (an empty string by default or a defined tenant value)
+ */
+ protected boolean fallbackToDefaultTenant;
+
+ /**
+ * Default tenant provider that is executed when looking up definitions, in case the global or local fallback to default tenant value is true
+ */
+ protected DefaultTenantProvider defaultTenantProvider = (tenantId, scope, scopeKey) -> NO_TENANT_ID;
+
+ /**
+ * Enables the MyBatis plugin that logs the execution time of sql statements.
+ */
+ protected boolean enableLogSqlExecutionTime;
+
+ protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings();
+
+ /**
+ * Duration between the checks when acquiring a lock.
+ */
+ protected Duration lockPollRate = Duration.ofSeconds(10);
+
+ /**
+ * Duration to wait for the DB Schema lock before giving up.
+ */
+ protected Duration schemaLockWaitTime = Duration.ofMinutes(5);
+
+ // DATA MANAGERS //////////////////////////////////////////////////////////////////
+
+ protected PropertyDataManager propertyDataManager;
+ protected ByteArrayDataManager byteArrayDataManager;
+ protected TableDataManager tableDataManager;
+
+ // ENTITY MANAGERS ////////////////////////////////////////////////////////////////
+
+ protected PropertyEntityManager propertyEntityManager;
+ protected ByteArrayEntityManager byteArrayEntityManager;
+
+ protected List customPreDeployers;
+ protected List customPostDeployers;
+ protected List deployers;
+
+ // CONFIGURATORS ////////////////////////////////////////////////////////////
+
+ protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi)
+ protected List configurators; // The injected configurators
+ protected List allConfigurators; // Including auto-discovered configurators
+ protected EngineConfigurator idmEngineConfigurator;
+ protected EngineConfigurator eventRegistryConfigurator;
+
+ public static final String PRODUCT_NAME_POSTGRES = "PostgreSQL";
+ public static final String PRODUCT_NAME_CRDB = "CockroachDB";
+
+ public static final String DATABASE_TYPE_H2 = "h2";
+ public static final String DATABASE_TYPE_HSQL = "hsql";
+ public static final String DATABASE_TYPE_MYSQL = "mysql";
+ public static final String DATABASE_TYPE_ORACLE = "oracle";
+ public static final String DATABASE_TYPE_POSTGRES = "postgres";
+ public static final String DATABASE_TYPE_MSSQL = "mssql";
+ public static final String DATABASE_TYPE_DB2 = "db2";
+ public static final String DATABASE_TYPE_COCKROACHDB = "cockroachdb";
+
+ public static Properties getDefaultDatabaseTypeMappings() {
+ Properties databaseTypeMappings = new Properties();
+ databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2);
+ databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL);
+ databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL);
+ databaseTypeMappings.setProperty("MariaDB", DATABASE_TYPE_MYSQL);
+ databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE);
+ databaseTypeMappings.setProperty(PRODUCT_NAME_POSTGRES, DATABASE_TYPE_POSTGRES);
+ databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL);
+ databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXPPC64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/LINUXPPC64LE", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2);
+ databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB);
+ databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE); // dhb52: DM support
+ return databaseTypeMappings;
+ }
+
+ protected Map beans;
+
+ protected IdGenerator idGenerator;
+ protected boolean usePrefixId;
+
+ protected Clock clock;
+ protected ObjectMapper objectMapper;
+
+ // Variables
+
+ public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING = 4000;
+ public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING = 2000;
+
+ /**
+ * Define a max length for storing String variable types in the database. Mainly used for the Oracle NVARCHAR2 limit of 2000 characters
+ */
+ protected int maxLengthStringVariableType = -1;
+
+ protected void initEngineConfigurations() {
+ addEngineConfiguration(getEngineCfgKey(), getEngineScopeType(), this);
+ }
+
+ // DataSource
+ // ///////////////////////////////////////////////////////////////
+
+ protected void initDataSource() {
+ if (dataSource == null) {
+ if (dataSourceJndiName != null) {
+ try {
+ dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
+ } catch (Exception e) {
+ throw new FlowableException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e);
+ }
+
+ } else if (jdbcUrl != null) {
+ if ((jdbcDriver == null) || (jdbcUsername == null)) {
+ throw new FlowableException("DataSource or JDBC properties have to be specified in a process engine configuration");
+ }
+
+ logger.debug("initializing datasource to db: {}", jdbcUrl);
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Configuring Datasource with following properties (omitted password for security)");
+ logger.info("datasource driver : {}", jdbcDriver);
+ logger.info("datasource url : {}", jdbcUrl);
+ logger.info("datasource user name : {}", jdbcUsername);
+ }
+
+ PooledDataSource pooledDataSource = new PooledDataSource(this.getClass().getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);
+
+ if (jdbcMaxActiveConnections > 0) {
+ pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
+ }
+ if (jdbcMaxIdleConnections > 0) {
+ pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
+ }
+ if (jdbcMaxCheckoutTime > 0) {
+ pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
+ }
+ if (jdbcMaxWaitTime > 0) {
+ pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
+ }
+ if (jdbcPingEnabled) {
+ pooledDataSource.setPoolPingEnabled(true);
+ if (jdbcPingQuery != null) {
+ pooledDataSource.setPoolPingQuery(jdbcPingQuery);
+ }
+ pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor);
+ }
+ if (jdbcDefaultTransactionIsolationLevel > 0) {
+ pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel);
+ }
+ dataSource = pooledDataSource;
+ }
+ }
+
+ if (databaseType == null) {
+ initDatabaseType();
+ }
+ }
+
+ public void initDatabaseType() {
+ Connection connection = null;
+ try {
+ connection = dataSource.getConnection();
+ DatabaseMetaData databaseMetaData = connection.getMetaData();
+ String databaseProductName = databaseMetaData.getDatabaseProductName();
+ logger.debug("database product name: '{}'", databaseProductName);
+
+ // CRDB does not expose the version through the jdbc driver, so we need to fetch it through version().
+ if (PRODUCT_NAME_POSTGRES.equalsIgnoreCase(databaseProductName)) {
+ try (PreparedStatement preparedStatement = connection.prepareStatement("select version() as version;");
+ ResultSet resultSet = preparedStatement.executeQuery()) {
+ String version = null;
+ if (resultSet.next()) {
+ version = resultSet.getString("version");
+ }
+
+ if (StringUtils.isNotEmpty(version) && version.toLowerCase().startsWith(PRODUCT_NAME_CRDB.toLowerCase())) {
+ databaseProductName = PRODUCT_NAME_CRDB;
+ logger.info("CockroachDB version '{}' detected", version);
+ }
+ }
+ }
+
+ databaseType = databaseTypeMappings.getProperty(databaseProductName);
+ if (databaseType == null) {
+ throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'");
+ }
+ logger.debug("using database type: {}", databaseType);
+
+ } catch (SQLException e) {
+ throw new RuntimeException("Exception while initializing Database connection", e);
+ } finally {
+ try {
+ if (connection != null) {
+ connection.close();
+ }
+ } catch (SQLException e) {
+ logger.error("Exception while closing the Database connection", e);
+ }
+ }
+
+ // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement).
+ // Especially with executions, with 100 as default, this limit is passed.
+ if (DATABASE_TYPE_MSSQL.equals(databaseType)) {
+ maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER;
+ }
+ }
+
+ public void initSchemaManager() {
+ if (this.commonSchemaManager == null) {
+ this.commonSchemaManager = new CommonDbSchemaManager();
+ }
+ }
+
+ // session factories ////////////////////////////////////////////////////////
+
+ public void addSessionFactory(SessionFactory sessionFactory) {
+ sessionFactories.put(sessionFactory.getSessionType(), sessionFactory);
+ }
+
+ public void initCommandContextFactory() {
+ if (commandContextFactory == null) {
+ commandContextFactory = new CommandContextFactory();
+ }
+ }
+
+ public void initTransactionContextFactory() {
+ if (transactionContextFactory == null) {
+ transactionContextFactory = new StandaloneMybatisTransactionContextFactory();
+ }
+ }
+
+ public void initCommandExecutors() {
+ initDefaultCommandConfig();
+ initSchemaCommandConfig();
+ initCommandInvoker();
+ initCommandInterceptors();
+ initCommandExecutor();
+ }
+
+
+ public void initDefaultCommandConfig() {
+ if (defaultCommandConfig == null) {
+ defaultCommandConfig = new CommandConfig();
+ }
+ }
+
+ public void initSchemaCommandConfig() {
+ if (schemaCommandConfig == null) {
+ schemaCommandConfig = new CommandConfig();
+ }
+ }
+
+ public void initCommandInvoker() {
+ if (commandInvoker == null) {
+ commandInvoker = new DefaultCommandInvoker();
+ }
+ }
+
+ public void initCommandInterceptors() {
+ if (commandInterceptors == null) {
+ commandInterceptors = new ArrayList<>();
+ if (customPreCommandInterceptors != null) {
+ commandInterceptors.addAll(customPreCommandInterceptors);
+ }
+ commandInterceptors.addAll(getDefaultCommandInterceptors());
+ if (customPostCommandInterceptors != null) {
+ commandInterceptors.addAll(customPostCommandInterceptors);
+ }
+ commandInterceptors.add(commandInvoker);
+ }
+ }
+
+ public Collection extends CommandInterceptor> getDefaultCommandInterceptors() {
+ if (defaultCommandInterceptors == null) {
+ List interceptors = new ArrayList<>();
+ interceptors.add(new LogInterceptor());
+
+ if (DATABASE_TYPE_COCKROACHDB.equals(databaseType)) {
+ interceptors.add(new CrDbRetryInterceptor());
+ }
+
+ CommandInterceptor transactionInterceptor = createTransactionInterceptor();
+ if (transactionInterceptor != null) {
+ interceptors.add(transactionInterceptor);
+ }
+
+ if (commandContextFactory != null) {
+ String engineCfgKey = getEngineCfgKey();
+ CommandContextInterceptor commandContextInterceptor = new CommandContextInterceptor(commandContextFactory,
+ classLoader, useClassForNameClassLoading, clock, objectMapper);
+ engineConfigurations.put(engineCfgKey, this);
+ commandContextInterceptor.setEngineCfgKey(engineCfgKey);
+ commandContextInterceptor.setEngineConfigurations(engineConfigurations);
+ interceptors.add(commandContextInterceptor);
+ }
+
+ if (transactionContextFactory != null) {
+ interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
+ }
+
+ List additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors();
+ if (additionalCommandInterceptors != null) {
+ interceptors.addAll(additionalCommandInterceptors);
+ }
+
+ defaultCommandInterceptors = interceptors;
+ }
+ return defaultCommandInterceptors;
+ }
+
+ public abstract String getEngineCfgKey();
+
+ public abstract String getEngineScopeType();
+
+ public List getAdditionalDefaultCommandInterceptors() {
+ return null;
+ }
+
+ public void initCommandExecutor() {
+ if (commandExecutor == null) {
+ CommandInterceptor first = initInterceptorChain(commandInterceptors);
+ commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
+ }
+ }
+
+ public CommandInterceptor initInterceptorChain(List chain) {
+ if (chain == null || chain.isEmpty()) {
+ throw new FlowableException("invalid command interceptor chain configuration: " + chain);
+ }
+ for (int i = 0; i < chain.size() - 1; i++) {
+ chain.get(i).setNext(chain.get(i + 1));
+ }
+ return chain.get(0);
+ }
+
+ public abstract CommandInterceptor createTransactionInterceptor();
+
+
+ public void initBeans() {
+ if (beans == null) {
+ beans = new HashMap<>();
+ }
+ }
+
+ // id generator
+ // /////////////////////////////////////////////////////////////
+
+ public void initIdGenerator() {
+ if (idGenerator == null) {
+ idGenerator = new StrongUuidGenerator();
+ }
+ }
+
+ public void initObjectMapper() {
+ if (objectMapper == null) {
+ objectMapper = new ObjectMapper();
+ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ }
+ }
+
+ public void initClock() {
+ if (clock == null) {
+ clock = new DefaultClockImpl();
+ }
+ }
+
+ // Data managers ///////////////////////////////////////////////////////////
+
+ public void initDataManagers() {
+ if (propertyDataManager == null) {
+ propertyDataManager = new MybatisPropertyDataManager(idGenerator);
+ }
+
+ if (byteArrayDataManager == null) {
+ byteArrayDataManager = new MybatisByteArrayDataManager(idGenerator);
+ }
+ }
+
+ // Entity managers //////////////////////////////////////////////////////////
+
+ public void initEntityManagers() {
+ if (propertyEntityManager == null) {
+ propertyEntityManager = new PropertyEntityManagerImpl(this, propertyDataManager);
+ }
+
+ if (byteArrayEntityManager == null) {
+ byteArrayEntityManager = new ByteArrayEntityManagerImpl(byteArrayDataManager, getEngineCfgKey(), this::getEventDispatcher);
+ }
+
+ if (tableDataManager == null) {
+ tableDataManager = new TableDataManagerImpl(this);
+ }
+ }
+
+ // services
+ // /////////////////////////////////////////////////////////////////
+
+ protected void initService(Object service) {
+ if (service instanceof CommonEngineServiceImpl) {
+ ((CommonEngineServiceImpl) service).setCommandExecutor(commandExecutor);
+ }
+ }
+
+ // myBatis SqlSessionFactory
+ // ////////////////////////////////////////////////
+
+ public void initSessionFactories() {
+ if (sessionFactories == null) {
+ sessionFactories = new HashMap<>();
+
+ if (usingRelationalDatabase) {
+ initDbSqlSessionFactory();
+ }
+
+ addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class));
+
+ if (isLoggingSessionEnabled()) {
+ if (!sessionFactories.containsKey(LoggingSession.class)) {
+ LoggingSessionFactory loggingSessionFactory = new LoggingSessionFactory();
+ loggingSessionFactory.setLoggingListener(loggingListener);
+ loggingSessionFactory.setObjectMapper(objectMapper);
+ sessionFactories.put(LoggingSession.class, loggingSessionFactory);
+ }
+ }
+
+ commandContextFactory.setSessionFactories(sessionFactories);
+
+ } else {
+ if (usingRelationalDatabase) {
+ initDbSqlSessionFactoryEntitySettings();
+ }
+ }
+
+ if (customSessionFactories != null) {
+ for (SessionFactory sessionFactory : customSessionFactories) {
+ addSessionFactory(sessionFactory);
+ }
+ }
+ }
+
+ public void initDbSqlSessionFactory() {
+ if (dbSqlSessionFactory == null) {
+ dbSqlSessionFactory = createDbSqlSessionFactory();
+ }
+ dbSqlSessionFactory.setDatabaseType(databaseType);
+ dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory);
+ dbSqlSessionFactory.setDbHistoryUsed(isDbHistoryUsed);
+ dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix);
+ dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema);
+ dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog);
+ dbSqlSessionFactory.setDatabaseSchema(databaseSchema);
+ dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert);
+
+ initDbSqlSessionFactoryEntitySettings();
+
+ addSessionFactory(dbSqlSessionFactory);
+ }
+
+ public DbSqlSessionFactory createDbSqlSessionFactory() {
+ return new DbSqlSessionFactory(usePrefixId);
+ }
+
+ protected abstract void initDbSqlSessionFactoryEntitySettings();
+
+ protected void defaultInitDbSqlSessionFactoryEntitySettings(List> insertOrder, List> deleteOrder) {
+ if (insertOrder != null) {
+ for (Class extends Entity> clazz : insertOrder) {
+ dbSqlSessionFactory.getInsertionOrder().add(clazz);
+
+ if (isBulkInsertEnabled) {
+ dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz);
+ }
+ }
+ }
+
+ if (deleteOrder != null) {
+ for (Class extends Entity> clazz : deleteOrder) {
+ dbSqlSessionFactory.getDeletionOrder().add(clazz);
+ }
+ }
+ }
+
+ public void initTransactionFactory() {
+ if (transactionFactory == null) {
+ if (transactionsExternallyManaged) {
+ transactionFactory = new ManagedTransactionFactory();
+ Properties properties = new Properties();
+ properties.put("closeConnection", "false");
+ this.transactionFactory.setProperties(properties);
+ } else {
+ transactionFactory = new JdbcTransactionFactory();
+ }
+ }
+ }
+
+ public void initSqlSessionFactory() {
+ if (sqlSessionFactory == null) {
+ InputStream inputStream = null;
+ try {
+ inputStream = getMyBatisXmlConfigurationStream();
+
+ Environment environment = new Environment("default", transactionFactory, dataSource);
+ Reader reader = new InputStreamReader(inputStream);
+ Properties properties = new Properties();
+ properties.put("prefix", databaseTablePrefix);
+
+ String wildcardEscapeClause = "";
+ if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) {
+ wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'";
+ }
+ properties.put("wildcardEscapeClause", wildcardEscapeClause);
+
+ // set default properties
+ properties.put("limitBefore", "");
+ properties.put("limitAfter", "");
+ properties.put("limitBetween", "");
+ properties.put("limitBeforeNativeQuery", "");
+ properties.put("limitAfterNativeQuery", "");
+ properties.put("blobType", "BLOB");
+ properties.put("boolValue", "TRUE");
+
+ if (databaseType != null) {
+ properties.load(getResourceAsStream(pathToEngineDbProperties()));
+ }
+
+ Configuration configuration = initMybatisConfiguration(environment, reader, properties);
+ sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
+
+ } catch (Exception e) {
+ throw new FlowableException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
+ } finally {
+ IoUtil.closeSilently(inputStream);
+ }
+ } else {
+ // This is needed when the SQL Session Factory is created by another engine.
+ // When custom XML Mappers are registered with this engine they need to be loaded in the configuration as well
+ applyCustomMybatisCustomizations(sqlSessionFactory.getConfiguration());
+ }
+ }
+
+ public String pathToEngineDbProperties() {
+ return "org/flowable/common/db/properties/" + databaseType + ".properties";
+ }
+
+ public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) {
+ XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties);
+ Configuration configuration = parser.getConfiguration();
+
+ if (databaseType != null) {
+ configuration.setDatabaseId(databaseType);
+ }
+
+ configuration.setEnvironment(environment);
+
+ initMybatisTypeHandlers(configuration);
+ initCustomMybatisInterceptors(configuration);
+ if (isEnableLogSqlExecutionTime()) {
+ initMyBatisLogSqlExecutionTimePlugin(configuration);
+ }
+
+ configuration = parseMybatisConfiguration(parser);
+ return configuration;
+ }
+
+ public void initCustomMybatisMappers(Configuration configuration) {
+ if (getCustomMybatisMappers() != null) {
+ for (Class> clazz : getCustomMybatisMappers()) {
+ if (!configuration.hasMapper(clazz)) {
+ configuration.addMapper(clazz);
+ }
+ }
+ }
+ }
+
+ public void initMybatisTypeHandlers(Configuration configuration) {
+ // When mapping into Map there is currently a problem with MyBatis.
+ // It will return objects which are driver specific.
+ // Therefore we are registering the mappings between Object.class and the specific jdbc type here.
+ // see https://github.com/mybatis/mybatis-3/issues/2216 for more info
+ TypeHandlerRegistry handlerRegistry = configuration.getTypeHandlerRegistry();
+
+ handlerRegistry.register(Object.class, JdbcType.BOOLEAN, new BooleanTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.BIT, new BooleanTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.TINYINT, new ByteTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.SMALLINT, new ShortTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.INTEGER, new IntegerTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.FLOAT, new FloatTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.DOUBLE, new DoubleTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.CHAR, new StringTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.CLOB, new ClobTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.VARCHAR, new StringTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.NVARCHAR, new NStringTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.NCHAR, new NStringTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.NCLOB, new NClobTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.BIGINT, new LongTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.REAL, new BigDecimalTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.DECIMAL, new BigDecimalTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.NUMERIC, new BigDecimalTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.BLOB, new BlobInputStreamTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.DATE, new DateOnlyTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.TIME, new TimeOnlyTypeHandler());
+ handlerRegistry.register(Object.class, JdbcType.TIMESTAMP, new DateTypeHandler());
+
+ handlerRegistry.register(Object.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
+ }
+
+ public void initCustomMybatisInterceptors(Configuration configuration) {
+ if (customMybatisInterceptors!=null){
+ for (Interceptor interceptor :customMybatisInterceptors){
+ configuration.addInterceptor(interceptor);
+ }
+ }
+ }
+
+ public void initMyBatisLogSqlExecutionTimePlugin(Configuration configuration) {
+ configuration.addInterceptor(new LogSqlExecutionTimePlugin());
+ }
+
+ public Configuration parseMybatisConfiguration(XMLConfigBuilder parser) {
+ Configuration configuration = parser.parse();
+
+ applyCustomMybatisCustomizations(configuration);
+ return configuration;
+ }
+
+ protected void applyCustomMybatisCustomizations(Configuration configuration) {
+ initCustomMybatisMappers(configuration);
+
+ if (dependentEngineMybatisTypeAliasConfigs != null) {
+ for (MybatisTypeAliasConfigurator typeAliasConfig : dependentEngineMybatisTypeAliasConfigs) {
+ typeAliasConfig.configure(configuration.getTypeAliasRegistry());
+ }
+ }
+ if (dependentEngineMybatisTypeHandlerConfigs != null) {
+ for (MybatisTypeHandlerConfigurator typeHandlerConfig : dependentEngineMybatisTypeHandlerConfigs) {
+ typeHandlerConfig.configure(configuration.getTypeHandlerRegistry());
+ }
+ }
+
+ parseDependentEngineMybatisXMLMappers(configuration);
+ parseCustomMybatisXMLMappers(configuration);
+ }
+
+ public void parseCustomMybatisXMLMappers(Configuration configuration) {
+ if (getCustomMybatisXMLMappers() != null) {
+ for (String resource : getCustomMybatisXMLMappers()) {
+ parseMybatisXmlMapping(configuration, resource);
+ }
+ }
+ }
+
+ public void parseDependentEngineMybatisXMLMappers(Configuration configuration) {
+ if (getDependentEngineMyBatisXmlMappers() != null) {
+ for (String resource : getDependentEngineMyBatisXmlMappers()) {
+ parseMybatisXmlMapping(configuration, resource);
+ }
+ }
+ }
+
+ protected void parseMybatisXmlMapping(Configuration configuration, String resource) {
+ // see XMLConfigBuilder.mapperElement()
+ XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments());
+ mapperParser.parse();
+ }
+
+ protected InputStream getResourceAsStream(String resource) {
+ ClassLoader classLoader = getClassLoader();
+ if (classLoader != null) {
+ return getClassLoader().getResourceAsStream(resource);
+ } else {
+ return this.getClass().getClassLoader().getResourceAsStream(resource);
+ }
+ }
+
+ public void setMybatisMappingFile(String file) {
+ this.mybatisMappingFile = file;
+ }
+
+ public String getMybatisMappingFile() {
+ return mybatisMappingFile;
+ }
+
+ public abstract InputStream getMyBatisXmlConfigurationStream();
+
+ public void initConfigurators() {
+
+ allConfigurators = new ArrayList<>();
+ allConfigurators.addAll(getEngineSpecificEngineConfigurators());
+
+ // Configurators that are explicitly added to the config
+ if (configurators != null) {
+ allConfigurators.addAll(configurators);
+ }
+
+ // Auto discovery through ServiceLoader
+ if (enableConfiguratorServiceLoader) {
+ ClassLoader classLoader = getClassLoader();
+ if (classLoader == null) {
+ classLoader = ReflectUtil.getClassLoader();
+ }
+
+ ServiceLoader configuratorServiceLoader = ServiceLoader.load(EngineConfigurator.class, classLoader);
+ int nrOfServiceLoadedConfigurators = 0;
+ for (EngineConfigurator configurator : configuratorServiceLoader) {
+ allConfigurators.add(configurator);
+ nrOfServiceLoadedConfigurators++;
+ }
+
+ if (nrOfServiceLoadedConfigurators > 0) {
+ logger.info("Found {} auto-discoverable Process Engine Configurator{}", nrOfServiceLoadedConfigurators, nrOfServiceLoadedConfigurators > 1 ? "s" : "");
+ }
+
+ if (!allConfigurators.isEmpty()) {
+
+ // Order them according to the priorities (useful for dependent
+ // configurator)
+ allConfigurators.sort(new Comparator() {
+
+ @Override
+ public int compare(EngineConfigurator configurator1, EngineConfigurator configurator2) {
+ int priority1 = configurator1.getPriority();
+ int priority2 = configurator2.getPriority();
+
+ if (priority1 < priority2) {
+ return -1;
+ } else if (priority1 > priority2) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+
+ // Execute the configurators
+ logger.info("Found {} Engine Configurators in total:", allConfigurators.size());
+ for (EngineConfigurator configurator : allConfigurators) {
+ logger.info("{} (priority:{})", configurator.getClass(), configurator.getPriority());
+ }
+
+ }
+
+ }
+ }
+
+ public void close() {
+ if (forceCloseMybatisConnectionPool && dataSource instanceof PooledDataSource) {
+ /*
+ * When the datasource is created by a Flowable engine (i.e. it's an instance of PooledDataSource),
+ * the connection pool needs to be closed when closing the engine.
+ * Note that calling forceCloseAll() multiple times (as is the case when running with multiple engine) is ok.
+ */
+ ((PooledDataSource) dataSource).forceCloseAll();
+ }
+ }
+
+ protected List getEngineSpecificEngineConfigurators() {
+ // meant to be overridden if needed
+ return Collections.emptyList();
+ }
+
+ public void configuratorsBeforeInit() {
+ for (EngineConfigurator configurator : allConfigurators) {
+ logger.info("Executing beforeInit() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
+ configurator.beforeInit(this);
+ }
+ }
+
+ public void configuratorsAfterInit() {
+ for (EngineConfigurator configurator : allConfigurators) {
+ logger.info("Executing configure() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
+ configurator.configure(this);
+ }
+ }
+
+ public LockManager getLockManager(String lockName) {
+ return new LockManagerImpl(commandExecutor, lockName, getLockPollRate(), getEngineCfgKey());
+ }
+
+ // getters and setters
+ // //////////////////////////////////////////////////////
+
+ public abstract String getEngineName();
+
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ public AbstractEngineConfiguration setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ return this;
+ }
+
+ public boolean isUseClassForNameClassLoading() {
+ return useClassForNameClassLoading;
+ }
+
+ public AbstractEngineConfiguration setUseClassForNameClassLoading(boolean useClassForNameClassLoading) {
+ this.useClassForNameClassLoading = useClassForNameClassLoading;
+ return this;
+ }
+
+ public void addEngineLifecycleListener(EngineLifecycleListener engineLifecycleListener) {
+ if (this.engineLifecycleListeners == null) {
+ this.engineLifecycleListeners = new ArrayList<>();
+ }
+ this.engineLifecycleListeners.add(engineLifecycleListener);
+ }
+
+ public List getEngineLifecycleListeners() {
+ return engineLifecycleListeners;
+ }
+
+ public AbstractEngineConfiguration setEngineLifecycleListeners(List engineLifecycleListeners) {
+ this.engineLifecycleListeners = engineLifecycleListeners;
+ return this;
+ }
+
+ public String getDatabaseType() {
+ return databaseType;
+ }
+
+ public AbstractEngineConfiguration setDatabaseType(String databaseType) {
+ this.databaseType = databaseType;
+ return this;
+ }
+
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ public AbstractEngineConfiguration setDataSource(DataSource dataSource) {
+ this.dataSource = dataSource;
+ return this;
+ }
+
+ public SchemaManager getSchemaManager() {
+ return schemaManager;
+ }
+
+ public AbstractEngineConfiguration setSchemaManager(SchemaManager schemaManager) {
+ this.schemaManager = schemaManager;
+ return this;
+ }
+
+ public SchemaManager getCommonSchemaManager() {
+ return commonSchemaManager;
+ }
+
+ public AbstractEngineConfiguration setCommonSchemaManager(SchemaManager commonSchemaManager) {
+ this.commonSchemaManager = commonSchemaManager;
+ return this;
+ }
+
+ public Command getSchemaManagementCmd() {
+ return schemaManagementCmd;
+ }
+
+ public AbstractEngineConfiguration setSchemaManagementCmd(Command schemaManagementCmd) {
+ this.schemaManagementCmd = schemaManagementCmd;
+ return this;
+ }
+
+ public String getJdbcDriver() {
+ return jdbcDriver;
+ }
+
+ public AbstractEngineConfiguration setJdbcDriver(String jdbcDriver) {
+ this.jdbcDriver = jdbcDriver;
+ return this;
+ }
+
+ public String getJdbcUrl() {
+ return jdbcUrl;
+ }
+
+ public AbstractEngineConfiguration setJdbcUrl(String jdbcUrl) {
+ this.jdbcUrl = jdbcUrl;
+ return this;
+ }
+
+ public String getJdbcUsername() {
+ return jdbcUsername;
+ }
+
+ public AbstractEngineConfiguration setJdbcUsername(String jdbcUsername) {
+ this.jdbcUsername = jdbcUsername;
+ return this;
+ }
+
+ public String getJdbcPassword() {
+ return jdbcPassword;
+ }
+
+ public AbstractEngineConfiguration setJdbcPassword(String jdbcPassword) {
+ this.jdbcPassword = jdbcPassword;
+ return this;
+ }
+
+ public int getJdbcMaxActiveConnections() {
+ return jdbcMaxActiveConnections;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxActiveConnections(int jdbcMaxActiveConnections) {
+ this.jdbcMaxActiveConnections = jdbcMaxActiveConnections;
+ return this;
+ }
+
+ public int getJdbcMaxIdleConnections() {
+ return jdbcMaxIdleConnections;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxIdleConnections(int jdbcMaxIdleConnections) {
+ this.jdbcMaxIdleConnections = jdbcMaxIdleConnections;
+ return this;
+ }
+
+ public int getJdbcMaxCheckoutTime() {
+ return jdbcMaxCheckoutTime;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxCheckoutTime(int jdbcMaxCheckoutTime) {
+ this.jdbcMaxCheckoutTime = jdbcMaxCheckoutTime;
+ return this;
+ }
+
+ public int getJdbcMaxWaitTime() {
+ return jdbcMaxWaitTime;
+ }
+
+ public AbstractEngineConfiguration setJdbcMaxWaitTime(int jdbcMaxWaitTime) {
+ this.jdbcMaxWaitTime = jdbcMaxWaitTime;
+ return this;
+ }
+
+ public boolean isJdbcPingEnabled() {
+ return jdbcPingEnabled;
+ }
+
+ public AbstractEngineConfiguration setJdbcPingEnabled(boolean jdbcPingEnabled) {
+ this.jdbcPingEnabled = jdbcPingEnabled;
+ return this;
+ }
+
+ public int getJdbcPingConnectionNotUsedFor() {
+ return jdbcPingConnectionNotUsedFor;
+ }
+
+ public AbstractEngineConfiguration setJdbcPingConnectionNotUsedFor(int jdbcPingConnectionNotUsedFor) {
+ this.jdbcPingConnectionNotUsedFor = jdbcPingConnectionNotUsedFor;
+ return this;
+ }
+
+ public int getJdbcDefaultTransactionIsolationLevel() {
+ return jdbcDefaultTransactionIsolationLevel;
+ }
+
+ public AbstractEngineConfiguration setJdbcDefaultTransactionIsolationLevel(int jdbcDefaultTransactionIsolationLevel) {
+ this.jdbcDefaultTransactionIsolationLevel = jdbcDefaultTransactionIsolationLevel;
+ return this;
+ }
+
+ public String getJdbcPingQuery() {
+ return jdbcPingQuery;
+ }
+
+ public AbstractEngineConfiguration setJdbcPingQuery(String jdbcPingQuery) {
+ this.jdbcPingQuery = jdbcPingQuery;
+ return this;
+ }
+
+ public String getDataSourceJndiName() {
+ return dataSourceJndiName;
+ }
+
+ public AbstractEngineConfiguration setDataSourceJndiName(String dataSourceJndiName) {
+ this.dataSourceJndiName = dataSourceJndiName;
+ return this;
+ }
+
+ public CommandConfig getSchemaCommandConfig() {
+ return schemaCommandConfig;
+ }
+
+ public AbstractEngineConfiguration setSchemaCommandConfig(CommandConfig schemaCommandConfig) {
+ this.schemaCommandConfig = schemaCommandConfig;
+ return this;
+ }
+
+ public boolean isTransactionsExternallyManaged() {
+ return transactionsExternallyManaged;
+ }
+
+ public AbstractEngineConfiguration setTransactionsExternallyManaged(boolean transactionsExternallyManaged) {
+ this.transactionsExternallyManaged = transactionsExternallyManaged;
+ return this;
+ }
+
+ public Map getBeans() {
+ return beans;
+ }
+
+ public AbstractEngineConfiguration setBeans(Map beans) {
+ this.beans = beans;
+ return this;
+ }
+
+ public IdGenerator getIdGenerator() {
+ return idGenerator;
+ }
+
+ public AbstractEngineConfiguration setIdGenerator(IdGenerator idGenerator) {
+ this.idGenerator = idGenerator;
+ return this;
+ }
+
+ public boolean isUsePrefixId() {
+ return usePrefixId;
+ }
+
+ public AbstractEngineConfiguration setUsePrefixId(boolean usePrefixId) {
+ this.usePrefixId = usePrefixId;
+ return this;
+ }
+
+ public String getXmlEncoding() {
+ return xmlEncoding;
+ }
+
+ public AbstractEngineConfiguration setXmlEncoding(String xmlEncoding) {
+ this.xmlEncoding = xmlEncoding;
+ return this;
+ }
+
+ public CommandConfig getDefaultCommandConfig() {
+ return defaultCommandConfig;
+ }
+
+ public AbstractEngineConfiguration setDefaultCommandConfig(CommandConfig defaultCommandConfig) {
+ this.defaultCommandConfig = defaultCommandConfig;
+ return this;
+ }
+
+ public CommandExecutor getCommandExecutor() {
+ return commandExecutor;
+ }
+
+ public AbstractEngineConfiguration setCommandExecutor(CommandExecutor commandExecutor) {
+ this.commandExecutor = commandExecutor;
+ return this;
+ }
+
+ public CommandContextFactory getCommandContextFactory() {
+ return commandContextFactory;
+ }
+
+ public AbstractEngineConfiguration setCommandContextFactory(CommandContextFactory commandContextFactory) {
+ this.commandContextFactory = commandContextFactory;
+ return this;
+ }
+
+ public CommandInterceptor getCommandInvoker() {
+ return commandInvoker;
+ }
+
+ public AbstractEngineConfiguration setCommandInvoker(CommandInterceptor commandInvoker) {
+ this.commandInvoker = commandInvoker;
+ return this;
+ }
+
+ public AgendaOperationRunner getAgendaOperationRunner() {
+ return agendaOperationRunner;
+ }
+
+ public AbstractEngineConfiguration setAgendaOperationRunner(AgendaOperationRunner agendaOperationRunner) {
+ this.agendaOperationRunner = agendaOperationRunner;
+ return this;
+ }
+
+ public List getCustomPreCommandInterceptors() {
+ return customPreCommandInterceptors;
+ }
+
+ public AbstractEngineConfiguration setCustomPreCommandInterceptors(List customPreCommandInterceptors) {
+ this.customPreCommandInterceptors = customPreCommandInterceptors;
+ return this;
+ }
+
+ public List getCustomPostCommandInterceptors() {
+ return customPostCommandInterceptors;
+ }
+
+ public AbstractEngineConfiguration setCustomPostCommandInterceptors(List customPostCommandInterceptors) {
+ this.customPostCommandInterceptors = customPostCommandInterceptors;
+ return this;
+ }
+
+ public List getCommandInterceptors() {
+ return commandInterceptors;
+ }
+
+ public AbstractEngineConfiguration setCommandInterceptors(List commandInterceptors) {
+ this.commandInterceptors = commandInterceptors;
+ return this;
+ }
+
+ public Map getEngineConfigurations() {
+ return engineConfigurations;
+ }
+
+ public AbstractEngineConfiguration setEngineConfigurations(Map engineConfigurations) {
+ this.engineConfigurations = engineConfigurations;
+ return this;
+ }
+
+ public void addEngineConfiguration(String key, String scopeType, AbstractEngineConfiguration engineConfiguration) {
+ if (engineConfigurations == null) {
+ engineConfigurations = new HashMap<>();
+ }
+ engineConfigurations.put(key, engineConfiguration);
+ engineConfigurations.put(scopeType, engineConfiguration);
+ }
+
+ public Map getServiceConfigurations() {
+ return serviceConfigurations;
+ }
+
+ public AbstractEngineConfiguration setServiceConfigurations(Map serviceConfigurations) {
+ this.serviceConfigurations = serviceConfigurations;
+ return this;
+ }
+
+ public void addServiceConfiguration(String key, AbstractServiceConfiguration serviceConfiguration) {
+ if (serviceConfigurations == null) {
+ serviceConfigurations = new HashMap<>();
+ }
+ serviceConfigurations.put(key, serviceConfiguration);
+ }
+
+ public Map getEventRegistryEventConsumers() {
+ return eventRegistryEventConsumers;
+ }
+
+ public AbstractEngineConfiguration setEventRegistryEventConsumers(Map eventRegistryEventConsumers) {
+ this.eventRegistryEventConsumers = eventRegistryEventConsumers;
+ return this;
+ }
+
+ public void addEventRegistryEventConsumer(String key, EventRegistryEventConsumer eventRegistryEventConsumer) {
+ if (eventRegistryEventConsumers == null) {
+ eventRegistryEventConsumers = new HashMap<>();
+ }
+ eventRegistryEventConsumers.put(key, eventRegistryEventConsumer);
+ }
+
+ public AbstractEngineConfiguration setDefaultCommandInterceptors(Collection extends CommandInterceptor> defaultCommandInterceptors) {
+ this.defaultCommandInterceptors = defaultCommandInterceptors;
+ return this;
+ }
+
+ public SqlSessionFactory getSqlSessionFactory() {
+ return sqlSessionFactory;
+ }
+
+ public AbstractEngineConfiguration setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
+ this.sqlSessionFactory = sqlSessionFactory;
+ return this;
+ }
+
+ public boolean isDbHistoryUsed() {
+ return isDbHistoryUsed;
+ }
+
+ public AbstractEngineConfiguration setDbHistoryUsed(boolean isDbHistoryUsed) {
+ this.isDbHistoryUsed = isDbHistoryUsed;
+ return this;
+ }
+
+ public DbSqlSessionFactory getDbSqlSessionFactory() {
+ return dbSqlSessionFactory;
+ }
+
+ public AbstractEngineConfiguration setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) {
+ this.dbSqlSessionFactory = dbSqlSessionFactory;
+ return this;
+ }
+
+ public TransactionFactory getTransactionFactory() {
+ return transactionFactory;
+ }
+
+ public AbstractEngineConfiguration setTransactionFactory(TransactionFactory transactionFactory) {
+ this.transactionFactory = transactionFactory;
+ return this;
+ }
+
+ public TransactionContextFactory getTransactionContextFactory() {
+ return transactionContextFactory;
+ }
+
+ public AbstractEngineConfiguration setTransactionContextFactory(TransactionContextFactory transactionContextFactory) {
+ this.transactionContextFactory = transactionContextFactory;
+ return this;
+ }
+
+ public int getMaxNrOfStatementsInBulkInsert() {
+ return maxNrOfStatementsInBulkInsert;
+ }
+
+ public AbstractEngineConfiguration setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) {
+ this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert;
+ return this;
+ }
+
+ public boolean isBulkInsertEnabled() {
+ return isBulkInsertEnabled;
+ }
+
+ public AbstractEngineConfiguration setBulkInsertEnabled(boolean isBulkInsertEnabled) {
+ this.isBulkInsertEnabled = isBulkInsertEnabled;
+ return this;
+ }
+
+ public Set> getCustomMybatisMappers() {
+ return customMybatisMappers;
+ }
+
+ public AbstractEngineConfiguration setCustomMybatisMappers(Set> customMybatisMappers) {
+ this.customMybatisMappers = customMybatisMappers;
+ return this;
+ }
+
+ public Set getCustomMybatisXMLMappers() {
+ return customMybatisXMLMappers;
+ }
+
+ public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set customMybatisXMLMappers) {
+ this.customMybatisXMLMappers = customMybatisXMLMappers;
+ return this;
+ }
+
+ public Set getDependentEngineMyBatisXmlMappers() {
+ return dependentEngineMyBatisXmlMappers;
+ }
+
+ public AbstractEngineConfiguration setCustomMybatisInterceptors(List customMybatisInterceptors) {
+ this.customMybatisInterceptors = customMybatisInterceptors;
+ return this;
+ }
+
+ public List getCustomMybatisInterceptors() {
+ return customMybatisInterceptors;
+ }
+
+ public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set dependentEngineMyBatisXmlMappers) {
+ this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers;
+ return this;
+ }
+
+ public List getDependentEngineMybatisTypeAliasConfigs() {
+ return dependentEngineMybatisTypeAliasConfigs;
+ }
+
+ public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List dependentEngineMybatisTypeAliasConfigs) {
+ this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs;
+ return this;
+ }
+
+ public List getDependentEngineMybatisTypeHandlerConfigs() {
+ return dependentEngineMybatisTypeHandlerConfigs;
+ }
+
+ public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List dependentEngineMybatisTypeHandlerConfigs) {
+ this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs;
+ return this;
+ }
+
+ public List getCustomSessionFactories() {
+ return customSessionFactories;
+ }
+
+ public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) {
+ if (customSessionFactories == null) {
+ customSessionFactories = new ArrayList<>();
+ }
+ customSessionFactories.add(sessionFactory);
+ return this;
+ }
+
+ public AbstractEngineConfiguration setCustomSessionFactories(List customSessionFactories) {
+ this.customSessionFactories = customSessionFactories;
+ return this;
+ }
+
+ public boolean isUsingRelationalDatabase() {
+ return usingRelationalDatabase;
+ }
+
+ public AbstractEngineConfiguration setUsingRelationalDatabase(boolean usingRelationalDatabase) {
+ this.usingRelationalDatabase = usingRelationalDatabase;
+ return this;
+ }
+
+ public boolean isUsingSchemaMgmt() {
+ return usingSchemaMgmt;
+ }
+
+ public AbstractEngineConfiguration setUsingSchemaMgmt(boolean usingSchema) {
+ this.usingSchemaMgmt = usingSchema;
+ return this;
+ }
+
+ public String getDatabaseTablePrefix() {
+ return databaseTablePrefix;
+ }
+
+ public AbstractEngineConfiguration setDatabaseTablePrefix(String databaseTablePrefix) {
+ this.databaseTablePrefix = databaseTablePrefix;
+ return this;
+ }
+
+ public String getDatabaseWildcardEscapeCharacter() {
+ return databaseWildcardEscapeCharacter;
+ }
+
+ public AbstractEngineConfiguration setDatabaseWildcardEscapeCharacter(String databaseWildcardEscapeCharacter) {
+ this.databaseWildcardEscapeCharacter = databaseWildcardEscapeCharacter;
+ return this;
+ }
+
+ public String getDatabaseCatalog() {
+ return databaseCatalog;
+ }
+
+ public AbstractEngineConfiguration setDatabaseCatalog(String databaseCatalog) {
+ this.databaseCatalog = databaseCatalog;
+ return this;
+ }
+
+ public String getDatabaseSchema() {
+ return databaseSchema;
+ }
+
+ public AbstractEngineConfiguration setDatabaseSchema(String databaseSchema) {
+ this.databaseSchema = databaseSchema;
+ return this;
+ }
+
+ public boolean isTablePrefixIsSchema() {
+ return tablePrefixIsSchema;
+ }
+
+ public AbstractEngineConfiguration setTablePrefixIsSchema(boolean tablePrefixIsSchema) {
+ this.tablePrefixIsSchema = tablePrefixIsSchema;
+ return this;
+ }
+
+ public boolean isAlwaysLookupLatestDefinitionVersion() {
+ return alwaysLookupLatestDefinitionVersion;
+ }
+
+ public AbstractEngineConfiguration setAlwaysLookupLatestDefinitionVersion(boolean alwaysLookupLatestDefinitionVersion) {
+ this.alwaysLookupLatestDefinitionVersion = alwaysLookupLatestDefinitionVersion;
+ return this;
+ }
+
+ public boolean isFallbackToDefaultTenant() {
+ return fallbackToDefaultTenant;
+ }
+
+ public AbstractEngineConfiguration setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) {
+ this.fallbackToDefaultTenant = fallbackToDefaultTenant;
+ return this;
+ }
+
+ /**
+ * @return name of the default tenant
+ * @deprecated use {@link AbstractEngineConfiguration#getDefaultTenantProvider()} instead
+ */
+ @Deprecated
+ public String getDefaultTenantValue() {
+ return getDefaultTenantProvider().getDefaultTenant(null, null, null);
+ }
+
+ public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) {
+ this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue;
+ return this;
+ }
+
+ public DefaultTenantProvider getDefaultTenantProvider() {
+ return defaultTenantProvider;
+ }
+
+ public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) {
+ this.defaultTenantProvider = defaultTenantProvider;
+ return this;
+ }
+
+ public boolean isEnableLogSqlExecutionTime() {
+ return enableLogSqlExecutionTime;
+ }
+
+ public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) {
+ this.enableLogSqlExecutionTime = enableLogSqlExecutionTime;
+ }
+
+ public Map, SessionFactory> getSessionFactories() {
+ return sessionFactories;
+ }
+
+ public AbstractEngineConfiguration setSessionFactories(Map, SessionFactory> sessionFactories) {
+ this.sessionFactories = sessionFactories;
+ return this;
+ }
+
+ public String getDatabaseSchemaUpdate() {
+ return databaseSchemaUpdate;
+ }
+
+ public AbstractEngineConfiguration setDatabaseSchemaUpdate(String databaseSchemaUpdate) {
+ this.databaseSchemaUpdate = databaseSchemaUpdate;
+ return this;
+ }
+
+ public boolean isUseLockForDatabaseSchemaUpdate() {
+ return useLockForDatabaseSchemaUpdate;
+ }
+
+ public AbstractEngineConfiguration setUseLockForDatabaseSchemaUpdate(boolean useLockForDatabaseSchemaUpdate) {
+ this.useLockForDatabaseSchemaUpdate = useLockForDatabaseSchemaUpdate;
+ return this;
+ }
+
+ public boolean isEnableEventDispatcher() {
+ return enableEventDispatcher;
+ }
+
+ public AbstractEngineConfiguration setEnableEventDispatcher(boolean enableEventDispatcher) {
+ this.enableEventDispatcher = enableEventDispatcher;
+ return this;
+ }
+
+ public FlowableEventDispatcher getEventDispatcher() {
+ return eventDispatcher;
+ }
+
+ public AbstractEngineConfiguration setEventDispatcher(FlowableEventDispatcher eventDispatcher) {
+ this.eventDispatcher = eventDispatcher;
+ return this;
+ }
+
+ public List getEventListeners() {
+ return eventListeners;
+ }
+
+ public AbstractEngineConfiguration setEventListeners(List eventListeners) {
+ this.eventListeners = eventListeners;
+ return this;
+ }
+
+ public Map> getTypedEventListeners() {
+ return typedEventListeners;
+ }
+
+ public AbstractEngineConfiguration setTypedEventListeners(Map> typedEventListeners) {
+ this.typedEventListeners = typedEventListeners;
+ return this;
+ }
+
+ public List getAdditionalEventDispatchActions() {
+ return additionalEventDispatchActions;
+ }
+
+ public AbstractEngineConfiguration setAdditionalEventDispatchActions(List additionalEventDispatchActions) {
+ this.additionalEventDispatchActions = additionalEventDispatchActions;
+ return this;
+ }
+
+ public void initEventDispatcher() {
+ if (this.eventDispatcher == null) {
+ this.eventDispatcher = new FlowableEventDispatcherImpl();
+ }
+
+ initAdditionalEventDispatchActions();
+
+ this.eventDispatcher.setEnabled(enableEventDispatcher);
+
+ initEventListeners();
+ initTypedEventListeners();
+ }
+
+ protected void initEventListeners() {
+ if (eventListeners != null) {
+ for (FlowableEventListener listenerToAdd : eventListeners) {
+ this.eventDispatcher.addEventListener(listenerToAdd);
+ }
+ }
+ }
+
+ protected void initAdditionalEventDispatchActions() {
+ if (this.additionalEventDispatchActions == null) {
+ this.additionalEventDispatchActions = new ArrayList<>();
+ }
+ }
+
+ protected void initTypedEventListeners() {
+ if (typedEventListeners != null) {
+ for (Map.Entry> listenersToAdd : typedEventListeners.entrySet()) {
+ // Extract types from the given string
+ FlowableEngineEventType[] types = FlowableEngineEventType.getTypesFromString(listenersToAdd.getKey());
+
+ for (FlowableEventListener listenerToAdd : listenersToAdd.getValue()) {
+ this.eventDispatcher.addEventListener(listenerToAdd, types);
+ }
+ }
+ }
+ }
+
+ public boolean isLoggingSessionEnabled() {
+ return loggingListener != null;
+ }
+
+ public LoggingListener getLoggingListener() {
+ return loggingListener;
+ }
+
+ public void setLoggingListener(LoggingListener loggingListener) {
+ this.loggingListener = loggingListener;
+ }
+
+ public Clock getClock() {
+ return clock;
+ }
+
+ public AbstractEngineConfiguration setClock(Clock clock) {
+ this.clock = clock;
+ return this;
+ }
+
+ public ObjectMapper getObjectMapper() {
+ return objectMapper;
+ }
+
+ public AbstractEngineConfiguration setObjectMapper(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ return this;
+ }
+
+ public int getMaxLengthString() {
+ if (maxLengthStringVariableType == -1) {
+ if ("oracle".equalsIgnoreCase(databaseType)) {
+ return DEFAULT_ORACLE_MAX_LENGTH_STRING;
+ } else {
+ return DEFAULT_GENERIC_MAX_LENGTH_STRING;
+ }
+ } else {
+ return maxLengthStringVariableType;
+ }
+ }
+
+ public int getMaxLengthStringVariableType() {
+ return maxLengthStringVariableType;
+ }
+
+ public AbstractEngineConfiguration setMaxLengthStringVariableType(int maxLengthStringVariableType) {
+ this.maxLengthStringVariableType = maxLengthStringVariableType;
+ return this;
+ }
+
+ public PropertyDataManager getPropertyDataManager() {
+ return propertyDataManager;
+ }
+
+ public Duration getLockPollRate() {
+ return lockPollRate;
+ }
+
+ public AbstractEngineConfiguration setLockPollRate(Duration lockPollRate) {
+ this.lockPollRate = lockPollRate;
+ return this;
+ }
+
+ public Duration getSchemaLockWaitTime() {
+ return schemaLockWaitTime;
+ }
+
+ public void setSchemaLockWaitTime(Duration schemaLockWaitTime) {
+ this.schemaLockWaitTime = schemaLockWaitTime;
+ }
+
+ public AbstractEngineConfiguration setPropertyDataManager(PropertyDataManager propertyDataManager) {
+ this.propertyDataManager = propertyDataManager;
+ return this;
+ }
+
+ public PropertyEntityManager getPropertyEntityManager() {
+ return propertyEntityManager;
+ }
+
+ public AbstractEngineConfiguration setPropertyEntityManager(PropertyEntityManager propertyEntityManager) {
+ this.propertyEntityManager = propertyEntityManager;
+ return this;
+ }
+
+ public ByteArrayDataManager getByteArrayDataManager() {
+ return byteArrayDataManager;
+ }
+
+ public AbstractEngineConfiguration setByteArrayDataManager(ByteArrayDataManager byteArrayDataManager) {
+ this.byteArrayDataManager = byteArrayDataManager;
+ return this;
+ }
+
+ public ByteArrayEntityManager getByteArrayEntityManager() {
+ return byteArrayEntityManager;
+ }
+
+ public AbstractEngineConfiguration setByteArrayEntityManager(ByteArrayEntityManager byteArrayEntityManager) {
+ this.byteArrayEntityManager = byteArrayEntityManager;
+ return this;
+ }
+
+ public TableDataManager getTableDataManager() {
+ return tableDataManager;
+ }
+
+ public AbstractEngineConfiguration setTableDataManager(TableDataManager tableDataManager) {
+ this.tableDataManager = tableDataManager;
+ return this;
+ }
+
+ public List getDeployers() {
+ return deployers;
+ }
+
+ public AbstractEngineConfiguration setDeployers(List deployers) {
+ this.deployers = deployers;
+ return this;
+ }
+
+ public List getCustomPreDeployers() {
+ return customPreDeployers;
+ }
+
+ public AbstractEngineConfiguration setCustomPreDeployers(List customPreDeployers) {
+ this.customPreDeployers = customPreDeployers;
+ return this;
+ }
+
+ public List getCustomPostDeployers() {
+ return customPostDeployers;
+ }
+
+ public AbstractEngineConfiguration setCustomPostDeployers(List customPostDeployers) {
+ this.customPostDeployers = customPostDeployers;
+ return this;
+ }
+
+ public boolean isEnableConfiguratorServiceLoader() {
+ return enableConfiguratorServiceLoader;
+ }
+
+ public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) {
+ this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader;
+ return this;
+ }
+
+ public List getConfigurators() {
+ return configurators;
+ }
+
+ public AbstractEngineConfiguration addConfigurator(EngineConfigurator configurator) {
+ if (configurators == null) {
+ configurators = new ArrayList<>();
+ }
+ configurators.add(configurator);
+ return this;
+ }
+
+ /**
+ * @return All {@link EngineConfigurator} instances. Will only contain values after init of the engine.
+ * Use the {@link #getConfigurators()} or {@link #addConfigurator(EngineConfigurator)} methods otherwise.
+ */
+ public List getAllConfigurators() {
+ return allConfigurators;
+ }
+
+ public AbstractEngineConfiguration setConfigurators(List configurators) {
+ this.configurators = configurators;
+ return this;
+ }
+
+ public EngineConfigurator getIdmEngineConfigurator() {
+ return idmEngineConfigurator;
+ }
+
+ public AbstractEngineConfiguration setIdmEngineConfigurator(EngineConfigurator idmEngineConfigurator) {
+ this.idmEngineConfigurator = idmEngineConfigurator;
+ return this;
+ }
+
+ public EngineConfigurator getEventRegistryConfigurator() {
+ return eventRegistryConfigurator;
+ }
+
+ public AbstractEngineConfiguration setEventRegistryConfigurator(EngineConfigurator eventRegistryConfigurator) {
+ this.eventRegistryConfigurator = eventRegistryConfigurator;
+ return this;
+ }
+
+ public AbstractEngineConfiguration setForceCloseMybatisConnectionPool(boolean forceCloseMybatisConnectionPool) {
+ this.forceCloseMybatisConnectionPool = forceCloseMybatisConnectionPool;
+ return this;
+ }
+
+ public boolean isForceCloseMybatisConnectionPool() {
+ return forceCloseMybatisConnectionPool;
+ }
+}
diff --git a/sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md b/sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md
new file mode 100644
index 000000000..1932c7a3f
--- /dev/null
+++ b/sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md
@@ -0,0 +1 @@
+防止IDEA将`.`和`/`混为一谈
\ No newline at end of file
diff --git a/sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database b/sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database
new file mode 100644
index 000000000..efbcfcca2
--- /dev/null
+++ b/sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database
@@ -0,0 +1,21 @@
+liquibase.database.core.CockroachDatabase
+liquibase.database.core.DB2Database
+liquibase.database.core.Db2zDatabase
+liquibase.database.core.DerbyDatabase
+liquibase.database.core.Firebird3Database
+liquibase.database.core.FirebirdDatabase
+liquibase.database.core.H2Database
+liquibase.database.core.HsqlDatabase
+liquibase.database.core.InformixDatabase
+liquibase.database.core.Ingres9Database
+liquibase.database.core.MSSQLDatabase
+liquibase.database.core.MariaDBDatabase
+liquibase.database.core.MockDatabase
+liquibase.database.core.MySQLDatabase
+liquibase.database.core.OracleDatabase
+liquibase.database.core.PostgresDatabase
+liquibase.database.core.SQLiteDatabase
+liquibase.database.core.SybaseASADatabase
+liquibase.database.core.SybaseDatabase
+liquibase.database.core.DmDatabase
+liquibase.database.core.UnsupportedDatabase
diff --git a/sql/mysql/crm.sql b/sql/mysql/crm.sql
new file mode 100644
index 000000000..135826585
--- /dev/null
+++ b/sql/mysql/crm.sql
@@ -0,0 +1,3 @@
+SET NAMES utf8mb4;
+-- `ruoyi-vue-pro`.crm_contact definition
+
diff --git a/sql/mysql/crm_data.sql b/sql/mysql/crm_data.sql
new file mode 100644
index 000000000..b5be1e691
--- /dev/null
+++ b/sql/mysql/crm_data.sql
@@ -0,0 +1,20 @@
+
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (184, '回款管理审批状态', 'crm_receivable_check_status', 0, '回款管理审批状态(0 未审核 1 审核通过 2 审核拒绝 3 审核中 4 已撤回)', '1', '2023-10-18 21:44:24', '1', '2023-10-18 21:44:24', b'0', '1970-01-01 00:00:00');
+
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (185, '回款管理-回款方式', 'crm_return_type', 0, '回款管理-回款方式', '1', '2023-10-18 21:54:10', '1', '2023-10-18 21:54:10', b'0', '1970-01-01 00:00:00');
+
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1389, 0, '未审核', '0', 'crm_receivable_check_status', 0, 'default', '', '0 未审核 ', '1', '2023-10-18 21:46:00', '1', '2023-10-18 21:47:16', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1390, 1, '审核通过', '1', 'crm_receivable_check_status', 0, 'default', '', '1 审核通过', '1', '2023-10-18 21:46:18', '1', '2023-10-18 21:47:08', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1391, 2, '审核拒绝', '2', 'crm_receivable_check_status', 0, 'default', '', ' 2 审核拒绝', '1', '2023-10-18 21:46:58', '1', '2023-10-18 21:47:21', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1392, 3, '审核中', '3', 'crm_receivable_check_status', 0, 'default', '', ' 3 审核中', '1', '2023-10-18 21:47:35', '1', '2023-10-18 21:47:35', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1393, 4, '已撤回', '4', 'crm_receivable_check_status', 0, 'default', '', ' 4 已撤回', '1', '2023-10-18 21:47:46', '1', '2023-10-18 21:47:46', b'0');
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1394, 1, '支票', '1', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:29', '1', '2023-10-18 21:54:29', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1395, 2, '现金', '2', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:41', '1', '2023-10-18 21:54:41', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1396, 3, '邮政汇款', '3', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:54:53', '1', '2023-10-18 21:54:53', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1397, 4, '电汇', '4', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:07', '1', '2023-10-18 21:55:07', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1398, 5, '网上转账', '5', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:24', '1', '2023-10-18 21:55:24', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1399, 6, '支付宝', '6', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:38', '1', '2023-10-18 21:55:38', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1400, 7, '微信支付', '7', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:55:53', '1', '2023-10-18 21:55:53', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1401, 8, '其他', '8', 'crm_return_type', 0, 'default', '', '', '1', '2023-10-18 21:56:06', '1', '2023-10-18 21:56:06', b'0');
diff --git a/sql/mysql/crm_menu.sql b/sql/mysql/crm_menu.sql
new file mode 100644
index 000000000..e69de29bb
diff --git a/sql/mysql/pay_wallet.sql b/sql/mysql/pay_wallet.sql
index 7c6c04003..1e9f1d255 100644
--- a/sql/mysql/pay_wallet.sql
+++ b/sql/mysql/pay_wallet.sql
@@ -246,3 +246,11 @@ VALUES (
'转账订单', '', 2, 3, 1117,
'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer'
);
+
+-- 转账通知脚本
+
+ALTER TABLE `pay_app`
+ ADD COLUMN `transfer_notify_url` varchar(1024) NOT NULL COMMENT '转账结果的回调地址' AFTER `refund_notify_url`;
+ALTER TABLE `pay_notify_task`
+ MODIFY COLUMN `merchant_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '商户订单编号' AFTER `status`,
+ ADD COLUMN `merchant_transfer_id` varchar(64) COMMENT '商户转账单编号' AFTER `merchant_order_id`;
\ No newline at end of file
diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql
index ebae9f87f..a18800f3f 100644
--- a/sql/mysql/ruoyi-vue-pro.sql
+++ b/sql/mysql/ruoyi-vue-pro.sql
@@ -3,15 +3,15 @@
Source Server : 127.0.0.1 MySQL
Source Server Type : MySQL
- Source Server Version : 80034
- Source Host : localhost:3306
+ Source Server Version : 80200 (8.2.0)
+ Source Host : 127.0.0.1:3306
Source Schema : ruoyi-vue-pro
Target Server Type : MySQL
- Target Server Version : 80034
+ Target Server Version : 80200 (8.2.0)
File Encoding : 65001
- Date: 18/11/2023 17:48:18
+ Date: 30/11/2023 21:13:06
*/
SET NAMES utf8mb4;
@@ -385,7 +385,7 @@ CREATE TABLE `infra_api_error_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1964 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 2018 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
-- ----------------------------
-- Records of infra_api_error_log
@@ -423,7 +423,7 @@ CREATE TABLE `infra_codegen_column` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1905 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 2000 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
-- ----------------------------
-- Records of infra_codegen_column
@@ -461,7 +461,7 @@ CREATE TABLE `infra_codegen_table` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 146 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 155 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
-- ----------------------------
-- Records of infra_codegen_table
@@ -568,7 +568,7 @@ CREATE TABLE `infra_demo02_category` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '示例分类表';
+) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '示例分类表';
-- ----------------------------
-- Records of infra_demo02_category
@@ -579,6 +579,7 @@ INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `crea
INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, '怪怪', 0, '1', '2023-11-16 20:24:32', '1', '2023-11-16 20:24:32', b'0', 1);
INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, '小番茄', 2, '1', '2023-11-16 20:24:39', '1', '2023-11-16 20:24:39', b'0', 1);
INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, '大番茄', 2, '1', '2023-11-16 20:24:46', '1', '2023-11-16 20:24:46', b'0', 1);
+INSERT INTO `infra_demo02_category` (`id`, `name`, `parent_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, '11', 3, '1', '2023-11-24 19:29:34', '1', '2023-11-24 19:29:34', b'0', 1);
COMMIT;
-- ----------------------------
@@ -651,7 +652,7 @@ CREATE TABLE `infra_demo03_student` (
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
`sex` tinyint NOT NULL COMMENT '性别',
`birthday` datetime NOT NULL COMMENT '出生日期',
- `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介',
+ `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -765,7 +766,7 @@ CREATE TABLE `infra_job` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
+) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
-- ----------------------------
-- Records of infra_job
@@ -806,7 +807,7 @@ CREATE TABLE `infra_job_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 232 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
-- ----------------------------
-- Records of infra_job_log
@@ -834,7 +835,7 @@ CREATE TABLE `member_address` (
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_userId`(`user_id` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
+) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '用户收件地址';
-- ----------------------------
-- Records of member_address
@@ -893,7 +894,7 @@ CREATE TABLE `member_experience_record` (
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员经验记录-用户编号',
INDEX `idx_user_biz_type`(`user_id` ASC, `biz_type` ASC) USING BTREE COMMENT '会员经验记录-用户业务类型'
-) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员经验记录';
-- ----------------------------
-- Records of member_experience_record
@@ -951,7 +952,7 @@ CREATE TABLE `member_group` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户分组';
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户分组';
-- ----------------------------
-- Records of member_group
@@ -979,7 +980,7 @@ CREATE TABLE `member_level` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级';
+) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级';
-- ----------------------------
-- Records of member_level
@@ -1009,7 +1010,7 @@ CREATE TABLE `member_level_record` (
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_user_id`(`user_id` ASC) USING BTREE COMMENT '会员等级记录-用户编号'
-) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员等级记录';
-- ----------------------------
-- Records of member_level_record
@@ -1039,7 +1040,7 @@ CREATE TABLE `member_point_record` (
PRIMARY KEY (`id`) USING BTREE,
INDEX `index_userId`(`user_id` ASC) USING BTREE,
INDEX `index_title`(`title` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 60 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 61 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分记录';
-- ----------------------------
-- Records of member_point_record
@@ -1179,7 +1180,7 @@ CREATE TABLE `member_tag` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员标签';
+) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员标签';
-- ----------------------------
-- Records of member_tag
@@ -1221,7 +1222,7 @@ CREATE TABLE `member_user` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 249 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员用户';
+) ENGINE = InnoDB AUTO_INCREMENT = 250 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '会员用户';
-- ----------------------------
-- Records of member_user
@@ -1289,7 +1290,7 @@ CREATE TABLE `system_dict_data` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1447 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1455 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
-- ----------------------------
-- Records of system_dict_data
@@ -1604,6 +1605,14 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1444, 10, '主表(标准模式)', '10', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-14 12:32:49', '1', '2023-11-14 12:32:49', b'0');
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1445, 11, '主表(ERP 模式)', '11', 'infra_codegen_template_type', 0, 'default', '', '', '1', '2023-11-14 12:33:05', '1', '2023-11-14 12:33:05', b'0');
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1446, 12, '主表(内嵌模式)', '12', 'infra_codegen_template_type', 0, '', '', '', '1', '2023-11-14 12:33:31', '1', '2023-11-14 12:33:31', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1447, 1, '负责人', '1', 'crm_permission_level', 0, 'default', '', '', '1', '2023-11-30 09:53:12', '1', '2023-11-30 09:53:12', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1448, 2, '只读', '2', 'crm_permission_level', 0, '', '', '', '1', '2023-11-30 09:53:29', '1', '2023-11-30 09:53:29', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1449, 3, '读写', '3', 'crm_permission_level', 0, '', '', '', '1', '2023-11-30 09:53:36', '1', '2023-11-30 09:53:36', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1450, 0, '未提交', '0', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:56:59', '1', '2023-11-30 18:56:59', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1451, 10, '审批中', '10', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:10', '1', '2023-11-30 18:57:10', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1452, 20, '审核通过', '20', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:24', '1', '2023-11-30 18:57:24', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1453, 30, '审核不通过', '30', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:32', '1', '2023-11-30 18:57:32', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1454, 40, '已取消', '40', 'crm_audit_status', 0, '', '', '', '1', '2023-11-30 18:57:42', '1', '2023-11-30 18:57:42', b'0');
COMMIT;
-- ----------------------------
@@ -1624,7 +1633,7 @@ CREATE TABLE `system_dict_type` (
`deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 605 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
+) ENGINE = InnoDB AUTO_INCREMENT = 607 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
-- ----------------------------
-- Records of system_dict_type
@@ -1704,6 +1713,8 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (600, 'Banner 位置', 'promotion_banner_position', 0, '', '1', '2023-10-08 07:24:25', '1', '2023-11-04 13:04:02', b'0', '1970-01-01 00:00:00');
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (601, '社交类型', 'system_social_type', 0, '', '1', '2023-11-04 13:03:54', '1', '2023-11-04 13:03:54', b'0', '1970-01-01 00:00:00');
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (604, '产品状态', 'crm_product_status', 0, '', '1', '2023-10-30 21:47:59', '1', '2023-10-30 21:48:45', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (605, 'CRM 数据权限的级别', 'crm_permission_level', 0, '', '1', '2023-11-30 09:51:59', '1', '2023-11-30 09:51:59', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (606, 'CRM 审批状态', 'crm_audit_status', 0, '', '1', '2023-11-30 18:56:23', '1', '2023-11-30 18:56:23', b'0', '1970-01-01 00:00:00');
COMMIT;
-- ----------------------------
@@ -1723,7 +1734,7 @@ CREATE TABLE `system_error_code` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 5932 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
+) ENGINE = InnoDB AUTO_INCREMENT = 6039 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '错误码表';
-- ----------------------------
-- Records of system_error_code
@@ -1752,7 +1763,7 @@ CREATE TABLE `system_login_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 2647 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 2667 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
-- ----------------------------
-- Records of system_login_log
@@ -1817,7 +1828,7 @@ CREATE TABLE `system_mail_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 355 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 356 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
-- ----------------------------
-- Records of system_mail_log
@@ -1882,7 +1893,7 @@ CREATE TABLE `system_menu` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 2504 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
+) ENGINE = InnoDB AUTO_INCREMENT = 2526 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
-- ----------------------------
-- Records of system_menu
@@ -2292,7 +2303,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2302, '支付通知查询', 'pay:notify:query', 3, 1, 2301, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-07-20 04:41:32', '', '2023-07-20 04:41:32', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2303, '拼团活动', '', 2, 3, 2030, 'combination', 'fa:group', '', '', 0, b'1', b'1', b'1', '1', '2023-08-12 17:19:54', '1', '2023-08-12 17:20:05', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2304, '拼团商品', '', 2, 1, 2303, 'acitivity', 'ep:apple', 'mall/promotion/combination/activity/index', 'PromotionCombinationActivity', 0, b'1', b'1', b'1', '1', '2023-08-12 17:22:03', '1', '2023-08-12 17:22:29', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2305, '拼团活动查询', 'promotion:combination-activity:query', 3, 1, 2304, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-08-12 17:54:32', '1', '2023-08-12 17:54:32', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2305, '拼团活动查询', 'promotion:combination-activity:query', 3, 1, 2304, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-08-12 17:54:32', '1', '2023-11-24 11:57:40', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2306, '拼团活动创建', 'promotion:combination-activity:create', 3, 2, 2304, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-08-12 17:54:49', '1', '2023-08-12 17:54:49', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2307, '拼团活动更新', 'promotion:combination-activity:update', 3, 3, 2304, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-08-12 17:55:04', '1', '2023-08-12 17:55:04', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2308, '拼团活动删除', 'promotion:combination-activity:delete', 3, 4, 2304, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-08-12 17:55:23', '1', '2023-08-12 17:55:23', b'0');
@@ -2457,6 +2468,16 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2494, '学生删除', 'infra:demo03-student:delete', 3, 4, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2495, '学生导出', 'infra:demo03-student:export', 3, 5, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2497, '主子表(ERP)', '', 2, 11, 1070, 'demo03-erp', 'ep:calendar', 'infra/demo/demo03/erp/index', 'Demo03StudentERP', 0, b'1', b'1', b'1', '', '2023-11-16 15:50:59', '1', '2023-11-17 13:19:56', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2516, '客户公海配置', '', 2, 0, 2524, 'customer-pool-config', 'ep:data-analysis', 'crm/config/customerPoolConfig/index', 'CrmCustomerPoolConfig', 0, b'1', b'1', b'1', '', '2023-11-18 13:33:31', '1', '2023-11-26 20:08:14', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2517, '客户公海配置保存', 'crm:customer-pool-config:update', 3, 1, 2516, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:31', '', '2023-11-18 13:33:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2518, '客户限制配置', '', 2, 0, 2524, 'customer-limit-config', 'ep:avatar', 'crm/config/customerLimitConfig/index', 'CrmCustomerLimitConfig', 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '1', '2023-11-26 20:07:04', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2519, '客户限制配置查询', 'crm:customer-limit-config:query', 3, 1, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2520, '客户限制配置创建', 'crm:customer-limit-config:create', 3, 2, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2521, '客户限制配置更新', 'crm:customer-limit-config:update', 3, 3, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2522, '客户限制配置删除', 'crm:customer-limit-config:delete', 3, 4, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2523, '客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2524, '系统配置', '', 1, 99, 2397, 'config', 'ep:connection', '', '', 0, b'1', b'1', b'1', '1', '2023-11-18 21:58:00', '1', '2023-11-18 21:58:00', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2525, 'WebSocket 测试', '', 2, 7, 2, 'websocket', 'ep:connection', 'infra/webSocket/index', 'InfraWebSocket', 0, b'1', b'1', b'1', '1', '2023-11-23 19:41:55', '1', '2023-11-24 19:22:30', b'0');
COMMIT;
-- ----------------------------
@@ -2483,7 +2504,7 @@ CREATE TABLE `system_notice` (
-- ----------------------------
BEGIN;
INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '芋道的公众', '新版本内容133
', 1, 0, 'admin', '2021-01-05 17:03:48', '1', '2022-05-04 21:00:20', b'0', 1);
-INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', ' 1111
', 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2023-11-11 12:51:11', b'0', 1);
+INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '维护通知:2018-07-01 系统凌晨维护', ' 1111
', 2, 1, 'admin', '2021-01-05 17:03:48', '1', '2023-11-23 23:37:41', b'0', 1);
INSERT INTO `system_notice` (`id`, `title`, `content`, `type`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, '我是测试标题', '哈哈哈哈123
', 1, 0, '110', '2022-02-22 01:01:25', '110', '2022-02-22 01:01:46', b'0', 121);
COMMIT;
@@ -2547,7 +2568,7 @@ CREATE TABLE `system_notify_template` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
+) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '站内信模板表';
-- ----------------------------
-- Records of system_notify_template
@@ -2577,7 +2598,7 @@ CREATE TABLE `system_oauth2_access_token` (
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3467 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 3587 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
-- ----------------------------
-- Records of system_oauth2_access_token
@@ -2699,7 +2720,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1115 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 1132 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
-- ----------------------------
-- Records of system_oauth2_refresh_token
@@ -2739,7 +2760,7 @@ CREATE TABLE `system_operate_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 9090 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 9175 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
-- ----------------------------
-- Records of system_operate_log
@@ -2765,7 +2786,7 @@ CREATE TABLE `system_post` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
-- ----------------------------
-- Records of system_post
@@ -3739,7 +3760,7 @@ CREATE TABLE `system_sms_code` (
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 535 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
+) ENGINE = InnoDB AUTO_INCREMENT = 536 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
-- ----------------------------
-- Records of system_sms_code
@@ -3766,8 +3787,6 @@ CREATE TABLE `system_sms_log` (
`user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型',
`send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态',
`send_time` datetime NULL DEFAULT NULL COMMENT '发送时间',
- `send_code` int NULL DEFAULT NULL COMMENT '发送结果的编码',
- `send_msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送结果的提示',
`api_send_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送结果的编码',
`api_send_msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送失败的提示',
`api_request_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信 API 发送返回的唯一请求 ID',
@@ -3782,7 +3801,7 @@ CREATE TABLE `system_sms_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 502 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 503 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
-- ----------------------------
-- Records of system_sms_log
@@ -3812,7 +3831,7 @@ CREATE TABLE `system_sms_template` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
+) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信模板';
-- ----------------------------
-- Records of system_sms_template
@@ -3882,7 +3901,7 @@ CREATE TABLE `system_social_user` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
+) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交用户表';
-- ----------------------------
-- Records of system_social_user
@@ -3907,7 +3926,7 @@ CREATE TABLE `system_social_user_bind` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 80 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
+) ENGINE = InnoDB AUTO_INCREMENT = 81 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
-- ----------------------------
-- Records of system_social_user_bind
@@ -4077,13 +4096,13 @@ CREATE TABLE `system_users` (
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_username`(`username` ASC, `update_time` ASC, `tenant_id` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 127 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表';
+) ENGINE = InnoDB AUTO_INCREMENT = 126 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表';
-- ----------------------------
-- Records of system_users
-- ----------------------------
BEGIN;
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://127.0.0.1:48080/admin-api/infra/file/4/get/37e56010ecbee472cdd821ac4b608e151e62a74d9633f15d085aee026eedeb60.png', 0, '0:0:0:0:0:0:0:1', '2023-11-18 17:19:30', 'admin', '2021-01-05 17:03:47', NULL, '2023-11-18 17:19:30', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://127.0.0.1:48080/admin-api/infra/file/4/get/37e56010ecbee472cdd821ac4b608e151e62a74d9633f15d085aee026eedeb60.png', 0, '127.0.0.1', '2023-11-30 09:16:00', 'admin', '2021-01-05 17:03:47', NULL, '2023-11-30 09:16:00', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-07-08 01:26:27', '', '2021-01-13 23:50:35', NULL, '2022-07-08 01:26:27', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2023-09-24 18:21:19', '', '2021-01-21 02:13:53', NULL, '2023-09-24 18:21:19', b'0', 1);
@@ -4098,7 +4117,7 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`,
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$10$/WCwGHu1eq0wOVDd/u8HweJ0gJCHyLS6T7ndCqI8UXZAQom1etk2e', '1', '11', 101, '[]', '', '', 1, '', 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2022-06-22 13:34:58', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (116, '15601691302', '$2a$10$L5C4S0U6adBWMvFv1Wwl4.DI/NwYS3WIfLj5Q.Naqr5II8CmqsDZ6', '小豆', NULL, NULL, NULL, '', '', 0, '', 0, '', NULL, '1', '2022-05-17 10:07:10', '1', '2022-05-17 10:07:10', b'0', 124);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 'admin123', '$2a$10$WI8Gg/lpZQIrOEZMHqka7OdFaD4Nx.B/qY8ZGTTUKrOJwaHFqibaC', '测试号', '1111', 100, '[2]', '', '15601691234', 1, '', 0, '', NULL, '1', '2022-07-09 17:40:26', '1', '2022-07-09 17:40:26', b'0', 1);
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (118, 'goudan', '$2a$10$Lrb71muL.s5/AFjQ2IHkzOFlAFwUToH.zQL7bnghvTDt/QptjGgF6', '狗蛋', NULL, 103, '[1]', '', '', 2, '', 0, '', NULL, '1', '2022-07-09 17:44:43', '1', '2022-12-31 17:29:13', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (118, 'goudan', '$2a$10$Lrb71muL.s5/AFjQ2IHkzOFlAFwUToH.zQL7bnghvTDt/QptjGgF6', '狗蛋', NULL, 103, '[1]', '', '', 2, '', 0, '', NULL, '1', '2022-07-09 17:44:43', '1', '2023-11-18 19:02:13', b'0', 1);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml
index 2fbf6eec2..563a6f6a0 100644
--- a/yudao-dependencies/pom.xml
+++ b/yudao-dependencies/pom.xml
@@ -14,7 +14,7 @@
https://github.com/YunaiV/ruoyi-vue-pro
- 1.8.3-snapshot
+ 1.9.0-snapshot
1.5.0
3.1.4
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/Telephone.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/Telephone.java
index 55c90536c..fa534e1c6 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/Telephone.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/Telephone.java
@@ -2,25 +2,20 @@ package cn.iocoder.yudao.framework.common.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
-
-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;
+import java.lang.annotation.*;
@Target({
- ElementType.METHOD,
- ElementType.FIELD,
- ElementType.ANNOTATION_TYPE,
- ElementType.CONSTRUCTOR,
- ElementType.PARAMETER,
- ElementType.TYPE_USE
+ ElementType.METHOD,
+ ElementType.FIELD,
+ ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR,
+ ElementType.PARAMETER,
+ ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
- validatedBy = TelephoneValidator.class
+ validatedBy = TelephoneValidator.class
)
public @interface Telephone {
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/TelephoneValidator.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/TelephoneValidator.java
index 6372dd59c..1406e0706 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/TelephoneValidator.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/validation/TelephoneValidator.java
@@ -2,18 +2,22 @@ package cn.iocoder.yudao.framework.common.validation;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.PhoneUtil;
+
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class TelephoneValidator implements ConstraintValidator {
+ @Override
+ public void initialize(Telephone annotation) {
+ }
+
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果手机号为空,默认不校验,即校验通过
if (CharSequenceUtil.isEmpty(value)) {
return true;
}
-
// 校验手机
return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value);
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
index 61a6d2e0c..ee7640b6e 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
@@ -103,7 +103,19 @@ public class CodegenEngine {
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/index.vue"),
vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"),
- vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
+ vueFilePath("api/${table.moduleName}/${table.businessName}/index.js"))
+ .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/form.vue"),
+ vueFilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
+ .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_normal.vue"), // 特殊:主子表专属逻辑
+ vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+ .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_inner.vue"), // 特殊:主子表专属逻辑
+ vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+ .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_erp.vue"), // 特殊:主子表专属逻辑
+ vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
+ .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_inner.vue"), // 特殊:主子表专属逻辑
+ vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
+ .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_erp.vue"), // 特殊:主子表专属逻辑
+ vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
// Vue3 标准模版
.put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
@@ -285,6 +297,10 @@ public class CodegenEngine {
if (StrUtil.count(content, "dateFormatter") == 1) {
content = StrUtils.removeLineContains(content, "dateFormatter");
}
+ // Vue2 界面:修正 $refs
+ if (StrUtil.count(content, "this.refs") >= 1) {
+ content = content.replace("this.refs", "this.$refs");
+ }
// Vue 界面:去除多的 dict 相关,只有一个的情况下,说明没使用到
if (StrUtil.count(content, "getIntDictOptions") == 1) {
content = content.replace("getIntDictOptions, ", "");
@@ -452,7 +468,7 @@ public class CodegenEngine {
}
private static String vueFilePath(String path) {
- return "yudao-ui-${sceneEnum.basePackage}/" + // 顶级目录
+ return "yudao-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录
"src/" + path;
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
index 5e9da3238..bfe2dc007 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
@@ -35,21 +35,113 @@ export function get${simpleClassName}(id) {
})
}
+#if ( $table.templateType != 2 )
// 获得${table.classComment}分页
-export function get${simpleClassName}Page(query) {
+export function get${simpleClassName}Page(params) {
return request({
url: '${baseURL}/page',
method: 'get',
- params: query
+ params
})
}
-
+#else
+// 获得${table.classComment}列表
+export function get${simpleClassName}List(params) {
+ return request({
+ url: '${baseURL}/list',
+ method: 'get',
+ params
+ })
+}
+#end
// 导出${table.classComment} Excel
-export function export${simpleClassName}Excel(query) {
+export function export${simpleClassName}Excel(params) {
return request({
url: '${baseURL}/export-excel',
method: 'get',
- params: query,
+ params,
responseType: 'blob'
})
}
+## 特殊:主子表专属逻辑 TODO @puhui999:下面方法的【空格】不太对
+#foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+ #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+ #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+ #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+ ## 情况一:MASTER_ERP 时,需要分查询页子表
+ #if ( $table.templateType == 11 )
+
+ // 获得${subTable.classComment}分页
+ export function get${subSimpleClassName}Page(params) {
+ return request({
+ url: '${baseURL}/${subSimpleClassName_strikeCase}/page',
+ method: 'get',
+ params
+ })
+ }
+ ## 情况二:非 MASTER_ERP 时,需要列表查询子表
+ #else
+ #if ( $subTable.subJoinMany )
+
+ // 获得${subTable.classComment}列表
+ export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
+ method: 'get'
+ })
+ }
+ #else
+
+ // 获得${subTable.classComment}
+ export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
+ method: 'get'
+ })
+ }
+ #end
+ #end
+ ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+ #if ( $table.templateType == 11 )
+ // 新增${subTable.classComment}
+ export function create${subSimpleClassName}(data) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 修改${subTable.classComment}
+ export function update${subSimpleClassName}(data) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 删除${subTable.classComment}
+ export function delete${subSimpleClassName}(id) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id,
+ method: 'delete'
+ })
+ }
+
+ // 获得${subTable.classComment}
+ export function get${subSimpleClassName}(id) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id,
+ method: 'get'
+ })
+ }
+ #end
+#end
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm
new file mode 100644
index 000000000..99aa91af1
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm
@@ -0,0 +1,205 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+
+
+
+
+
+ #foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+ #end
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm
new file mode 100644
index 000000000..ca266be9d
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm
@@ -0,0 +1,2 @@
+## 主表的 normal 和 inner 使用相同的 form 表单
+#parse("codegen/vue/views/components/form_sub_normal.vue.vm")
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm
new file mode 100644
index 000000000..48a404a3b
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm
@@ -0,0 +1,347 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+
+
+ #if ( $subTable.subJoinMany )## 情况一:一对多,table + form
+
+
+
+ #foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+
+
+
+
+ #end
+ #end
+ #end
+
+
+ —
+
+
+
+
+
+ + 添加${subTable.classComment}
+
+ #else## 情况二:一对一,form
+
+ #foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+ #end
+
+ #end
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
new file mode 100644
index 000000000..589736b6e
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
@@ -0,0 +1,165 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+
+
+#if ($table.templateType == 11)
+
+
+
+ 新增
+
+
+#end
+ ## 列表
+
+ #foreach($column in $subColumns)
+ #if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.javaType == "LocalDateTime")## 时间类型
+
+
+ {{ parseTime(scope.row.${javaField}) }}
+
+
+ #elseif($column.dictType && "" != $column.dictType)## 数据字典
+
+
+
+
+
+ #else
+
+ #end
+ #end
+ #end
+
+
+ 修改
+ 删除
+
+
+
+#if ($table.templateType == 11)
+
+
+
+ <${subSimpleClassName}Form ref="formRef" @success="getList" />
+#end
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm
new file mode 100644
index 000000000..90b8e4153
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm
@@ -0,0 +1,4 @@
+## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
+## 1)inner 使用 list 不分页,erp 使用 page 分页
+## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
+#parse("codegen/vue/views/components/list_sub_erp.vue.vm")
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm
new file mode 100644
index 000000000..634d05d3b
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm
@@ -0,0 +1,320 @@
+
+
+
+
+
+ #foreach($column in $columns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
+
+
+
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+ #end
+
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 10 || $table.templateType == 12 )
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
+
+ #end
+
+ #end
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm
index 7a6add604..2328007a0 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm
@@ -1,6 +1,5 @@
-
#foreach($column in $columns)
@@ -47,18 +46,68 @@
- 新增
导出
+ ## 特殊:树表专属逻辑
+ #if ( $table.templateType == 2 )
+
+
+ 展开/折叠
+
+
+ #end
-
-
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+ ## 特殊:树表专属逻辑
+ #elseif ( $table.templateType == 2 )
+
+ #else
+
+ #end
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+
+
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
+
+ #end
+
+
+
+ #end
#foreach($column in $columns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
@@ -84,102 +133,42 @@
#end
- 修改
删除
+## 特殊:树表专属逻辑(树不需要分页)
+#if ( $table.templateType != 2 )
-
+#end
-
-
-#foreach($column in $columns)
-#if ($column.createOperation || $column.updateOperation)
- #set ($dictType = $column.dictType)
- #set ($javaField = $column.javaField)
- #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
- #set ($comment = $column.columnComment)
-#if ($column.htmlType == "input")
- #if (!$column.primaryKey)## 忽略主键,不用在表单里
-
-
-
+ <${simpleClassName}Form ref="formRef" @success="getList" />
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List v-if="currentRow.id" :${subJoinColumn_strikeCase}="currentRow.id" />
+
+ #end
+
#end
-#elseif($column.htmlType == "imageUpload")## 图片上传
- #set ($hasImageUploadColumn = true)
-
-
-
-#elseif($column.htmlType == "fileUpload")## 文件上传
- #set ($hasFileUploadColumn = true)
-
-
-
-#elseif($column.htmlType == "editor")## 文本编辑器
- #set ($hasEditorColumn = true)
-
-
-
-#elseif($column.htmlType == "select")## 下拉框
-
-
- #if ("" != $dictType)## 有数据字典
-
- #else##没数据字典
-
- #end
-
-
-#elseif($column.htmlType == "checkbox")## 多选框
-
-
- #if ("" != $dictType)## 有数据字典
- {{dict.label}}
- #else##没数据字典
- 请选择字典生成
- #end
-
-
-#elseif($column.htmlType == "radio")## 单选框
-
-
- #if ("" != $dictType)## 有数据字典
- {{dict.label}}
- #else##没数据字典
- 请选择字典生成
- #end
-
-
-#elseif($column.htmlType == "datetime")## 时间框
-
-
-
-#elseif($column.htmlType == "textarea")## 文本框
-
-
-
-#end
-#end
-#end
-
-
-
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/assert.json b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/assert.json
new file mode 100644
index 000000000..f95ae9af1
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/assert.json
@@ -0,0 +1,73 @@
+[ {
+ "contentPath" : "java/InfraStudentPageReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentRespVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
+}, {
+ "contentPath" : "java/InfraStudentSaveReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentController",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
+}, {
+ "contentPath" : "java/InfraStudentDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
+}, {
+ "contentPath" : "java/InfraStudentContactDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
+}, {
+ "contentPath" : "java/InfraStudentTeacherDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
+}, {
+ "contentPath" : "java/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
+}, {
+ "contentPath" : "java/InfraStudentContactMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
+}, {
+ "contentPath" : "java/InfraStudentTeacherMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
+}, {
+ "contentPath" : "xml/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
+}, {
+ "contentPath" : "java/InfraStudentServiceImpl",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
+}, {
+ "contentPath" : "java/InfraStudentService",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
+}, {
+ "contentPath" : "java/InfraStudentServiceImplTest",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
+}, {
+ "contentPath" : "java/ErrorCodeConstants_手动操作",
+ "filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
+}, {
+ "contentPath" : "sql/sql",
+ "filePath" : "sql/sql.sql"
+}, {
+ "contentPath" : "sql/h2",
+ "filePath" : "sql/h2.sql"
+}, {
+ "contentPath" : "vue/index",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
+}, {
+ "contentPath" : "js/student",
+ "filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
+}, {
+ "contentPath" : "vue/StudentForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
+}, {
+ "contentPath" : "vue/StudentContactForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactForm.vue"
+}, {
+ "contentPath" : "vue/StudentTeacherForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherForm.vue"
+}, {
+ "contentPath" : "vue/StudentContactList",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactList.vue"
+}, {
+ "contentPath" : "vue/StudentTeacherList",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherList.vue"
+} ]
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/ErrorCodeConstants_手动操作 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/ErrorCodeConstants_手动操作
new file mode 100644
index 000000000..d3201dec3
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/ErrorCodeConstants_手动操作
@@ -0,0 +1,6 @@
+// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
+// ========== 学生 TODO 补充编号 ==========
+ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
+ErrorCode STUDENT_CONTACT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生联系人不存在");
+ErrorCode STUDENT_TEACHER_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任不存在");
+ErrorCode STUDENT_TEACHER_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任已存在");
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentContactDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentContactDO
new file mode 100644
index 000000000..17c668eaa
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentContactDO
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生联系人 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student_contact")
+@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentContactDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 学生编号
+ */
+ private Long studentId;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentContactMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentContactMapper
new file mode 100644
index 000000000..ca662d19c
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentContactMapper
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生联系人 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentContactMapper extends BaseMapperX {
+
+ default PageResult selectPage(PageParam reqVO, Long studentId) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eq(InfraStudentContactDO::getStudentId, studentId)
+ .orderByDesc(InfraStudentContactDO::getId));
+ }
+
+ default int deleteByStudentId(Long studentId) {
+ return delete(InfraStudentContactDO::getStudentId, studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentController b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentController
new file mode 100644
index 000000000..d6f20183d
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentController
@@ -0,0 +1,183 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
+
+@Tag(name = "管理后台 - 学生")
+@RestController
+@RequestMapping("/infra/student")
+@Validated
+public class InfraStudentController {
+
+ @Resource
+ private InfraStudentService studentService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:create')")
+ public CommonResult createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
+ return success(studentService.createStudent(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:update')")
+ public CommonResult updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
+ studentService.updateStudent(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除学生")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:student:delete')")
+ public CommonResult deleteStudent(@RequestParam("id") Long id) {
+ studentService.deleteStudent(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得学生")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudent(@RequestParam("id") Long id) {
+ InfraStudentDO student = studentService.getStudent(id);
+ return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得学生分页")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
+ PageResult pageResult = studentService.getStudentPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出学生 Excel")
+ @PreAuthorize("@ss.hasPermission('infra:student:export')")
+ @OperateLog(type = EXPORT)
+ public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = studentService.getStudentPage(pageReqVO).getList();
+ // 导出 Excel
+ ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
+ BeanUtils.toBean(list, InfraStudentRespVO.class));
+ }
+
+ // ==================== 子表(学生联系人) ====================
+
+ @GetMapping("/student-contact/page")
+ @Operation(summary = "获得学生联系人分页")
+ @Parameter(name = "studentId", description = "学生编号")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentContactPage(PageParam pageReqVO,
+ @RequestParam("studentId") Long studentId) {
+ return success(studentService.getStudentContactPage(pageReqVO, studentId));
+ }
+
+ @PostMapping("/student-contact/create")
+ @Operation(summary = "创建学生联系人")
+ @PreAuthorize("@ss.hasPermission('infra:student:create')")
+ public CommonResult createStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
+ return success(studentService.createStudentContact(studentContact));
+ }
+
+ @PutMapping("/student-contact/update")
+ @Operation(summary = "更新学生联系人")
+ @PreAuthorize("@ss.hasPermission('infra:student:update')")
+ public CommonResult updateStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
+ studentService.updateStudentContact(studentContact);
+ return success(true);
+ }
+
+ @DeleteMapping("/student-contact/delete")
+ @Parameter(name = "id", description = "编号", required = true)
+ @Operation(summary = "删除学生联系人")
+ @PreAuthorize("@ss.hasPermission('infra:student:delete')")
+ public CommonResult deleteStudentContact(@RequestParam("id") Long id) {
+ studentService.deleteStudentContact(id);
+ return success(true);
+ }
+
+ @GetMapping("/student-contact/get")
+ @Operation(summary = "获得学生联系人")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudentContact(@RequestParam("id") Long id) {
+ return success(studentService.getStudentContact(id));
+ }
+
+ // ==================== 子表(学生班主任) ====================
+
+ @GetMapping("/student-teacher/page")
+ @Operation(summary = "获得学生班主任分页")
+ @Parameter(name = "studentId", description = "学生编号")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentTeacherPage(PageParam pageReqVO,
+ @RequestParam("studentId") Long studentId) {
+ return success(studentService.getStudentTeacherPage(pageReqVO, studentId));
+ }
+
+ @PostMapping("/student-teacher/create")
+ @Operation(summary = "创建学生班主任")
+ @PreAuthorize("@ss.hasPermission('infra:student:create')")
+ public CommonResult createStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
+ return success(studentService.createStudentTeacher(studentTeacher));
+ }
+
+ @PutMapping("/student-teacher/update")
+ @Operation(summary = "更新学生班主任")
+ @PreAuthorize("@ss.hasPermission('infra:student:update')")
+ public CommonResult updateStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
+ studentService.updateStudentTeacher(studentTeacher);
+ return success(true);
+ }
+
+ @DeleteMapping("/student-teacher/delete")
+ @Parameter(name = "id", description = "编号", required = true)
+ @Operation(summary = "删除学生班主任")
+ @PreAuthorize("@ss.hasPermission('infra:student:delete')")
+ public CommonResult deleteStudentTeacher(@RequestParam("id") Long id) {
+ studentService.deleteStudentTeacher(id);
+ return success(true);
+ }
+
+ @GetMapping("/student-teacher/get")
+ @Operation(summary = "获得学生班主任")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudentTeacher(@RequestParam("id") Long id) {
+ return success(studentService.getStudentTeacher(id));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentDO
new file mode 100644
index 000000000..b0d4bd216
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentDO
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student")
+@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentMapper
new file mode 100644
index 000000000..34e70a082
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentMapper
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+
+/**
+ * 学生 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentMapper extends BaseMapperX {
+
+ default PageResult selectPage(InfraStudentPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(InfraStudentDO::getName, reqVO.getName())
+ .eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
+ .eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
+ .eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
+ .betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(InfraStudentDO::getId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentPageReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentPageReqVO
new file mode 100644
index 000000000..41a373012
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentPageReqVO
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+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 = "管理后台 - 学生分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraStudentPageReqVO extends PageParam {
+
+ @Schema(description = "名字", example = "芋头")
+ private String name;
+
+ @Schema(description = "出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", example = "1")
+ private Integer sex;
+
+ @Schema(description = "是否有效", example = "true")
+ private Boolean enabled;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentRespVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentRespVO
new file mode 100644
index 000000000..c41a5501f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentRespVO
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 学生 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class InfraStudentRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @ExcelProperty("编号")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @ExcelProperty("名字")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @ExcelProperty("简介")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty(value = "性别", converter = DictConvert.class)
+ @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @ExcelProperty(value = "是否有效", converter = DictConvert.class)
+ @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @ExcelProperty("头像")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @ExcelProperty("附件")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @ExcelProperty("备注")
+ private String memo;
+
+ @Schema(description = "创建时间")
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentSaveReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentSaveReqVO
new file mode 100644
index 000000000..eaadf7432
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentSaveReqVO
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+
+@Schema(description = "管理后台 - 学生新增/修改 Request VO")
+@Data
+public class InfraStudentSaveReqVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @NotEmpty(message = "名字不能为空")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @NotEmpty(message = "简介不能为空")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "出生日期不能为空")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "性别不能为空")
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否有效不能为空")
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @NotEmpty(message = "头像不能为空")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @NotEmpty(message = "附件不能为空")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @NotEmpty(message = "备注不能为空")
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentService b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentService
new file mode 100644
index 000000000..7df090d7f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentService
@@ -0,0 +1,139 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 学生 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfraStudentService {
+
+ /**
+ * 创建学生
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
+
+ /**
+ * 更新学生
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
+
+ /**
+ * 删除学生
+ *
+ * @param id 编号
+ */
+ void deleteStudent(Long id);
+
+ /**
+ * 获得学生
+ *
+ * @param id 编号
+ * @return 学生
+ */
+ InfraStudentDO getStudent(Long id);
+
+ /**
+ * 获得学生分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 学生分页
+ */
+ PageResult getStudentPage(InfraStudentPageReqVO pageReqVO);
+
+ // ==================== 子表(学生联系人) ====================
+
+ /**
+ * 获得学生联系人分页
+ *
+ * @param pageReqVO 分页查询
+ * @param studentId 学生编号
+ * @return 学生联系人分页
+ */
+ PageResult getStudentContactPage(PageParam pageReqVO, Long studentId);
+
+ /**
+ * 创建学生联系人
+ *
+ * @param studentContact 创建信息
+ * @return 编号
+ */
+ Long createStudentContact(@Valid InfraStudentContactDO studentContact);
+
+ /**
+ * 更新学生联系人
+ *
+ * @param studentContact 更新信息
+ */
+ void updateStudentContact(@Valid InfraStudentContactDO studentContact);
+
+ /**
+ * 删除学生联系人
+ *
+ * @param id 编号
+ */
+ void deleteStudentContact(Long id);
+
+ /**
+ * 获得学生联系人
+ *
+ * @param id 编号
+ * @return 学生联系人
+ */
+ InfraStudentContactDO getStudentContact(Long id);
+
+ // ==================== 子表(学生班主任) ====================
+
+ /**
+ * 获得学生班主任分页
+ *
+ * @param pageReqVO 分页查询
+ * @param studentId 学生编号
+ * @return 学生班主任分页
+ */
+ PageResult getStudentTeacherPage(PageParam pageReqVO, Long studentId);
+
+ /**
+ * 创建学生班主任
+ *
+ * @param studentTeacher 创建信息
+ * @return 编号
+ */
+ Long createStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
+
+ /**
+ * 更新学生班主任
+ *
+ * @param studentTeacher 更新信息
+ */
+ void updateStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
+
+ /**
+ * 删除学生班主任
+ *
+ * @param id 编号
+ */
+ void deleteStudentTeacher(Long id);
+
+ /**
+ * 获得学生班主任
+ *
+ * @param id 编号
+ * @return 学生班主任
+ */
+ InfraStudentTeacherDO getStudentTeacher(Long id);
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentServiceImpl b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentServiceImpl
new file mode 100644
index 000000000..793b2dd22
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentServiceImpl
@@ -0,0 +1,180 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 学生 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfraStudentServiceImpl implements InfraStudentService {
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+ @Resource
+ private InfraStudentContactMapper studentContactMapper;
+ @Resource
+ private InfraStudentTeacherMapper studentTeacherMapper;
+
+ @Override
+ public Long createStudent(InfraStudentSaveReqVO createReqVO) {
+ // 插入
+ InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
+ studentMapper.insert(student);
+ // 返回
+ return student.getId();
+ }
+
+ @Override
+ public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
+ // 校验存在
+ validateStudentExists(updateReqVO.getId());
+ // 更新
+ InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
+ studentMapper.updateById(updateObj);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteStudent(Long id) {
+ // 校验存在
+ validateStudentExists(id);
+ // 删除
+ studentMapper.deleteById(id);
+
+ // 删除子表
+ deleteStudentContactByStudentId(id);
+ deleteStudentTeacherByStudentId(id);
+ }
+
+ private void validateStudentExists(Long id) {
+ if (studentMapper.selectById(id) == null) {
+ throw exception(STUDENT_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public InfraStudentDO getStudent(Long id) {
+ return studentMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getStudentPage(InfraStudentPageReqVO pageReqVO) {
+ return studentMapper.selectPage(pageReqVO);
+ }
+
+ // ==================== 子表(学生联系人) ====================
+
+ @Override
+ public PageResult getStudentContactPage(PageParam pageReqVO, Long studentId) {
+ return studentContactMapper.selectPage(pageReqVO, studentId);
+ }
+
+ @Override
+ public Long createStudentContact(InfraStudentContactDO studentContact) {
+ studentContactMapper.insert(studentContact);
+ return studentContact.getId();
+ }
+
+ @Override
+ public void updateStudentContact(InfraStudentContactDO studentContact) {
+ // 校验存在
+ validateStudentContactExists(studentContact.getId());
+ // 更新
+ studentContactMapper.updateById(studentContact);
+ }
+
+ @Override
+ public void deleteStudentContact(Long id) {
+ // 校验存在
+ validateStudentContactExists(id);
+ // 删除
+ studentContactMapper.deleteById(id);
+ }
+
+ @Override
+ public InfraStudentContactDO getStudentContact(Long id) {
+ return studentContactMapper.selectById(id);
+ }
+
+ private void validateStudentContactExists(Long id) {
+ if (studentContactMapper.selectById(id) == null) {
+ throw exception(STUDENT_CONTACT_NOT_EXISTS);
+ }
+ }
+
+ private void deleteStudentContactByStudentId(Long studentId) {
+ studentContactMapper.deleteByStudentId(studentId);
+ }
+
+ // ==================== 子表(学生班主任) ====================
+
+ @Override
+ public PageResult getStudentTeacherPage(PageParam pageReqVO, Long studentId) {
+ return studentTeacherMapper.selectPage(pageReqVO, studentId);
+ }
+
+ @Override
+ public Long createStudentTeacher(InfraStudentTeacherDO studentTeacher) {
+ // 校验是否已经存在
+ if (studentTeacherMapper.selectByStudentId(studentTeacher.getStudentId()) != null) {
+ throw exception(STUDENT_TEACHER_EXISTS);
+ }
+ // 插入
+ studentTeacherMapper.insert(studentTeacher);
+ return studentTeacher.getId();
+ }
+
+ @Override
+ public void updateStudentTeacher(InfraStudentTeacherDO studentTeacher) {
+ // 校验存在
+ validateStudentTeacherExists(studentTeacher.getId());
+ // 更新
+ studentTeacherMapper.updateById(studentTeacher);
+ }
+
+ @Override
+ public void deleteStudentTeacher(Long id) {
+ // 校验存在
+ validateStudentTeacherExists(id);
+ // 删除
+ studentTeacherMapper.deleteById(id);
+ }
+
+ @Override
+ public InfraStudentTeacherDO getStudentTeacher(Long id) {
+ return studentTeacherMapper.selectById(id);
+ }
+
+ private void validateStudentTeacherExists(Long id) {
+ if (studentTeacherMapper.selectById(id) == null) {
+ throw exception(STUDENT_TEACHER_NOT_EXISTS);
+ }
+ }
+
+ private void deleteStudentTeacherByStudentId(Long studentId) {
+ studentTeacherMapper.deleteByStudentId(studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentServiceImplTest b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentServiceImplTest
new file mode 100644
index 000000000..b5f4bf0ff
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentServiceImplTest
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link InfraStudentServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(InfraStudentServiceImpl.class)
+public class InfraStudentServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private InfraStudentServiceImpl studentService;
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+
+ @Test
+ public void testCreateStudent_success() {
+ // 准备参数
+ InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
+
+ // 调用
+ Long studentId = studentService.createStudent(createReqVO);
+ // 断言
+ assertNotNull(studentId);
+ // 校验记录的属性是否正确
+ InfraStudentDO student = studentMapper.selectById(studentId);
+ assertPojoEquals(createReqVO, student, "id");
+ }
+
+ @Test
+ public void testUpdateStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
+ o.setId(dbStudent.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ studentService.updateStudent(updateReqVO);
+ // 校验是否更新正确
+ InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
+ assertPojoEquals(updateReqVO, student);
+ }
+
+ @Test
+ public void testUpdateStudent_notExists() {
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbStudent.getId();
+
+ // 调用
+ studentService.deleteStudent(id);
+ // 校验数据不存在了
+ assertNull(studentMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteStudent_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetStudentPage() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
+ o.setName(null);
+ o.setBirthday(null);
+ o.setSex(null);
+ o.setEnabled(null);
+ o.setCreateTime(null);
+ });
+ studentMapper.insert(dbStudent);
+ // 测试 name 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
+ // 测试 birthday 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
+ // 测试 sex 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
+ // 测试 enabled 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
+ // 测试 createTime 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
+ // 准备参数
+ InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
+ reqVO.setName(null);
+ reqVO.setBirthday(null);
+ reqVO.setSex(null);
+ reqVO.setEnabled(null);
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+ // 调用
+ PageResult pageResult = studentService.getStudentPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbStudent, pageResult.getList().get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentTeacherDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentTeacherDO
new file mode 100644
index 000000000..c19cf9fab
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentTeacherDO
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生班主任 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student_teacher")
+@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentTeacherDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 学生编号
+ */
+ private Long studentId;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentTeacherMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentTeacherMapper
new file mode 100644
index 000000000..994212dab
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/java/InfraStudentTeacherMapper
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生班主任 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentTeacherMapper extends BaseMapperX {
+
+ default PageResult selectPage(PageParam reqVO, Long studentId) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eq(InfraStudentTeacherDO::getStudentId, studentId)
+ .orderByDesc(InfraStudentTeacherDO::getId));
+ }
+
+ default int deleteByStudentId(Long studentId) {
+ return delete(InfraStudentTeacherDO::getStudentId, studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/js/student b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/js/student
new file mode 100644
index 000000000..211d95e42
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/js/student
@@ -0,0 +1,141 @@
+import request from '@/utils/request'
+
+// 创建学生
+export function createStudent(data) {
+ return request({
+ url: '/infra/student/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 更新学生
+export function updateStudent(data) {
+ return request({
+ url: '/infra/student/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除学生
+export function deleteStudent(id) {
+ return request({
+ url: '/infra/student/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 获得学生
+export function getStudent(id) {
+ return request({
+ url: '/infra/student/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 获得学生分页
+export function getStudentPage(params) {
+ return request({
+ url: '/infra/student/page',
+ method: 'get',
+ params
+ })
+}
+// 导出学生 Excel
+export function exportStudentExcel(params) {
+ return request({
+ url: '/infra/student/export-excel',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
+
+// ==================== 子表(学生联系人) ====================
+
+ // 获得学生联系人分页
+ export function getStudentContactPage(params) {
+ return request({
+ url: '/infra/student/student-contact/page',
+ method: 'get',
+ params
+ })
+ }
+ // 新增学生联系人
+ export function createStudentContact(data) {
+ return request({
+ url: `/infra/student/student-contact/create`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 修改学生联系人
+ export function updateStudentContact(data) {
+ return request({
+ url: `/infra/student/student-contact/update`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 删除学生联系人
+ export function deleteStudentContact(id) {
+ return request({
+ url: `/infra/student/student-contact/delete?id=` + id,
+ method: 'delete'
+ })
+ }
+
+ // 获得学生联系人
+ export function getStudentContact(id) {
+ return request({
+ url: `/infra/student/student-contact/get?id=` + id,
+ method: 'get'
+ })
+ }
+
+// ==================== 子表(学生班主任) ====================
+
+ // 获得学生班主任分页
+ export function getStudentTeacherPage(params) {
+ return request({
+ url: '/infra/student/student-teacher/page',
+ method: 'get',
+ params
+ })
+ }
+ // 新增学生班主任
+ export function createStudentTeacher(data) {
+ return request({
+ url: `/infra/student/student-teacher/create`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 修改学生班主任
+ export function updateStudentTeacher(data) {
+ return request({
+ url: `/infra/student/student-teacher/update`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 删除学生班主任
+ export function deleteStudentTeacher(id) {
+ return request({
+ url: `/infra/student/student-teacher/delete?id=` + id,
+ method: 'delete'
+ })
+ }
+
+ // 获得学生班主任
+ export function getStudentTeacher(id) {
+ return request({
+ url: `/infra/student/student-teacher/get?id=` + id,
+ method: 'get'
+ })
+ }
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/sql/h2 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/sql/h2
new file mode 100644
index 000000000..6c1875f60
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/sql/h2
@@ -0,0 +1,17 @@
+-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
+CREATE TABLE IF NOT EXISTS "infra_student" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar NOT NULL,
+ "description" varchar NOT NULL,
+ "birthday" varchar NOT NULL,
+ "sex" int NOT NULL,
+ "enabled" bit NOT NULL,
+ "avatar" varchar NOT NULL,
+ "video" varchar NOT NULL,
+ "memo" varchar NOT NULL,
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY ("id")
+) COMMENT '学生表';
+
+-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
+DELETE FROM "infra_student";
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/sql/sql b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/sql/sql
new file mode 100644
index 000000000..83df27926
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/sql/sql
@@ -0,0 +1,55 @@
+-- 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '学生管理', '', 2, 0, 888,
+ 'student', '', 'infra/demo/index', 0, 'InfraStudent'
+);
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生查询', 'infra:student:query', 3, 1, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生创建', 'infra:student:create', 3, 2, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生更新', 'infra:student:update', 3, 3, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生删除', 'infra:student:delete', 3, 4, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生导出', 'infra:student:export', 3, 5, @parentId,
+ '', '', '', 0
+);
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentContactForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentContactForm
new file mode 100644
index 000000000..de3b0a734
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentContactForm
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentContactList b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentContactList
new file mode 100644
index 000000000..00f1ce040
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentContactList
@@ -0,0 +1,129 @@
+
+
+
+
+
+ 新增
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentForm
new file mode 100644
index 000000000..d89e5066d
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentForm
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherForm
new file mode 100644
index 000000000..874a03bbc
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherForm
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherList b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherList
new file mode 100644
index 000000000..7d561a0ee
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/StudentTeacherList
@@ -0,0 +1,129 @@
+
+
+
+
+
+ 新增
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/index b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/index
new file mode 100644
index 000000000..9c7588f2a
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_erp/vue/index
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/assert.json b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/assert.json
new file mode 100644
index 000000000..f95ae9af1
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/assert.json
@@ -0,0 +1,73 @@
+[ {
+ "contentPath" : "java/InfraStudentPageReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentRespVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
+}, {
+ "contentPath" : "java/InfraStudentSaveReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentController",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
+}, {
+ "contentPath" : "java/InfraStudentDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
+}, {
+ "contentPath" : "java/InfraStudentContactDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
+}, {
+ "contentPath" : "java/InfraStudentTeacherDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
+}, {
+ "contentPath" : "java/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
+}, {
+ "contentPath" : "java/InfraStudentContactMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
+}, {
+ "contentPath" : "java/InfraStudentTeacherMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
+}, {
+ "contentPath" : "xml/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
+}, {
+ "contentPath" : "java/InfraStudentServiceImpl",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
+}, {
+ "contentPath" : "java/InfraStudentService",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
+}, {
+ "contentPath" : "java/InfraStudentServiceImplTest",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
+}, {
+ "contentPath" : "java/ErrorCodeConstants_手动操作",
+ "filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
+}, {
+ "contentPath" : "sql/sql",
+ "filePath" : "sql/sql.sql"
+}, {
+ "contentPath" : "sql/h2",
+ "filePath" : "sql/h2.sql"
+}, {
+ "contentPath" : "vue/index",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
+}, {
+ "contentPath" : "js/student",
+ "filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
+}, {
+ "contentPath" : "vue/StudentForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
+}, {
+ "contentPath" : "vue/StudentContactForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactForm.vue"
+}, {
+ "contentPath" : "vue/StudentTeacherForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherForm.vue"
+}, {
+ "contentPath" : "vue/StudentContactList",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactList.vue"
+}, {
+ "contentPath" : "vue/StudentTeacherList",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherList.vue"
+} ]
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/ErrorCodeConstants_手动操作 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/ErrorCodeConstants_手动操作
new file mode 100644
index 000000000..f8be66202
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/ErrorCodeConstants_手动操作
@@ -0,0 +1,3 @@
+// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
+// ========== 学生 TODO 补充编号 ==========
+ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentContactDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentContactDO
new file mode 100644
index 000000000..17c668eaa
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentContactDO
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生联系人 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student_contact")
+@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentContactDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 学生编号
+ */
+ private Long studentId;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentContactMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentContactMapper
new file mode 100644
index 000000000..35bbd53c2
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentContactMapper
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生联系人 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentContactMapper extends BaseMapperX {
+
+ default List selectListByStudentId(Long studentId) {
+ return selectList(InfraStudentContactDO::getStudentId, studentId);
+ }
+
+ default int deleteByStudentId(Long studentId) {
+ return delete(InfraStudentContactDO::getStudentId, studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentController b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentController
new file mode 100644
index 000000000..b9a587b44
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentController
@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
+
+@Tag(name = "管理后台 - 学生")
+@RestController
+@RequestMapping("/infra/student")
+@Validated
+public class InfraStudentController {
+
+ @Resource
+ private InfraStudentService studentService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:create')")
+ public CommonResult createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
+ return success(studentService.createStudent(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:update')")
+ public CommonResult updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
+ studentService.updateStudent(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除学生")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:student:delete')")
+ public CommonResult deleteStudent(@RequestParam("id") Long id) {
+ studentService.deleteStudent(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得学生")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudent(@RequestParam("id") Long id) {
+ InfraStudentDO student = studentService.getStudent(id);
+ return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得学生分页")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
+ PageResult pageResult = studentService.getStudentPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出学生 Excel")
+ @PreAuthorize("@ss.hasPermission('infra:student:export')")
+ @OperateLog(type = EXPORT)
+ public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = studentService.getStudentPage(pageReqVO).getList();
+ // 导出 Excel
+ ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
+ BeanUtils.toBean(list, InfraStudentRespVO.class));
+ }
+
+ // ==================== 子表(学生联系人) ====================
+
+ @GetMapping("/student-contact/list-by-student-id")
+ @Operation(summary = "获得学生联系人列表")
+ @Parameter(name = "studentId", description = "学生编号")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentContactListByStudentId(@RequestParam("studentId") Long studentId) {
+ return success(studentService.getStudentContactListByStudentId(studentId));
+ }
+
+ // ==================== 子表(学生班主任) ====================
+
+ @GetMapping("/student-teacher/get-by-student-id")
+ @Operation(summary = "获得学生班主任")
+ @Parameter(name = "studentId", description = "学生编号")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudentTeacherByStudentId(@RequestParam("studentId") Long studentId) {
+ return success(studentService.getStudentTeacherByStudentId(studentId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentDO
new file mode 100644
index 000000000..b0d4bd216
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentDO
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student")
+@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentMapper
new file mode 100644
index 000000000..34e70a082
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentMapper
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+
+/**
+ * 学生 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentMapper extends BaseMapperX {
+
+ default PageResult selectPage(InfraStudentPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(InfraStudentDO::getName, reqVO.getName())
+ .eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
+ .eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
+ .eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
+ .betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(InfraStudentDO::getId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentPageReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentPageReqVO
new file mode 100644
index 000000000..41a373012
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentPageReqVO
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+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 = "管理后台 - 学生分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraStudentPageReqVO extends PageParam {
+
+ @Schema(description = "名字", example = "芋头")
+ private String name;
+
+ @Schema(description = "出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", example = "1")
+ private Integer sex;
+
+ @Schema(description = "是否有效", example = "true")
+ private Boolean enabled;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentRespVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentRespVO
new file mode 100644
index 000000000..c41a5501f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentRespVO
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 学生 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class InfraStudentRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @ExcelProperty("编号")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @ExcelProperty("名字")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @ExcelProperty("简介")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty(value = "性别", converter = DictConvert.class)
+ @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @ExcelProperty(value = "是否有效", converter = DictConvert.class)
+ @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @ExcelProperty("头像")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @ExcelProperty("附件")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @ExcelProperty("备注")
+ private String memo;
+
+ @Schema(description = "创建时间")
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentSaveReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentSaveReqVO
new file mode 100644
index 000000000..faa491dfb
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentSaveReqVO
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+
+@Schema(description = "管理后台 - 学生新增/修改 Request VO")
+@Data
+public class InfraStudentSaveReqVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @NotEmpty(message = "名字不能为空")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @NotEmpty(message = "简介不能为空")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "出生日期不能为空")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "性别不能为空")
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否有效不能为空")
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @NotEmpty(message = "头像不能为空")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @NotEmpty(message = "附件不能为空")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @NotEmpty(message = "备注不能为空")
+ private String memo;
+
+ @Schema(description = "学生联系人列表")
+ private List studentContacts;
+
+ @Schema(description = "学生班主任")
+ private InfraStudentTeacherDO studentTeacher;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentService b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentService
new file mode 100644
index 000000000..afa7d22eb
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentService
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 学生 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfraStudentService {
+
+ /**
+ * 创建学生
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
+
+ /**
+ * 更新学生
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
+
+ /**
+ * 删除学生
+ *
+ * @param id 编号
+ */
+ void deleteStudent(Long id);
+
+ /**
+ * 获得学生
+ *
+ * @param id 编号
+ * @return 学生
+ */
+ InfraStudentDO getStudent(Long id);
+
+ /**
+ * 获得学生分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 学生分页
+ */
+ PageResult getStudentPage(InfraStudentPageReqVO pageReqVO);
+
+ // ==================== 子表(学生联系人) ====================
+
+ /**
+ * 获得学生联系人列表
+ *
+ * @param studentId 学生编号
+ * @return 学生联系人列表
+ */
+ List getStudentContactListByStudentId(Long studentId);
+
+ // ==================== 子表(学生班主任) ====================
+
+ /**
+ * 获得学生班主任
+ *
+ * @param studentId 学生编号
+ * @return 学生班主任
+ */
+ InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentServiceImpl b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentServiceImpl
new file mode 100644
index 000000000..c57cba613
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentServiceImpl
@@ -0,0 +1,147 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 学生 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfraStudentServiceImpl implements InfraStudentService {
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+ @Resource
+ private InfraStudentContactMapper studentContactMapper;
+ @Resource
+ private InfraStudentTeacherMapper studentTeacherMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createStudent(InfraStudentSaveReqVO createReqVO) {
+ // 插入
+ InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
+ studentMapper.insert(student);
+
+ // 插入子表
+ createStudentContactList(student.getId(), createReqVO.getStudentContacts());
+ createStudentTeacher(student.getId(), createReqVO.getStudentTeacher());
+ // 返回
+ return student.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
+ // 校验存在
+ validateStudentExists(updateReqVO.getId());
+ // 更新
+ InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
+ studentMapper.updateById(updateObj);
+
+ // 更新子表
+ updateStudentContactList(updateReqVO.getId(), updateReqVO.getStudentContacts());
+ updateStudentTeacher(updateReqVO.getId(), updateReqVO.getStudentTeacher());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteStudent(Long id) {
+ // 校验存在
+ validateStudentExists(id);
+ // 删除
+ studentMapper.deleteById(id);
+
+ // 删除子表
+ deleteStudentContactByStudentId(id);
+ deleteStudentTeacherByStudentId(id);
+ }
+
+ private void validateStudentExists(Long id) {
+ if (studentMapper.selectById(id) == null) {
+ throw exception(STUDENT_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public InfraStudentDO getStudent(Long id) {
+ return studentMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getStudentPage(InfraStudentPageReqVO pageReqVO) {
+ return studentMapper.selectPage(pageReqVO);
+ }
+
+ // ==================== 子表(学生联系人) ====================
+
+ @Override
+ public List getStudentContactListByStudentId(Long studentId) {
+ return studentContactMapper.selectListByStudentId(studentId);
+ }
+
+ private void createStudentContactList(Long studentId, List list) {
+ list.forEach(o -> o.setStudentId(studentId));
+ studentContactMapper.insertBatch(list);
+ }
+
+ private void updateStudentContactList(Long studentId, List list) {
+ deleteStudentContactByStudentId(studentId);
+ list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+ createStudentContactList(studentId, list);
+ }
+
+ private void deleteStudentContactByStudentId(Long studentId) {
+ studentContactMapper.deleteByStudentId(studentId);
+ }
+
+ // ==================== 子表(学生班主任) ====================
+
+ @Override
+ public InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId) {
+ return studentTeacherMapper.selectByStudentId(studentId);
+ }
+
+ private void createStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
+ if (studentTeacher == null) {
+ return;
+ }
+ studentTeacher.setStudentId(studentId);
+ studentTeacherMapper.insert(studentTeacher);
+ }
+
+ private void updateStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
+ if (studentTeacher == null) {
+ return;
+ }
+ studentTeacher.setStudentId(studentId);
+ studentTeacher.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新
+ studentTeacherMapper.insertOrUpdate(studentTeacher);
+ }
+
+ private void deleteStudentTeacherByStudentId(Long studentId) {
+ studentTeacherMapper.deleteByStudentId(studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentServiceImplTest b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentServiceImplTest
new file mode 100644
index 000000000..b5f4bf0ff
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentServiceImplTest
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link InfraStudentServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(InfraStudentServiceImpl.class)
+public class InfraStudentServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private InfraStudentServiceImpl studentService;
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+
+ @Test
+ public void testCreateStudent_success() {
+ // 准备参数
+ InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
+
+ // 调用
+ Long studentId = studentService.createStudent(createReqVO);
+ // 断言
+ assertNotNull(studentId);
+ // 校验记录的属性是否正确
+ InfraStudentDO student = studentMapper.selectById(studentId);
+ assertPojoEquals(createReqVO, student, "id");
+ }
+
+ @Test
+ public void testUpdateStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
+ o.setId(dbStudent.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ studentService.updateStudent(updateReqVO);
+ // 校验是否更新正确
+ InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
+ assertPojoEquals(updateReqVO, student);
+ }
+
+ @Test
+ public void testUpdateStudent_notExists() {
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbStudent.getId();
+
+ // 调用
+ studentService.deleteStudent(id);
+ // 校验数据不存在了
+ assertNull(studentMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteStudent_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetStudentPage() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
+ o.setName(null);
+ o.setBirthday(null);
+ o.setSex(null);
+ o.setEnabled(null);
+ o.setCreateTime(null);
+ });
+ studentMapper.insert(dbStudent);
+ // 测试 name 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
+ // 测试 birthday 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
+ // 测试 sex 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
+ // 测试 enabled 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
+ // 测试 createTime 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
+ // 准备参数
+ InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
+ reqVO.setName(null);
+ reqVO.setBirthday(null);
+ reqVO.setSex(null);
+ reqVO.setEnabled(null);
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+ // 调用
+ PageResult pageResult = studentService.getStudentPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbStudent, pageResult.getList().get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentTeacherDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentTeacherDO
new file mode 100644
index 000000000..c19cf9fab
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentTeacherDO
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生班主任 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student_teacher")
+@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentTeacherDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 学生编号
+ */
+ private Long studentId;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentTeacherMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentTeacherMapper
new file mode 100644
index 000000000..0521bbaf4
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/java/InfraStudentTeacherMapper
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生班主任 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentTeacherMapper extends BaseMapperX {
+
+ default InfraStudentTeacherDO selectByStudentId(Long studentId) {
+ return selectOne(InfraStudentTeacherDO::getStudentId, studentId);
+ }
+
+ default int deleteByStudentId(Long studentId) {
+ return delete(InfraStudentTeacherDO::getStudentId, studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/js/student b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/js/student
new file mode 100644
index 000000000..b4e6ac5e4
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/js/student
@@ -0,0 +1,74 @@
+import request from '@/utils/request'
+
+// 创建学生
+export function createStudent(data) {
+ return request({
+ url: '/infra/student/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 更新学生
+export function updateStudent(data) {
+ return request({
+ url: '/infra/student/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除学生
+export function deleteStudent(id) {
+ return request({
+ url: '/infra/student/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 获得学生
+export function getStudent(id) {
+ return request({
+ url: '/infra/student/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 获得学生分页
+export function getStudentPage(params) {
+ return request({
+ url: '/infra/student/page',
+ method: 'get',
+ params
+ })
+}
+// 导出学生 Excel
+export function exportStudentExcel(params) {
+ return request({
+ url: '/infra/student/export-excel',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
+
+// ==================== 子表(学生联系人) ====================
+
+ // 获得学生联系人列表
+ export function getStudentContactListByStudentId(studentId) {
+ return request({
+ url: `/infra/student/student-contact/list-by-student-id?studentId=` + studentId,
+ method: 'get'
+ })
+ }
+
+// ==================== 子表(学生班主任) ====================
+
+ // 获得学生班主任
+ export function getStudentTeacherByStudentId(studentId) {
+ return request({
+ url: `/infra/student/student-teacher/get-by-student-id?studentId=` + studentId,
+ method: 'get'
+ })
+ }
+
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/sql/h2 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/sql/h2
new file mode 100644
index 000000000..6c1875f60
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/sql/h2
@@ -0,0 +1,17 @@
+-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
+CREATE TABLE IF NOT EXISTS "infra_student" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar NOT NULL,
+ "description" varchar NOT NULL,
+ "birthday" varchar NOT NULL,
+ "sex" int NOT NULL,
+ "enabled" bit NOT NULL,
+ "avatar" varchar NOT NULL,
+ "video" varchar NOT NULL,
+ "memo" varchar NOT NULL,
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY ("id")
+) COMMENT '学生表';
+
+-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
+DELETE FROM "infra_student";
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/sql/sql b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/sql/sql
new file mode 100644
index 000000000..83df27926
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/sql/sql
@@ -0,0 +1,55 @@
+-- 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '学生管理', '', 2, 0, 888,
+ 'student', '', 'infra/demo/index', 0, 'InfraStudent'
+);
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生查询', 'infra:student:query', 3, 1, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生创建', 'infra:student:create', 3, 2, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生更新', 'infra:student:update', 3, 3, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生删除', 'infra:student:delete', 3, 4, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生导出', 'infra:student:export', 3, 5, @parentId,
+ '', '', '', 0
+);
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentContactForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentContactForm
new file mode 100644
index 000000000..c953bfa13
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentContactForm
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ —
+
+
+
+
+
+ + 添加学生联系人
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentContactList b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentContactList
new file mode 100644
index 000000000..c0a8710e9
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentContactList
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentForm
new file mode 100644
index 000000000..6d93b6122
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentForm
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentTeacherForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentTeacherForm
new file mode 100644
index 000000000..0dac19bb3
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentTeacherForm
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentTeacherList b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentTeacherList
new file mode 100644
index 000000000..9f572742d
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/StudentTeacherList
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/index b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/index
new file mode 100644
index 000000000..ddeafdf29
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/vue/index
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/xml/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/xml/InfraStudentMapper
new file mode 100644
index 000000000..155aa5c27
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_inner/xml/InfraStudentMapper
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/assert.json b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/assert.json
new file mode 100644
index 000000000..844cc753c
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/assert.json
@@ -0,0 +1,67 @@
+[ {
+ "contentPath" : "java/InfraStudentPageReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentRespVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
+}, {
+ "contentPath" : "java/InfraStudentSaveReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentController",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
+}, {
+ "contentPath" : "java/InfraStudentDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
+}, {
+ "contentPath" : "java/InfraStudentContactDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
+}, {
+ "contentPath" : "java/InfraStudentTeacherDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
+}, {
+ "contentPath" : "java/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
+}, {
+ "contentPath" : "java/InfraStudentContactMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
+}, {
+ "contentPath" : "java/InfraStudentTeacherMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
+}, {
+ "contentPath" : "xml/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
+}, {
+ "contentPath" : "java/InfraStudentServiceImpl",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
+}, {
+ "contentPath" : "java/InfraStudentService",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
+}, {
+ "contentPath" : "java/InfraStudentServiceImplTest",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
+}, {
+ "contentPath" : "java/ErrorCodeConstants_手动操作",
+ "filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
+}, {
+ "contentPath" : "sql/sql",
+ "filePath" : "sql/sql.sql"
+}, {
+ "contentPath" : "sql/h2",
+ "filePath" : "sql/h2.sql"
+}, {
+ "contentPath" : "vue/index",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
+}, {
+ "contentPath" : "js/student",
+ "filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
+}, {
+ "contentPath" : "vue/StudentForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
+}, {
+ "contentPath" : "vue/StudentContactForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactForm.vue"
+}, {
+ "contentPath" : "vue/StudentTeacherForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherForm.vue"
+} ]
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/ErrorCodeConstants_手动操作 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/ErrorCodeConstants_手动操作
new file mode 100644
index 000000000..f8be66202
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/ErrorCodeConstants_手动操作
@@ -0,0 +1,3 @@
+// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
+// ========== 学生 TODO 补充编号 ==========
+ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentContactDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentContactDO
new file mode 100644
index 000000000..17c668eaa
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentContactDO
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生联系人 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student_contact")
+@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentContactDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 学生编号
+ */
+ private Long studentId;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentContactMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentContactMapper
new file mode 100644
index 000000000..35bbd53c2
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentContactMapper
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生联系人 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentContactMapper extends BaseMapperX {
+
+ default List selectListByStudentId(Long studentId) {
+ return selectList(InfraStudentContactDO::getStudentId, studentId);
+ }
+
+ default int deleteByStudentId(Long studentId) {
+ return delete(InfraStudentContactDO::getStudentId, studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentController b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentController
new file mode 100644
index 000000000..b9a587b44
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentController
@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
+
+@Tag(name = "管理后台 - 学生")
+@RestController
+@RequestMapping("/infra/student")
+@Validated
+public class InfraStudentController {
+
+ @Resource
+ private InfraStudentService studentService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:create')")
+ public CommonResult createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
+ return success(studentService.createStudent(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:update')")
+ public CommonResult updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
+ studentService.updateStudent(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除学生")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:student:delete')")
+ public CommonResult deleteStudent(@RequestParam("id") Long id) {
+ studentService.deleteStudent(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得学生")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudent(@RequestParam("id") Long id) {
+ InfraStudentDO student = studentService.getStudent(id);
+ return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得学生分页")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
+ PageResult pageResult = studentService.getStudentPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出学生 Excel")
+ @PreAuthorize("@ss.hasPermission('infra:student:export')")
+ @OperateLog(type = EXPORT)
+ public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = studentService.getStudentPage(pageReqVO).getList();
+ // 导出 Excel
+ ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
+ BeanUtils.toBean(list, InfraStudentRespVO.class));
+ }
+
+ // ==================== 子表(学生联系人) ====================
+
+ @GetMapping("/student-contact/list-by-student-id")
+ @Operation(summary = "获得学生联系人列表")
+ @Parameter(name = "studentId", description = "学生编号")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentContactListByStudentId(@RequestParam("studentId") Long studentId) {
+ return success(studentService.getStudentContactListByStudentId(studentId));
+ }
+
+ // ==================== 子表(学生班主任) ====================
+
+ @GetMapping("/student-teacher/get-by-student-id")
+ @Operation(summary = "获得学生班主任")
+ @Parameter(name = "studentId", description = "学生编号")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudentTeacherByStudentId(@RequestParam("studentId") Long studentId) {
+ return success(studentService.getStudentTeacherByStudentId(studentId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentDO
new file mode 100644
index 000000000..b0d4bd216
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentDO
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student")
+@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentMapper
new file mode 100644
index 000000000..34e70a082
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentMapper
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+
+/**
+ * 学生 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentMapper extends BaseMapperX {
+
+ default PageResult selectPage(InfraStudentPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(InfraStudentDO::getName, reqVO.getName())
+ .eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
+ .eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
+ .eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
+ .betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(InfraStudentDO::getId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentPageReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentPageReqVO
new file mode 100644
index 000000000..41a373012
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentPageReqVO
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+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 = "管理后台 - 学生分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraStudentPageReqVO extends PageParam {
+
+ @Schema(description = "名字", example = "芋头")
+ private String name;
+
+ @Schema(description = "出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", example = "1")
+ private Integer sex;
+
+ @Schema(description = "是否有效", example = "true")
+ private Boolean enabled;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentRespVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentRespVO
new file mode 100644
index 000000000..c41a5501f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentRespVO
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 学生 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class InfraStudentRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @ExcelProperty("编号")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @ExcelProperty("名字")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @ExcelProperty("简介")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty(value = "性别", converter = DictConvert.class)
+ @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @ExcelProperty(value = "是否有效", converter = DictConvert.class)
+ @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @ExcelProperty("头像")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @ExcelProperty("附件")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @ExcelProperty("备注")
+ private String memo;
+
+ @Schema(description = "创建时间")
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentSaveReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentSaveReqVO
new file mode 100644
index 000000000..faa491dfb
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentSaveReqVO
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+
+@Schema(description = "管理后台 - 学生新增/修改 Request VO")
+@Data
+public class InfraStudentSaveReqVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @NotEmpty(message = "名字不能为空")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @NotEmpty(message = "简介不能为空")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "出生日期不能为空")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "性别不能为空")
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否有效不能为空")
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @NotEmpty(message = "头像不能为空")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @NotEmpty(message = "附件不能为空")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @NotEmpty(message = "备注不能为空")
+ private String memo;
+
+ @Schema(description = "学生联系人列表")
+ private List studentContacts;
+
+ @Schema(description = "学生班主任")
+ private InfraStudentTeacherDO studentTeacher;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentService b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentService
new file mode 100644
index 000000000..afa7d22eb
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentService
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 学生 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfraStudentService {
+
+ /**
+ * 创建学生
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
+
+ /**
+ * 更新学生
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
+
+ /**
+ * 删除学生
+ *
+ * @param id 编号
+ */
+ void deleteStudent(Long id);
+
+ /**
+ * 获得学生
+ *
+ * @param id 编号
+ * @return 学生
+ */
+ InfraStudentDO getStudent(Long id);
+
+ /**
+ * 获得学生分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 学生分页
+ */
+ PageResult getStudentPage(InfraStudentPageReqVO pageReqVO);
+
+ // ==================== 子表(学生联系人) ====================
+
+ /**
+ * 获得学生联系人列表
+ *
+ * @param studentId 学生编号
+ * @return 学生联系人列表
+ */
+ List getStudentContactListByStudentId(Long studentId);
+
+ // ==================== 子表(学生班主任) ====================
+
+ /**
+ * 获得学生班主任
+ *
+ * @param studentId 学生编号
+ * @return 学生班主任
+ */
+ InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentServiceImpl b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentServiceImpl
new file mode 100644
index 000000000..c57cba613
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentServiceImpl
@@ -0,0 +1,147 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 学生 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfraStudentServiceImpl implements InfraStudentService {
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+ @Resource
+ private InfraStudentContactMapper studentContactMapper;
+ @Resource
+ private InfraStudentTeacherMapper studentTeacherMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createStudent(InfraStudentSaveReqVO createReqVO) {
+ // 插入
+ InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
+ studentMapper.insert(student);
+
+ // 插入子表
+ createStudentContactList(student.getId(), createReqVO.getStudentContacts());
+ createStudentTeacher(student.getId(), createReqVO.getStudentTeacher());
+ // 返回
+ return student.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
+ // 校验存在
+ validateStudentExists(updateReqVO.getId());
+ // 更新
+ InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
+ studentMapper.updateById(updateObj);
+
+ // 更新子表
+ updateStudentContactList(updateReqVO.getId(), updateReqVO.getStudentContacts());
+ updateStudentTeacher(updateReqVO.getId(), updateReqVO.getStudentTeacher());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteStudent(Long id) {
+ // 校验存在
+ validateStudentExists(id);
+ // 删除
+ studentMapper.deleteById(id);
+
+ // 删除子表
+ deleteStudentContactByStudentId(id);
+ deleteStudentTeacherByStudentId(id);
+ }
+
+ private void validateStudentExists(Long id) {
+ if (studentMapper.selectById(id) == null) {
+ throw exception(STUDENT_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public InfraStudentDO getStudent(Long id) {
+ return studentMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getStudentPage(InfraStudentPageReqVO pageReqVO) {
+ return studentMapper.selectPage(pageReqVO);
+ }
+
+ // ==================== 子表(学生联系人) ====================
+
+ @Override
+ public List getStudentContactListByStudentId(Long studentId) {
+ return studentContactMapper.selectListByStudentId(studentId);
+ }
+
+ private void createStudentContactList(Long studentId, List list) {
+ list.forEach(o -> o.setStudentId(studentId));
+ studentContactMapper.insertBatch(list);
+ }
+
+ private void updateStudentContactList(Long studentId, List list) {
+ deleteStudentContactByStudentId(studentId);
+ list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+ createStudentContactList(studentId, list);
+ }
+
+ private void deleteStudentContactByStudentId(Long studentId) {
+ studentContactMapper.deleteByStudentId(studentId);
+ }
+
+ // ==================== 子表(学生班主任) ====================
+
+ @Override
+ public InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId) {
+ return studentTeacherMapper.selectByStudentId(studentId);
+ }
+
+ private void createStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
+ if (studentTeacher == null) {
+ return;
+ }
+ studentTeacher.setStudentId(studentId);
+ studentTeacherMapper.insert(studentTeacher);
+ }
+
+ private void updateStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
+ if (studentTeacher == null) {
+ return;
+ }
+ studentTeacher.setStudentId(studentId);
+ studentTeacher.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新
+ studentTeacherMapper.insertOrUpdate(studentTeacher);
+ }
+
+ private void deleteStudentTeacherByStudentId(Long studentId) {
+ studentTeacherMapper.deleteByStudentId(studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentServiceImplTest b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentServiceImplTest
new file mode 100644
index 000000000..b5f4bf0ff
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentServiceImplTest
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link InfraStudentServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(InfraStudentServiceImpl.class)
+public class InfraStudentServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private InfraStudentServiceImpl studentService;
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+
+ @Test
+ public void testCreateStudent_success() {
+ // 准备参数
+ InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
+
+ // 调用
+ Long studentId = studentService.createStudent(createReqVO);
+ // 断言
+ assertNotNull(studentId);
+ // 校验记录的属性是否正确
+ InfraStudentDO student = studentMapper.selectById(studentId);
+ assertPojoEquals(createReqVO, student, "id");
+ }
+
+ @Test
+ public void testUpdateStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
+ o.setId(dbStudent.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ studentService.updateStudent(updateReqVO);
+ // 校验是否更新正确
+ InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
+ assertPojoEquals(updateReqVO, student);
+ }
+
+ @Test
+ public void testUpdateStudent_notExists() {
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbStudent.getId();
+
+ // 调用
+ studentService.deleteStudent(id);
+ // 校验数据不存在了
+ assertNull(studentMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteStudent_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetStudentPage() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
+ o.setName(null);
+ o.setBirthday(null);
+ o.setSex(null);
+ o.setEnabled(null);
+ o.setCreateTime(null);
+ });
+ studentMapper.insert(dbStudent);
+ // 测试 name 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
+ // 测试 birthday 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
+ // 测试 sex 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
+ // 测试 enabled 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
+ // 测试 createTime 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
+ // 准备参数
+ InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
+ reqVO.setName(null);
+ reqVO.setBirthday(null);
+ reqVO.setSex(null);
+ reqVO.setEnabled(null);
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+ // 调用
+ PageResult pageResult = studentService.getStudentPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbStudent, pageResult.getList().get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentTeacherDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentTeacherDO
new file mode 100644
index 000000000..c19cf9fab
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentTeacherDO
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生班主任 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student_teacher")
+@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentTeacherDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 学生编号
+ */
+ private Long studentId;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentTeacherMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentTeacherMapper
new file mode 100644
index 000000000..0521bbaf4
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/java/InfraStudentTeacherMapper
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 学生班主任 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentTeacherMapper extends BaseMapperX {
+
+ default InfraStudentTeacherDO selectByStudentId(Long studentId) {
+ return selectOne(InfraStudentTeacherDO::getStudentId, studentId);
+ }
+
+ default int deleteByStudentId(Long studentId) {
+ return delete(InfraStudentTeacherDO::getStudentId, studentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/js/student b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/js/student
new file mode 100644
index 000000000..b4e6ac5e4
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/js/student
@@ -0,0 +1,74 @@
+import request from '@/utils/request'
+
+// 创建学生
+export function createStudent(data) {
+ return request({
+ url: '/infra/student/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 更新学生
+export function updateStudent(data) {
+ return request({
+ url: '/infra/student/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除学生
+export function deleteStudent(id) {
+ return request({
+ url: '/infra/student/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 获得学生
+export function getStudent(id) {
+ return request({
+ url: '/infra/student/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 获得学生分页
+export function getStudentPage(params) {
+ return request({
+ url: '/infra/student/page',
+ method: 'get',
+ params
+ })
+}
+// 导出学生 Excel
+export function exportStudentExcel(params) {
+ return request({
+ url: '/infra/student/export-excel',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
+
+// ==================== 子表(学生联系人) ====================
+
+ // 获得学生联系人列表
+ export function getStudentContactListByStudentId(studentId) {
+ return request({
+ url: `/infra/student/student-contact/list-by-student-id?studentId=` + studentId,
+ method: 'get'
+ })
+ }
+
+// ==================== 子表(学生班主任) ====================
+
+ // 获得学生班主任
+ export function getStudentTeacherByStudentId(studentId) {
+ return request({
+ url: `/infra/student/student-teacher/get-by-student-id?studentId=` + studentId,
+ method: 'get'
+ })
+ }
+
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/sql/h2 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/sql/h2
new file mode 100644
index 000000000..6c1875f60
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/sql/h2
@@ -0,0 +1,17 @@
+-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
+CREATE TABLE IF NOT EXISTS "infra_student" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar NOT NULL,
+ "description" varchar NOT NULL,
+ "birthday" varchar NOT NULL,
+ "sex" int NOT NULL,
+ "enabled" bit NOT NULL,
+ "avatar" varchar NOT NULL,
+ "video" varchar NOT NULL,
+ "memo" varchar NOT NULL,
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY ("id")
+) COMMENT '学生表';
+
+-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
+DELETE FROM "infra_student";
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/sql/sql b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/sql/sql
new file mode 100644
index 000000000..83df27926
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/sql/sql
@@ -0,0 +1,55 @@
+-- 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '学生管理', '', 2, 0, 888,
+ 'student', '', 'infra/demo/index', 0, 'InfraStudent'
+);
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生查询', 'infra:student:query', 3, 1, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生创建', 'infra:student:create', 3, 2, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生更新', 'infra:student:update', 3, 3, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生删除', 'infra:student:delete', 3, 4, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生导出', 'infra:student:export', 3, 5, @parentId,
+ '', '', '', 0
+);
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentContactForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentContactForm
new file mode 100644
index 000000000..c953bfa13
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentContactForm
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ —
+
+
+
+
+
+ + 添加学生联系人
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentForm
new file mode 100644
index 000000000..6d93b6122
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentForm
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentTeacherForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentTeacherForm
new file mode 100644
index 000000000..0dac19bb3
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/StudentTeacherForm
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/index b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/index
new file mode 100644
index 000000000..460758127
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/vue/index
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/xml/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/xml/InfraStudentMapper
new file mode 100644
index 000000000..155aa5c27
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_master_normal/xml/InfraStudentMapper
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/assert.json b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/assert.json
new file mode 100644
index 000000000..ccb00a814
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/assert.json
@@ -0,0 +1,49 @@
+[ {
+ "contentPath" : "java/InfraStudentPageReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentRespVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
+}, {
+ "contentPath" : "java/InfraStudentSaveReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
+}, {
+ "contentPath" : "java/InfraStudentController",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
+}, {
+ "contentPath" : "java/InfraStudentDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
+}, {
+ "contentPath" : "java/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
+}, {
+ "contentPath" : "xml/InfraStudentMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
+}, {
+ "contentPath" : "java/InfraStudentServiceImpl",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
+}, {
+ "contentPath" : "java/InfraStudentService",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
+}, {
+ "contentPath" : "java/InfraStudentServiceImplTest",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
+}, {
+ "contentPath" : "java/ErrorCodeConstants_手动操作",
+ "filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
+}, {
+ "contentPath" : "sql/sql",
+ "filePath" : "sql/sql.sql"
+}, {
+ "contentPath" : "sql/h2",
+ "filePath" : "sql/h2.sql"
+}, {
+ "contentPath" : "vue/index",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
+}, {
+ "contentPath" : "js/student",
+ "filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
+}, {
+ "contentPath" : "vue/StudentForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
+} ]
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/ErrorCodeConstants_手动操作 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/ErrorCodeConstants_手动操作
new file mode 100644
index 000000000..f8be66202
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/ErrorCodeConstants_手动操作
@@ -0,0 +1,3 @@
+// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
+// ========== 学生 TODO 补充编号 ==========
+ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentController b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentController
new file mode 100644
index 000000000..3796982c4
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentController
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
+
+@Tag(name = "管理后台 - 学生")
+@RestController
+@RequestMapping("/infra/student")
+@Validated
+public class InfraStudentController {
+
+ @Resource
+ private InfraStudentService studentService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:create')")
+ public CommonResult createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
+ return success(studentService.createStudent(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新学生")
+ @PreAuthorize("@ss.hasPermission('infra:student:update')")
+ public CommonResult updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
+ studentService.updateStudent(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除学生")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:student:delete')")
+ public CommonResult deleteStudent(@RequestParam("id") Long id) {
+ studentService.deleteStudent(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得学生")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult getStudent(@RequestParam("id") Long id) {
+ InfraStudentDO student = studentService.getStudent(id);
+ return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得学生分页")
+ @PreAuthorize("@ss.hasPermission('infra:student:query')")
+ public CommonResult> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
+ PageResult pageResult = studentService.getStudentPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出学生 Excel")
+ @PreAuthorize("@ss.hasPermission('infra:student:export')")
+ @OperateLog(type = EXPORT)
+ public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = studentService.getStudentPage(pageReqVO).getList();
+ // 导出 Excel
+ ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
+ BeanUtils.toBean(list, InfraStudentRespVO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentDO
new file mode 100644
index 000000000..b0d4bd216
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentDO
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 学生 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_student")
+@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraStudentDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 简介
+ */
+ private String description;
+ /**
+ * 出生日期
+ */
+ private LocalDateTime birthday;
+ /**
+ * 性别
+ *
+ * 枚举 {@link TODO system_user_sex 对应的类}
+ */
+ private Integer sex;
+ /**
+ * 是否有效
+ *
+ * 枚举 {@link TODO infra_boolean_string 对应的类}
+ */
+ private Boolean enabled;
+ /**
+ * 头像
+ */
+ private String avatar;
+ /**
+ * 附件
+ */
+ private String video;
+ /**
+ * 备注
+ */
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentMapper
new file mode 100644
index 000000000..34e70a082
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentMapper
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+
+/**
+ * 学生 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraStudentMapper extends BaseMapperX {
+
+ default PageResult selectPage(InfraStudentPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(InfraStudentDO::getName, reqVO.getName())
+ .eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
+ .eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
+ .eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
+ .betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(InfraStudentDO::getId));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentPageReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentPageReqVO
new file mode 100644
index 000000000..41a373012
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentPageReqVO
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+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 = "管理后台 - 学生分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class InfraStudentPageReqVO extends PageParam {
+
+ @Schema(description = "名字", example = "芋头")
+ private String name;
+
+ @Schema(description = "出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", example = "1")
+ private Integer sex;
+
+ @Schema(description = "是否有效", example = "true")
+ private Boolean enabled;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentRespVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentRespVO
new file mode 100644
index 000000000..c41a5501f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentRespVO
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+
+@Schema(description = "管理后台 - 学生 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class InfraStudentRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @ExcelProperty("编号")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @ExcelProperty("名字")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @ExcelProperty("简介")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("出生日期")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty(value = "性别", converter = DictConvert.class)
+ @DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @ExcelProperty(value = "是否有效", converter = DictConvert.class)
+ @DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @ExcelProperty("头像")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @ExcelProperty("附件")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @ExcelProperty("备注")
+ private String memo;
+
+ @Schema(description = "创建时间")
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentSaveReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentSaveReqVO
new file mode 100644
index 000000000..43e7f147d
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentSaveReqVO
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 学生新增/修改 Request VO")
+@Data
+public class InfraStudentSaveReqVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @NotEmpty(message = "名字不能为空")
+ private String name;
+
+ @Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
+ @NotEmpty(message = "简介不能为空")
+ private String description;
+
+ @Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "出生日期不能为空")
+ private LocalDateTime birthday;
+
+ @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "性别不能为空")
+ private Integer sex;
+
+ @Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否有效不能为空")
+ private Boolean enabled;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+ @NotEmpty(message = "头像不能为空")
+ private String avatar;
+
+ @Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
+ @NotEmpty(message = "附件不能为空")
+ private String video;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
+ @NotEmpty(message = "备注不能为空")
+ private String memo;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentService b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentService
new file mode 100644
index 000000000..c4a0e1792
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentService
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 学生 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfraStudentService {
+
+ /**
+ * 创建学生
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
+
+ /**
+ * 更新学生
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
+
+ /**
+ * 删除学生
+ *
+ * @param id 编号
+ */
+ void deleteStudent(Long id);
+
+ /**
+ * 获得学生
+ *
+ * @param id 编号
+ * @return 学生
+ */
+ InfraStudentDO getStudent(Long id);
+
+ /**
+ * 获得学生分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 学生分页
+ */
+ PageResult getStudentPage(InfraStudentPageReqVO pageReqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentServiceImpl b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentServiceImpl
new file mode 100644
index 000000000..2292a66f3
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentServiceImpl
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 学生 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfraStudentServiceImpl implements InfraStudentService {
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+
+ @Override
+ public Long createStudent(InfraStudentSaveReqVO createReqVO) {
+ // 插入
+ InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
+ studentMapper.insert(student);
+ // 返回
+ return student.getId();
+ }
+
+ @Override
+ public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
+ // 校验存在
+ validateStudentExists(updateReqVO.getId());
+ // 更新
+ InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
+ studentMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteStudent(Long id) {
+ // 校验存在
+ validateStudentExists(id);
+ // 删除
+ studentMapper.deleteById(id);
+ }
+
+ private void validateStudentExists(Long id) {
+ if (studentMapper.selectById(id) == null) {
+ throw exception(STUDENT_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public InfraStudentDO getStudent(Long id) {
+ return studentMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getStudentPage(InfraStudentPageReqVO pageReqVO) {
+ return studentMapper.selectPage(pageReqVO);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentServiceImplTest b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentServiceImplTest
new file mode 100644
index 000000000..b5f4bf0ff
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/java/InfraStudentServiceImplTest
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link InfraStudentServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(InfraStudentServiceImpl.class)
+public class InfraStudentServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private InfraStudentServiceImpl studentService;
+
+ @Resource
+ private InfraStudentMapper studentMapper;
+
+ @Test
+ public void testCreateStudent_success() {
+ // 准备参数
+ InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
+
+ // 调用
+ Long studentId = studentService.createStudent(createReqVO);
+ // 断言
+ assertNotNull(studentId);
+ // 校验记录的属性是否正确
+ InfraStudentDO student = studentMapper.selectById(studentId);
+ assertPojoEquals(createReqVO, student, "id");
+ }
+
+ @Test
+ public void testUpdateStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
+ o.setId(dbStudent.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ studentService.updateStudent(updateReqVO);
+ // 校验是否更新正确
+ InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
+ assertPojoEquals(updateReqVO, student);
+ }
+
+ @Test
+ public void testUpdateStudent_notExists() {
+ // 准备参数
+ InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteStudent_success() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
+ studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbStudent.getId();
+
+ // 调用
+ studentService.deleteStudent(id);
+ // 校验数据不存在了
+ assertNull(studentMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteStudent_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
+ }
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetStudentPage() {
+ // mock 数据
+ InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
+ o.setName(null);
+ o.setBirthday(null);
+ o.setSex(null);
+ o.setEnabled(null);
+ o.setCreateTime(null);
+ });
+ studentMapper.insert(dbStudent);
+ // 测试 name 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
+ // 测试 birthday 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
+ // 测试 sex 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
+ // 测试 enabled 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
+ // 测试 createTime 不匹配
+ studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
+ // 准备参数
+ InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
+ reqVO.setName(null);
+ reqVO.setBirthday(null);
+ reqVO.setSex(null);
+ reqVO.setEnabled(null);
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+ // 调用
+ PageResult pageResult = studentService.getStudentPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbStudent, pageResult.getList().get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/js/student b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/js/student
new file mode 100644
index 000000000..44db46806
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/js/student
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 创建学生
+export function createStudent(data) {
+ return request({
+ url: '/infra/student/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 更新学生
+export function updateStudent(data) {
+ return request({
+ url: '/infra/student/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除学生
+export function deleteStudent(id) {
+ return request({
+ url: '/infra/student/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 获得学生
+export function getStudent(id) {
+ return request({
+ url: '/infra/student/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 获得学生分页
+export function getStudentPage(params) {
+ return request({
+ url: '/infra/student/page',
+ method: 'get',
+ params
+ })
+}
+// 导出学生 Excel
+export function exportStudentExcel(params) {
+ return request({
+ url: '/infra/student/export-excel',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/sql/h2 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/sql/h2
new file mode 100644
index 000000000..6c1875f60
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/sql/h2
@@ -0,0 +1,17 @@
+-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
+CREATE TABLE IF NOT EXISTS "infra_student" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar NOT NULL,
+ "description" varchar NOT NULL,
+ "birthday" varchar NOT NULL,
+ "sex" int NOT NULL,
+ "enabled" bit NOT NULL,
+ "avatar" varchar NOT NULL,
+ "video" varchar NOT NULL,
+ "memo" varchar NOT NULL,
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY ("id")
+) COMMENT '学生表';
+
+-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
+DELETE FROM "infra_student";
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/sql/sql b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/sql/sql
new file mode 100644
index 000000000..83df27926
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/sql/sql
@@ -0,0 +1,55 @@
+-- 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '学生管理', '', 2, 0, 888,
+ 'student', '', 'infra/demo/index', 0, 'InfraStudent'
+);
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生查询', 'infra:student:query', 3, 1, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生创建', 'infra:student:create', 3, 2, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生更新', 'infra:student:update', 3, 3, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生删除', 'infra:student:delete', 3, 4, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '学生导出', 'infra:student:export', 3, 5, @parentId,
+ '', '', '', 0
+);
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/vue/StudentForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/vue/StudentForm
new file mode 100644
index 000000000..d89e5066d
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/vue/StudentForm
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dict.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/vue/index b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/vue/index
new file mode 100644
index 000000000..460758127
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/vue/index
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.birthday) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/xml/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/xml/InfraStudentMapper
new file mode 100644
index 000000000..155aa5c27
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one/xml/InfraStudentMapper
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/assert.json b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/assert.json
new file mode 100644
index 000000000..470a9dec8
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/assert.json
@@ -0,0 +1,49 @@
+[ {
+ "contentPath" : "java/InfraCategoryListReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraCategoryListReqVO.java"
+}, {
+ "contentPath" : "java/InfraCategoryRespVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraCategoryRespVO.java"
+}, {
+ "contentPath" : "java/InfraCategorySaveReqVO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraCategorySaveReqVO.java"
+}, {
+ "contentPath" : "java/InfraCategoryController",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraCategoryController.java"
+}, {
+ "contentPath" : "java/InfraCategoryDO",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraCategoryDO.java"
+}, {
+ "contentPath" : "java/InfraCategoryMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraCategoryMapper.java"
+}, {
+ "contentPath" : "xml/InfraCategoryMapper",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraCategoryMapper.xml"
+}, {
+ "contentPath" : "java/InfraCategoryServiceImpl",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraCategoryServiceImpl.java"
+}, {
+ "contentPath" : "java/InfraCategoryService",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraCategoryService.java"
+}, {
+ "contentPath" : "java/InfraCategoryServiceImplTest",
+ "filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraCategoryServiceImplTest.java"
+}, {
+ "contentPath" : "java/ErrorCodeConstants_手动操作",
+ "filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
+}, {
+ "contentPath" : "sql/sql",
+ "filePath" : "sql/sql.sql"
+}, {
+ "contentPath" : "sql/h2",
+ "filePath" : "sql/h2.sql"
+}, {
+ "contentPath" : "vue/index",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
+}, {
+ "contentPath" : "js/category",
+ "filePath" : "yudao-ui-admin-vue2/src/api/infra/category.js"
+}, {
+ "contentPath" : "vue/CategoryForm",
+ "filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/CategoryForm.vue"
+} ]
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/ErrorCodeConstants_手动操作 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/ErrorCodeConstants_手动操作
new file mode 100644
index 000000000..36df6752e
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/ErrorCodeConstants_手动操作
@@ -0,0 +1,8 @@
+// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
+// ========== 分类 TODO 补充编号 ==========
+ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(TODO 补充编号, "分类不存在");
+ErrorCode CATEGORY_EXITS_CHILDREN = new ErrorCode(TODO 补充编号, "存在存在子分类,无法删除");
+ErrorCode CATEGORY_PARENT_NOT_EXITS = new ErrorCode(TODO 补充编号,"父级分类不存在");
+ErrorCode CATEGORY_PARENT_ERROR = new ErrorCode(TODO 补充编号, "不能设置自己为父分类");
+ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(TODO 补充编号, "已经存在该名字的分类");
+ErrorCode CATEGORY_PARENT_IS_CHILD = new ErrorCode(TODO 补充编号, "不能设置自己的子InfraCategory为父InfraCategory");
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryController b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryController
new file mode 100644
index 000000000..a7b2f8163
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryController
@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraCategoryDO;
+import cn.iocoder.yudao.module.infra.service.demo.InfraCategoryService;
+
+@Tag(name = "管理后台 - 分类")
+@RestController
+@RequestMapping("/infra/category")
+@Validated
+public class InfraCategoryController {
+
+ @Resource
+ private InfraCategoryService categoryService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建分类")
+ @PreAuthorize("@ss.hasPermission('infra:category:create')")
+ public CommonResult createCategory(@Valid @RequestBody InfraCategorySaveReqVO createReqVO) {
+ return success(categoryService.createCategory(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新分类")
+ @PreAuthorize("@ss.hasPermission('infra:category:update')")
+ public CommonResult updateCategory(@Valid @RequestBody InfraCategorySaveReqVO updateReqVO) {
+ categoryService.updateCategory(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除分类")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('infra:category:delete')")
+ public CommonResult deleteCategory(@RequestParam("id") Long id) {
+ categoryService.deleteCategory(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得分类")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('infra:category:query')")
+ public CommonResult getCategory(@RequestParam("id") Long id) {
+ InfraCategoryDO category = categoryService.getCategory(id);
+ return success(BeanUtils.toBean(category, InfraCategoryRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得分类列表")
+ @PreAuthorize("@ss.hasPermission('infra:category:query')")
+ public CommonResult> getCategoryList(@Valid InfraCategoryListReqVO listReqVO) {
+ List list = categoryService.getCategoryList(listReqVO);
+ return success(BeanUtils.toBean(list, InfraCategoryRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出分类 Excel")
+ @PreAuthorize("@ss.hasPermission('infra:category:export')")
+ @OperateLog(type = EXPORT)
+ public void exportCategoryExcel(@Valid InfraCategoryListReqVO listReqVO,
+ HttpServletResponse response) throws IOException {
+ List list = categoryService.getCategoryList(listReqVO);
+ // 导出 Excel
+ ExcelUtils.write(response, "分类.xls", "数据", InfraCategoryRespVO.class,
+ BeanUtils.toBean(list, InfraCategoryRespVO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryDO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryDO
new file mode 100644
index 000000000..9bf21c08b
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryDO
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 分类 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("infra_category")
+@KeySequence("infra_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InfraCategoryDO extends BaseDO {
+
+ public static final Long PARENT_ID_ROOT = 0L;
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名字
+ */
+ private String name;
+ /**
+ * 父编号
+ */
+ private Long parentId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryListReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryListReqVO
new file mode 100644
index 000000000..e5c6f181f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryListReqVO
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+@Schema(description = "管理后台 - 分类列表 Request VO")
+@Data
+public class InfraCategoryListReqVO {
+
+ @Schema(description = "名字", example = "芋头")
+ private String name;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryMapper
new file mode 100644
index 000000000..9dadbf1d9
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryMapper
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.infra.dal.mysql.demo;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraCategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+
+/**
+ * 分类 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface InfraCategoryMapper extends BaseMapperX {
+
+ default List selectList(InfraCategoryListReqVO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .likeIfPresent(InfraCategoryDO::getName, reqVO.getName())
+ .orderByDesc(InfraCategoryDO::getId));
+ }
+
+ default InfraCategoryDO selectByParentIdAndName(Long parentId, String name) {
+ return selectOne(InfraCategoryDO::getParentId, parentId, InfraCategoryDO::getName, name);
+ }
+
+ default Long selectCountByParentId(Long parentId) {
+ return selectCount(InfraCategoryDO::getParentId, parentId);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryRespVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryRespVO
new file mode 100644
index 000000000..6325d866c
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryRespVO
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import java.util.*;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - 分类 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class InfraCategoryRespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @ExcelProperty("编号")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @ExcelProperty("名字")
+ private String name;
+
+ @Schema(description = "父编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+ @ExcelProperty("父编号")
+ private Long parentId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategorySaveReqVO b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategorySaveReqVO
new file mode 100644
index 000000000..3c03b977f
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategorySaveReqVO
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+import java.util.*;
+
+@Schema(description = "管理后台 - 分类新增/修改 Request VO")
+@Data
+public class InfraCategorySaveReqVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
+ @NotEmpty(message = "名字不能为空")
+ private String name;
+
+ @Schema(description = "父编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+ @NotNull(message = "父编号不能为空")
+ private Long parentId;
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryService b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryService
new file mode 100644
index 000000000..9d0ae1afa
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryService
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraCategoryDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+/**
+ * 分类 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface InfraCategoryService {
+
+ /**
+ * 创建分类
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createCategory(@Valid InfraCategorySaveReqVO createReqVO);
+
+ /**
+ * 更新分类
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateCategory(@Valid InfraCategorySaveReqVO updateReqVO);
+
+ /**
+ * 删除分类
+ *
+ * @param id 编号
+ */
+ void deleteCategory(Long id);
+
+ /**
+ * 获得分类
+ *
+ * @param id 编号
+ * @return 分类
+ */
+ InfraCategoryDO getCategory(Long id);
+
+ /**
+ * 获得分类列表
+ *
+ * @param listReqVO 查询条件
+ * @return 分类列表
+ */
+ List getCategoryList(InfraCategoryListReqVO listReqVO);
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryServiceImpl b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryServiceImpl
new file mode 100644
index 000000000..351568b18
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryServiceImpl
@@ -0,0 +1,136 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraCategoryDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraCategoryMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+
+/**
+ * 分类 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class InfraCategoryServiceImpl implements InfraCategoryService {
+
+ @Resource
+ private InfraCategoryMapper categoryMapper;
+
+ @Override
+ public Long createCategory(InfraCategorySaveReqVO createReqVO) {
+ // 校验父编号的有效性
+ validateParentCategory(null, createReqVO.getParentId());
+ // 校验名字的唯一性
+ validateCategoryNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
+
+ // 插入
+ InfraCategoryDO category = BeanUtils.toBean(createReqVO, InfraCategoryDO.class);
+ categoryMapper.insert(category);
+ // 返回
+ return category.getId();
+ }
+
+ @Override
+ public void updateCategory(InfraCategorySaveReqVO updateReqVO) {
+ // 校验存在
+ validateCategoryExists(updateReqVO.getId());
+ // 校验父编号的有效性
+ validateParentCategory(updateReqVO.getId(), updateReqVO.getParentId());
+ // 校验名字的唯一性
+ validateCategoryNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
+
+ // 更新
+ InfraCategoryDO updateObj = BeanUtils.toBean(updateReqVO, InfraCategoryDO.class);
+ categoryMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteCategory(Long id) {
+ // 校验存在
+ validateCategoryExists(id);
+ // 校验是否有子分类
+ if (categoryMapper.selectCountByParentId(id) > 0) {
+ throw exception(CATEGORY_EXITS_CHILDREN);
+ }
+ // 删除
+ categoryMapper.deleteById(id);
+ }
+
+ private void validateCategoryExists(Long id) {
+ if (categoryMapper.selectById(id) == null) {
+ throw exception(CATEGORY_NOT_EXISTS);
+ }
+ }
+
+ private void validateParentCategory(Long id, Long parentId) {
+ if (parentId == null || CategoryDO.PARENT_ID_ROOT.equals(parentId)) {
+ return;
+ }
+ // 1. 不能设置自己为父分类
+ if (Objects.equals(id, parentId)) {
+ throw exception(CATEGORY_PARENT_ERROR);
+ }
+ // 2. 父分类不存在
+ CategoryDO parentCategory = categoryMapper.selectById(parentId);
+ if (parentCategory == null) {
+ throw exception(CATEGORY_PARENT_NOT_EXITS);
+ }
+ // 3. 递归校验父分类,如果父分类是自己的子分类,则报错,避免形成环路
+ if (id == null) { // id 为空,说明新增,不需要考虑环路
+ return;
+ }
+ for (int i = 0; i < Short.MAX_VALUE; i++) {
+ // 3.1 校验环路
+ parentId = parentCategory.getParentId();
+ if (Objects.equals(id, parentId)) {
+ throw exception(CATEGORY_PARENT_IS_CHILD);
+ }
+ // 3.2 继续递归下一级父分类
+ if (parentId == null || CategoryDO.PARENT_ID_ROOT.equals(parentId)) {
+ break;
+ }
+ parentCategory = categoryMapper.selectById(parentId);
+ if (parentCategory == null) {
+ break;
+ }
+ }
+ }
+
+ private void validateCategoryNameUnique(Long id, Long parentId, String name) {
+ CategoryDO category = categoryMapper.selectByParentIdAndName(parentId, name);
+ if (category == null) {
+ return;
+ }
+ // 如果 id 为空,说明不用比较是否为相同 id 的分类
+ if (id == null) {
+ throw exception(CATEGORY_NAME_DUPLICATE);
+ }
+ if (!Objects.equals(category.getId(), id)) {
+ throw exception(CATEGORY_NAME_DUPLICATE);
+ }
+ }
+
+ @Override
+ public InfraCategoryDO getCategory(Long id) {
+ return categoryMapper.selectById(id);
+ }
+
+ @Override
+ public List getCategoryList(InfraCategoryListReqVO listReqVO) {
+ return categoryMapper.selectList(listReqVO);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryServiceImplTest b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryServiceImplTest
new file mode 100644
index 000000000..efb70fd33
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/java/InfraCategoryServiceImplTest
@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.infra.service.demo;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
+import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraCategoryDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraCategoryMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link InfraCategoryServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(InfraCategoryServiceImpl.class)
+public class InfraCategoryServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private InfraCategoryServiceImpl categoryService;
+
+ @Resource
+ private InfraCategoryMapper categoryMapper;
+
+ @Test
+ public void testCreateCategory_success() {
+ // 准备参数
+ InfraCategorySaveReqVO createReqVO = randomPojo(InfraCategorySaveReqVO.class).setId(null);
+
+ // 调用
+ Long categoryId = categoryService.createCategory(createReqVO);
+ // 断言
+ assertNotNull(categoryId);
+ // 校验记录的属性是否正确
+ InfraCategoryDO category = categoryMapper.selectById(categoryId);
+ assertPojoEquals(createReqVO, category, "id");
+ }
+
+ @Test
+ public void testUpdateCategory_success() {
+ // mock 数据
+ InfraCategoryDO dbCategory = randomPojo(InfraCategoryDO.class);
+ categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ InfraCategorySaveReqVO updateReqVO = randomPojo(InfraCategorySaveReqVO.class, o -> {
+ o.setId(dbCategory.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ categoryService.updateCategory(updateReqVO);
+ // 校验是否更新正确
+ InfraCategoryDO category = categoryMapper.selectById(updateReqVO.getId()); // 获取最新的
+ assertPojoEquals(updateReqVO, category);
+ }
+
+ @Test
+ public void testUpdateCategory_notExists() {
+ // 准备参数
+ InfraCategorySaveReqVO updateReqVO = randomPojo(InfraCategorySaveReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> categoryService.updateCategory(updateReqVO), CATEGORY_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteCategory_success() {
+ // mock 数据
+ InfraCategoryDO dbCategory = randomPojo(InfraCategoryDO.class);
+ categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbCategory.getId();
+
+ // 调用
+ categoryService.deleteCategory(id);
+ // 校验数据不存在了
+ assertNull(categoryMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteCategory_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> categoryService.deleteCategory(id), CATEGORY_NOT_EXISTS);
+ }
+
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGetCategoryList() {
+ // mock 数据
+ InfraCategoryDO dbCategory = randomPojo(InfraCategoryDO.class, o -> { // 等会查询到
+ o.setName(null);
+ });
+ categoryMapper.insert(dbCategory);
+ // 测试 name 不匹配
+ categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName(null)));
+ // 准备参数
+ InfraCategoryListReqVO reqVO = new InfraCategoryListReqVO();
+ reqVO.setName(null);
+
+ // 调用
+ List list = categoryService.getCategoryList(reqVO);
+ // 断言
+ assertEquals(1, list.size());
+ assertPojoEquals(dbCategory, list.get(0));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/js/category b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/js/category
new file mode 100644
index 000000000..1e6ffdcea
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/js/category
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 创建分类
+export function createCategory(data) {
+ return request({
+ url: '/infra/category/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 更新分类
+export function updateCategory(data) {
+ return request({
+ url: '/infra/category/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除分类
+export function deleteCategory(id) {
+ return request({
+ url: '/infra/category/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 获得分类
+export function getCategory(id) {
+ return request({
+ url: '/infra/category/get?id=' + id,
+ method: 'get'
+ })
+}
+
+// 获得分类列表
+export function getCategoryList(params) {
+ return request({
+ url: '/infra/category/list',
+ method: 'get',
+ params
+ })
+}
+// 导出分类 Excel
+export function exportCategoryExcel(params) {
+ return request({
+ url: '/infra/category/export-excel',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/sql/h2 b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/sql/h2
new file mode 100644
index 000000000..4141766cf
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/sql/h2
@@ -0,0 +1,10 @@
+-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
+CREATE TABLE IF NOT EXISTS "infra_category" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar NOT NULL,
+ "description" bigint NOT NULL,
+ PRIMARY KEY ("id")
+) COMMENT '分类表';
+
+-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
+DELETE FROM "infra_category";
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/sql/sql b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/sql/sql
new file mode 100644
index 000000000..81409488a
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/sql/sql
@@ -0,0 +1,55 @@
+-- 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '分类管理', '', 2, 0, 888,
+ 'category', '', 'infra/demo/index', 0, 'InfraCategory'
+);
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '分类查询', 'infra:category:query', 3, 1, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '分类创建', 'infra:category:create', 3, 2, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '分类更新', 'infra:category:update', 3, 3, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '分类删除', 'infra:category:delete', 3, 4, @parentId,
+ '', '', '', 0
+);
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '分类导出', 'infra:category:export', 3, 5, @parentId,
+ '', '', '', 0
+);
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/vue/CategoryForm b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/vue/CategoryForm
new file mode 100644
index 000000000..7fa06e8cf
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/vue/CategoryForm
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/vue/index b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/vue/index
new file mode 100644
index 000000000..88da68254
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/vue/index
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/xml/InfraCategoryMapper b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/xml/InfraCategoryMapper
new file mode 100644
index 000000000..025ac8507
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree/xml/InfraCategoryMapper
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
index d6c5fb016..24edc65fa 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
@@ -7,11 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialCl
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
-import jakarta.annotation.Resource;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
+import jakarta.annotation.Resource;
+
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;