Преглед на файлове

feat 开心中文报名相关接口

classic_blue преди 1 седмица
родител
ревизия
b1ff81f902
променени са 29 файла, в които са добавени 1613 реда и са изтрити 0 реда
  1. 6 0
      edu-travel-service/edu-travel-service-education/Dockerfile
  2. 200 0
      edu-travel-service/edu-travel-service-education/pom.xml
  3. 16 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/EducationApplication.java
  4. 32 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/MyAccessDeniedHandler.java
  5. 31 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/MyAuthenticationEntryPoint.java
  6. 39 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/ResourceServerConfig.java
  7. 48 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/TokenConfig.java
  8. 21 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/WebSecurityConfig.java
  9. 23 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/WebServerFactoryConfigs.java
  10. 65 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/dto/AddHappyEntryDto.java
  11. 60 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/dto/HappyEntryDto.java
  12. 90 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/entity/HappyEntry.java
  13. 18 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/entity/dict/CourseTypeDict.java
  14. 42 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/exception/GlobalExceptionHandler.java
  15. 61 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/filter/TokenAuthenticationFilter.java
  16. 10 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/mapper/HappyEntryMapper.java
  17. 26 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/service/HappyEntryService.java
  18. 116 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/service/impl/HappyEntryServiceImpl.java
  19. 19 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/ObjectUtils.java
  20. 28 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/PageUtil.java
  21. 36 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/RedisUtil.java
  22. 16 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/TokenData.java
  23. 161 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/TreeUtils.java
  24. 63 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/vo/HappyEntryVo.java
  25. 107 0
      edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/web/HappyEntryController.java
  26. 34 0
      edu-travel-service/edu-travel-service-education/src/main/resources/bootstrap-dev.yml
  27. 28 0
      edu-travel-service/edu-travel-service-education/src/main/resources/bootstrap-prod.yml
  28. 9 0
      edu-travel-service/edu-travel-service-education/src/main/resources/bootstrap.yml
  29. 208 0
      edu-travel-service/edu-travel-service-education/src/main/resources/mapper/HappyEntryMapper.xml

+ 6 - 0
edu-travel-service/edu-travel-service-education/Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:8-jdk-slim
+
+WORKDIR /opt
+COPY ./target/edu-travel-service-debezium-1.0-SNAPSHOT.jar  edu-travel-service-debezium-1.0-SNAPSHOT.jar
+EXPOSE 10010
+ENTRYPOINT ["nohup","java", "-jar","-server","-Xms2048m","-Xmx2048m","edu-travel-service-debezium-1.0-SNAPSHOT.jar",">","edu-travel-service-debezium-1.0-SNAPSHOT.log","&"]

+ 200 - 0
edu-travel-service/edu-travel-service-education/pom.xml

@@ -0,0 +1,200 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>edu.travel</groupId>
+        <artifactId>edu-travel-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>edu-travel-service-education</artifactId>
+    <packaging>jar</packaging>
+
+    <name>edu-travel-service-education</name>
+    <url>http://maven.apache.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-zipkin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-oauth2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-transport-simple-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-annotation-aspectj</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-datasource-nacos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-web-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-remote-tenant</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-common-util</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-model-base</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-common-datasource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-common-constant</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-common-core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-common-cache</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+    </dependencies>
+    <profiles>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <env>dev</env>
+                <versionCode>${version}-SNAPSHOT</versionCode>
+                <spark.compiler.score>compile</spark.compiler.score>
+            </properties>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <env>prod</env>
+                <versionCode>${version}</versionCode>
+                <spark.compiler.score>provided</spark.compiler.score>
+            </properties>
+        </profile>
+    </profiles>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <configuration>
+                    <delimiters>@</delimiters>
+                    <useDefaultDelimiters>false</useDefaultDelimiters>
+                </configuration>
+            </plugin>
+            <!-- 打包插件 -->
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <!-- maven 打包时跳过测试 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <!--开启过滤,用指定的参数替换directory下的文件中的参数-->
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+</project>

+ 16 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/EducationApplication.java

@@ -0,0 +1,16 @@
+package edu.travel;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+@MapperScan("edu.travel.education.mapper")
+public class EducationApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(EducationApplication.class, args);
+    }
+
+}

+ 32 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/MyAccessDeniedHandler.java

@@ -0,0 +1,32 @@
+package edu.travel.education.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MyAccessDeniedHandler  implements AccessDeniedHandler {
+    @Override
+    public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
+        resp.setContentType("application/json;charset=UTF-8");
+        Map map = new HashMap();
+        map.put("code", "450");
+        map.put("message", e.getMessage());
+        map.put("path", req.getServletPath());
+        map.put("timestamp", String.valueOf(System.currentTimeMillis()));
+        resp.setContentType("application/json");
+        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.writeValue(resp.getOutputStream(), map);
+        } catch (Exception ex) {
+            throw new ServletException();
+        }
+    }
+}

+ 31 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/MyAuthenticationEntryPoint.java

@@ -0,0 +1,31 @@
+package edu.travel.education.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
+    @Override
+    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
+        Map map = new HashMap();
+        map.put("code", "451");
+        map.put("message", "无权限访问");
+        map.put("path", httpServletRequest.getServletPath());
+        map.put("timestamp", String.valueOf(System.currentTimeMillis()));
+        httpServletResponse.setContentType("application/json");
+        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            mapper.writeValue(httpServletResponse.getOutputStream(), map);
+        } catch (Exception ex) {
+            throw new ServletException();
+        }
+    }
+}

+ 39 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/ResourceServerConfig.java

@@ -0,0 +1,39 @@
+package edu.travel.education.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
+import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
+import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
+import org.springframework.security.oauth2.provider.token.TokenStore;
+
+@Configuration
+@EnableResourceServer
+public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
+    private static  final String RESOURCE_ID = "admin";
+
+    @Autowired
+    private TokenStore tokenStore;
+
+    @Override
+    public void configure(ResourceServerSecurityConfigurer resources) {
+        resources.resourceId(RESOURCE_ID)//资源 id
+                .tokenStore(tokenStore)
+                .authenticationEntryPoint(new MyAuthenticationEntryPoint())
+                .accessDeniedHandler(new MyAccessDeniedHandler())
+//                .tokenServices(tokenService())//验证令牌的服务
+                .stateless(true);
+    }
+
+    @Override
+    public void configure(HttpSecurity http) throws Exception {
+
+        http
+                .authorizeRequests()
+                .antMatchers("/**").permitAll()
+                .and().csrf().disable()
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+    }
+}

+ 48 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/TokenConfig.java

@@ -0,0 +1,48 @@
+package edu.travel.education.config;
+
+import edu.travel.RSAUtill;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.password.NoOpPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.provider.token.TokenStore;
+import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
+
+import java.security.PrivateKey;
+
+@Configuration
+public class TokenConfig {
+    @Value("${OAUTH_KEY}")
+    private String key;
+    @Value("${PRIVATE_KEY}")
+    private String privateKey;
+    @Bean
+    public PasswordEncoder passwordEncoder(){
+        return NoOpPasswordEncoder.getInstance();
+    }
+    @Bean
+    public TokenStore tokenStore() {
+        //JWT令牌存储方案
+        return new JwtTokenStore(accessTokenConverter());
+    }
+
+    @Bean
+    public JwtAccessTokenConverter accessTokenConverter() {
+        try {
+            PrivateKey privateKeyFromString = RSAUtill.getPrivateKeyFromString(privateKey);
+            String decrypt = RSAUtill.decrypt(key, privateKeyFromString);
+            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
+            //对称秘钥,资源服务器使用该秘钥来验证
+//        converter.setKeyPair(keyPair());
+            converter.setSigningKey(decrypt);
+            return converter;
+
+        }catch (Exception e){
+            e.printStackTrace();
+            return null;
+        }
+
+    }
+}

+ 21 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/WebSecurityConfig.java

@@ -0,0 +1,21 @@
+package edu.travel.education.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+    //安全拦截机制(最重要)
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.csrf().disable()
+                .authorizeRequests()
+                .anyRequest().authenticated()
+        ;
+
+
+    }
+}

+ 23 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/config/WebServerFactoryConfigs.java

@@ -0,0 +1,23 @@
+package edu.travel.education.config;
+
+import org.apache.catalina.connector.Connector;
+import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class WebServerFactoryConfigs {
+    @Bean
+    public ConfigurableServletWebServerFactory webServerFactory() {
+        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
+        factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
+            @Override
+            public void customize(Connector connector) {
+                connector.setProperty("relaxedQueryChars", "|{}[]");
+            }
+        });
+        return factory;
+    }
+}

+ 65 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/dto/AddHappyEntryDto.java

@@ -0,0 +1,65 @@
+package edu.travel.education.dto;
+
+/**
+ * AddHappyEntryDto 类。
+ * <p>
+ * 描述:
+ *
+ * @author huangwenwen
+ * @date 2025/3/5
+ */
+
+
+import edu.travel.po.PagePO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 开心中文报名表
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AddHappyEntryDto{
+
+    /**
+     * 姓
+     */
+    private String surname;
+
+    /**
+     * 名
+     */
+    private String name;
+
+    /**
+     * 电话号码
+     */
+    private String phone;
+
+    /**
+     * 国家区号
+     */
+    private String areaCode;
+
+    /**
+     * 课程类型
+     */
+    private Integer courseType;
+
+    /**
+     * 年龄
+     */
+    private Integer age;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+    /**
+     * 项目code
+     */
+    private String project;
+}

+ 60 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/dto/HappyEntryDto.java

@@ -0,0 +1,60 @@
+package edu.travel.education.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import edu.travel.annotation.LinkConst;
+import edu.travel.education.entity.dict.CourseTypeDict;
+import edu.travel.po.PagePO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 开心中文报名表
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class HappyEntryDto extends PagePO {
+    /**
+    * 主键id
+    */
+    private Long id;
+
+    /**
+    * 姓
+    */
+    private String surname;
+
+    /**
+    * 名
+    */
+    private String name;
+
+    /**
+    * 电话号码
+    */
+    private String phone;
+
+    /**
+    * 国家区号
+    */
+    private String areaCode;
+
+    /**
+    * 课程类型
+    */
+    private Integer courseType;
+
+    /**
+    * 年龄
+    */
+    private Integer age;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+}

+ 90 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/entity/HappyEntry.java

@@ -0,0 +1,90 @@
+package edu.travel.education.entity;
+
+import java.util.Date;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import edu.travel.annotation.LinkConst;
+import edu.travel.education.entity.dict.CourseTypeDict;
+import edu.travel.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 开心中文报名表
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class HappyEntry extends BaseEntity {
+    /**
+    * 主键id
+    */
+
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+    * 姓
+    */
+    @NotNull
+    @TableField(value = "surname")
+    private String surname;
+
+    /**
+    * 名
+    */
+    @NotNull
+    @TableField(value = "name")
+    private String name;
+
+    /**
+    * 电话号码
+    */
+    @NotNull
+    @TableField(value = "phone")
+    private String phone;
+
+    /**
+    * 国家区号
+    */
+    @NotNull
+    @TableField(value = "area_code")
+    private String areaCode;
+
+    /**
+    * 课程类型
+    */
+    @NotNull
+    @TableField(value = "course_type")
+    private Integer courseType;
+
+    /**
+    * 年龄
+    */
+    @NotNull
+    @TableField(value = "age")
+    private Integer age;
+
+    /**
+     * 邮箱
+     */
+    @NotNull
+    @TableField(value = "email")
+    private String email;
+
+    /**
+     * 课程类型字典
+     */
+    @TableField(exist = false)
+    @LinkConst(fieldName = "courseType",clazz = CourseTypeDict.class)
+    private Map CodeCourseTypeDict;
+
+
+}

+ 18 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/entity/dict/CourseTypeDict.java

@@ -0,0 +1,18 @@
+package edu.travel.education.entity.dict;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CourseTypeDict {
+
+    private final  Map<Integer,String> courseTypeMap = new HashMap<Integer,String>();
+
+    public CourseTypeDict(){
+        courseTypeMap.put(1,"初级");
+        courseTypeMap.put(2,"中级");
+        courseTypeMap.put(3,"高级");
+    }
+    public String getValue(Integer key){
+        return courseTypeMap.containsKey(key) ?  courseTypeMap.get(key) : null;
+    }
+}

+ 42 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/exception/GlobalExceptionHandler.java

@@ -0,0 +1,42 @@
+package edu.travel.education.exception;
+
+import edu.travel.emun.ResponseCode;
+import edu.travel.exception.BaseException;
+import edu.travel.resp.BaseResponse;
+import edu.travel.resp.PageResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * GlobalExceptionHandler 类。
+ * <p>
+ * 描述:
+ *
+ * @author huangwenwen
+ * @date 2025/2/11
+ */
+
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(BaseException.class)
+    public BaseResponse<Object> handleUserInfoException(BaseException e){
+        e.printStackTrace();
+        return  PageResponse.out(e.getCode(),e.getMessage(),null);
+    }
+
+    @ExceptionHandler(IllegalArgumentException.class)
+    public BaseResponse<Object> handleException(IllegalArgumentException e){
+        e.printStackTrace();
+        return PageResponse.out(ResponseCode.LOGIC_ERROR.getCode(), e.getMessage(),null);
+    }
+
+    @ExceptionHandler(Throwable.class)
+    public BaseResponse<Object> handleException(Exception e){
+        e.printStackTrace();
+        return PageResponse.out(ResponseCode.ERROR.getCode(),e.getMessage(),null);
+    }
+
+}

+ 61 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/filter/TokenAuthenticationFilter.java

@@ -0,0 +1,61 @@
+package edu.travel.education.filter;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import edu.travel.EncryptUtil;
+import edu.travel.entity.EduTenantPO;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class TokenAuthenticationFilter extends OncePerRequestFilter {
+    @Autowired
+    private RedisTemplate redisTemplate;
+    @Override
+    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+        String token = httpServletRequest.getHeader("token");
+//token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYWRtaW4iXSwiZXhwIjoxNzQwMjAyNTcyLCJ1c2VyX25hbWUiOiIxNTk5ODk1NzA3NCIsImp0aSI6IjM0M2JjNGUzLTk5ZjMtNGE4Zi1iMmIxLTI1ZjRkMzBmNmJmYyIsImNsaWVudF9pZCI6ImFkbWluIiwic2NvcGUiOlsic2VydmVyIl19.MYoFq8gg832DQMX-wVMLN0JlIaWeuQZvl1z1NUNFspQ";
+        if (StringUtils.isNotBlank(token)){
+            String json = EncryptUtil.decodeUTF8StringBase64(token);
+            //将token转成json对象
+            JSONObject jsonObject = JSON.parseObject(json);
+            //用户身份信息
+            String username  = jsonObject.getString("principal");
+            Object object = redisTemplate.opsForValue().get(username + "_info");
+            if (object == null){
+                JSONObject resultObject = new JSONObject();
+                resultObject.put("code",401);
+                resultObject.put("msg","not found user");
+                resultObject.put("data",null);
+                httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+                httpServletResponse.setContentType("application/json;charset=utf-8");
+                httpServletResponse.getWriter().write(resultObject.toJSONString());
+                return;
+            }
+            EduTenantPO eduTenant = JSON.parseObject(object.toString(), EduTenantPO.class);
+            //用户权限
+            JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
+            String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
+            //将用户信息和权限填充 到用户身份token对象中
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(eduTenant, null, AuthorityUtils.createAuthorityList(authorities));
+            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
+            //将authenticationToken填充到安全上下文
+            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        }
+        filterChain.doFilter(httpServletRequest,httpServletResponse);
+    }
+}

+ 10 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/mapper/HappyEntryMapper.java

@@ -0,0 +1,10 @@
+package edu.travel.education.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import edu.travel.education.entity.HappyEntry;
+import org.apache.ibatis.annotations.Mapper;
+
+
+public interface HappyEntryMapper extends BaseMapper<HappyEntry> {
+
+}

+ 26 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/service/HappyEntryService.java

@@ -0,0 +1,26 @@
+package edu.travel.education.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import edu.travel.education.dto.AddHappyEntryDto;
+import edu.travel.education.dto.HappyEntryDto;
+import edu.travel.education.entity.HappyEntry;
+import edu.travel.education.vo.HappyEntryVo;
+
+import java.util.Set;
+
+public interface HappyEntryService extends IService<HappyEntry> {
+
+    HappyEntryVo addHappyEntry(AddHappyEntryDto happyEntryDto);
+
+    HappyEntryVo getHappyEntryById(Long id);
+
+    HappyEntryVo UpdateHappyEntryById(HappyEntryDto happyEntryDto);
+
+    void deleteHappyEntryByIds(Set<Long> set);
+
+    IPage<HappyEntry> listPageHappyEntry(HappyEntryDto happyEntryDto);
+
+    IPage<HappyEntry> dictMapPage(IPage<HappyEntry> page, LambdaQueryWrapper<HappyEntry> queryWrapper);
+}

+ 116 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/service/impl/HappyEntryServiceImpl.java

@@ -0,0 +1,116 @@
+package edu.travel.education.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import edu.travel.education.dto.AddHappyEntryDto;
+import edu.travel.education.dto.HappyEntryDto;
+import edu.travel.education.vo.HappyEntryVo;
+import edu.travel.entity.EduTenantPO;
+import edu.travel.remote.feign.mode.vo.tenant.EduTenantVo;
+import edu.travel.service.SysServiceImpl;
+import org.springframework.beans.BeanUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import edu.travel.education.mapper.HappyEntryMapper;
+import edu.travel.education.entity.HappyEntry;
+import edu.travel.education.service.HappyEntryService;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.Time;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+@Service
+public class HappyEntryServiceImpl  extends SysServiceImpl<HappyEntryMapper, HappyEntry> implements HappyEntryService{
+
+    @Autowired
+    private HappyEntryMapper happyEntryMapper;
+
+    @Override
+    @Transactional
+    public HappyEntryVo addHappyEntry(AddHappyEntryDto happyEntryDto) {
+
+        Assert.isTrue(ObjectUtil.isNotEmpty(happyEntryDto),"参数不能为空");
+        HappyEntry entity = BeanUtil.copyProperties(happyEntryDto, HappyEntry.class);
+        boolean save = this.save(entity);
+        Assert.isTrue(save,"保存失败");
+        return BeanUtil.copyProperties(entity, HappyEntryVo.class);
+    }
+
+    @Override
+    public HappyEntryVo getHappyEntryById(Long id) {
+        Assert.isTrue(ObjectUtil.isNotEmpty(id),"参数不能为空");
+        HappyEntry happyEntry = this.getOneLink(new LambdaQueryWrapper<HappyEntry>().eq(HappyEntry::getId,id));
+        return happyEntry==null?null:BeanUtil.copyProperties(happyEntry,HappyEntryVo.class);
+    }
+
+    @Override
+    @Transactional
+    public HappyEntryVo UpdateHappyEntryById(HappyEntryDto happyEntryDto) {
+
+        Assert.isTrue(ObjectUtil.isNotEmpty(happyEntryDto),"参数不能为空");
+        HappyEntry happyEntry = this.getById(happyEntryDto.getId());
+        Assert.isTrue(ObjectUtil.isNotEmpty(happyEntry),"该记录不存在");
+
+        HappyEntry entry = new HappyEntry();
+        entry.setId(happyEntryDto.getId());
+        entry.setSurname(happyEntryDto.getSurname());
+        entry.setName(happyEntryDto.getName());
+        entry.setPhone(happyEntryDto.getPhone());
+        entry.setAreaCode(happyEntryDto.getAreaCode());
+        entry.setEmail(happyEntryDto.getEmail());
+        entry.setCourseType(happyEntryDto.getCourseType());
+        entry.setAge(happyEntryDto.getAge());
+        entry.setUpdateTime(new Date());
+        EduTenantPO principal = (EduTenantPO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        entry.setUpdateUserId(principal.getId().toString());
+
+        boolean update = this.update(entry, new LambdaQueryWrapper<HappyEntry>().eq(HappyEntry::getId, happyEntryDto.getId()));
+        Assert.isTrue(update,"更新失败");
+        HappyEntry oneLink = this.getOneLink(new LambdaQueryWrapper<HappyEntry>().eq(HappyEntry::getId, entry.getId()));
+
+        return BeanUtil.copyProperties(oneLink,HappyEntryVo.class);
+    }
+
+    @Override
+    @Transactional
+    public void deleteHappyEntryByIds(Set<Long> set) {
+        Assert.isTrue(ObjectUtil.isNotEmpty(set),"参数不能为空");
+        List<HappyEntry> list = this.list(new LambdaQueryWrapper<HappyEntry>().in(HappyEntry::getId, set));
+        Assert.isTrue(ObjectUtil.isNotEmpty(list),"包含不存在的记录");
+        Assert.isTrue(ObjectUtil.equals(list.size(), set.size()),"包含不存在的记录");
+
+        boolean b = this.removeByIds(set);
+        Assert.isTrue(b,"删除失败");
+
+    }
+
+    @Override
+    @Transactional
+    public IPage<HappyEntry> listPageHappyEntry(HappyEntryDto happyEntryDto) {
+        Assert.isTrue(ObjectUtil.isNotEmpty(happyEntryDto),"参数不能为空");
+        IPage page = new Page(happyEntryDto.getCurrentPage() == null ? 1 : happyEntryDto.getCurrentPage(), happyEntryDto.getPageSize() == null ? 10 : happyEntryDto.getPageSize());
+        LambdaQueryWrapper<HappyEntry> queryWrapper = new LambdaQueryWrapper<HappyEntry>()
+                .eq(HappyEntry::getDeleteFlag, 0)
+                .eq(happyEntryDto.getCourseType() != null,HappyEntry::getCourseType, happyEntryDto.getCourseType())
+                .like(happyEntryDto.getSurname() != null, HappyEntry::getSurname, happyEntryDto.getSurname())
+                .like(happyEntryDto.getName() != null && !happyEntryDto.getName().isEmpty(), HappyEntry::getName, happyEntryDto.getName())
+                .like(happyEntryDto.getEmail() != null && !happyEntryDto.getEmail().isEmpty(), HappyEntry::getEmail, happyEntryDto.getEmail())
+                .like(happyEntryDto.getPhone() != null && !happyEntryDto.getPhone().isEmpty(), HappyEntry::getPhone, happyEntryDto.getPhone());
+        IPage<HappyEntry> happyEntryIPage = this.dictMapPage(page, queryWrapper);
+        return happyEntryIPage;
+    }
+
+    @Override
+    public IPage<HappyEntry> dictMapPage(IPage<HappyEntry> page, LambdaQueryWrapper<HappyEntry> queryWrapper) {
+        return super.getPageLink(queryWrapper,page);
+    }
+}

+ 19 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/ObjectUtils.java

@@ -0,0 +1,19 @@
+package edu.travel.education.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ObjectUtils {
+
+
+    public static <T> List<T> castList(Object obj, Class<T> clazz) {
+        List<T> result = new ArrayList<T>();
+        if (obj instanceof List<?>) {
+            for (Object o : (List<?>) obj) {
+                result.add(clazz.cast(o));
+            }
+            return result;
+        }
+        return null;
+    }
+}

+ 28 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/PageUtil.java

@@ -0,0 +1,28 @@
+package edu.travel.education.utils;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.beans.BeanUtils;
+
+public class PageUtil{
+
+    /**
+     * Page类型entity转换
+     * @param source
+     * @param type
+     * @return
+     */
+    public static <T,E> Page<E> toPageEntity(Page<T> source, Class<E> type) {
+        Page<E> target = new Page<E>();
+        BeanUtils.copyProperties(source, target);
+        target.setRecords(BeanUtil.copyToList(source.getRecords(), type));
+        return target;
+    }
+    public static <T,E> Page<E> toPageEntity(IPage<T> source, Class<E> type) {
+        Page<E> target = new Page<E>();
+        BeanUtils.copyProperties(source, target);
+        target.setRecords(BeanUtil.copyToList(source.getRecords(), type));
+        return target;
+    }
+}

+ 36 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/RedisUtil.java

@@ -0,0 +1,36 @@
+package edu.travel.education.utils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class RedisUtil {
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+    /**
+     *通过KEY 获取值
+     */
+    public String getString(String key) {
+        return stringRedisTemplate.opsForValue().get(key);
+    }
+    /**
+     * 设置KEY 和 VALUE
+     */
+    public void setString(String key, String value) {
+        stringRedisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * @param key KEY
+     * @param value 值
+     * @param expire 过期时间
+     * @param timeUnit 时间单位
+     */
+    public void setString(String key, String value,long expire,TimeUnit timeUnit) {
+        stringRedisTemplate.opsForValue().set(key, value,expire, timeUnit);
+    }
+
+}

+ 16 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/TokenData.java

@@ -0,0 +1,16 @@
+package edu.travel.education.utils;
+
+import edu.travel.entity.EduTenantPO;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class TokenData {
+
+    /**
+     *  获取用户ID
+     * @return {@link String }
+     */
+    public static String getUserId(){
+        EduTenantPO principal =(EduTenantPO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        return principal.getId().toString();
+    }
+}

+ 161 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/utils/TreeUtils.java

@@ -0,0 +1,161 @@
+package edu.travel.education.utils;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * 通用树形结构构建工具类
+ *
+ * <p><strong>功能特性:</strong></p>
+ * <ul>
+ *   <li>支持任意 Java 对象(实体类/DTO/VO)</li>
+ *   <li>自动检测循环依赖(死循环)</li>
+ *   <li>零依赖(无需继承接口或父类)</li>
+ *   <li>编译期类型安全检查</li>
+ * </ul>
+ *
+ * <p><strong>使用示例:</strong></p>
+ * <pre>{@code
+ * List<Department> tree = TreeUtils.buildTree(
+ *     departments,
+ *     Department::getId,
+ *     Department::getParentId,
+ *     Department::setChildren
+ * );
+ * }</pre>
+ */
+public class TreeUtils {
+
+    /**
+     * 构建树形结构的核心方法
+     *
+     * @param nodes          待处理的节点列表(必须包含所有节点)
+     * @param idGetter       节点ID的获取方法(方法引用,如:User::getId)
+     * @param pidGetter      父节点ID的获取方法(方法引用,如:User::getParentId)
+     * @param childrenSetter 子节点列表的设置方法(如:(node, children) -> node.setChildren(children))
+     * @param <T>            节点类型(如:User.class)
+     * @param <ID>           ID 的类型(需正确实现 equals 和 hashCode)
+     * @return 树形结构的根节点列表
+     *
+     * @throws IllegalArgumentException 如果存在以下情况:
+     *                                  <ul>
+     *                                    <li>节点列表为null</li>
+     *                                    <li>存在重复ID</li>
+     *                                    <li>ID字段为null</li>
+     *                                  </ul>
+     * @throws IllegalStateException    如果检测到循环依赖
+     */
+    public static <T, ID> List<T> buildTree(List<T> nodes,
+                                            Function<T, ID> idGetter,
+                                            Function<T, ID> pidGetter,
+                                            BiConsumer<T, List<T>> childrenSetter) {
+        // 防御性拷贝,避免修改原始数据
+        List<T> copyNodes = new ArrayList<>(Optional.ofNullable(nodes).orElseGet(ArrayList::new));
+
+        //========== 数据校验 ==========//
+        validateNodes(copyNodes, idGetter);
+
+        //========== 初始化数据结构 ==========//
+        Map<ID, T> nodeMap = new HashMap<>(copyNodes.size());
+        List<T> rootNodes = new ArrayList<>();
+
+        // 构建 id -> node 映射表
+        copyNodes.forEach(node -> nodeMap.put(idGetter.apply(node), node));
+
+        //========== 构建父子关系 ==========//
+        for (T node : copyNodes) {
+            ID currentId = idGetter.apply(node);
+            ID parentId = pidGetter.apply(node);
+
+            // 判断根节点条件(父ID为空/等于自身/不存在于Map中)
+            if (isRootNode(parentId, currentId, nodeMap)) {
+                rootNodes.add(node);
+                continue;
+            }
+
+            // 获取父节点并建立关联
+            T parent = nodeMap.get(parentId);
+            if (parent != null) {
+                linkParentAndChild(parent, node, childrenSetter);
+                checkCircularDependency(parent, currentId, idGetter, pidGetter, nodeMap);
+            }
+        }
+
+        return rootNodes;
+    }
+
+    //========== 私有方法 ==========//
+
+    /**
+     * 数据校验
+     */
+    private static <T, ID> void validateNodes(List<T> nodes, Function<T, ID> idGetter) {
+        if (nodes == null) {
+            throw new IllegalArgumentException("节点列表不能为null");
+        }
+
+        Set<ID> idSet = new HashSet<>();
+        for (T node : nodes) {
+            ID id = idGetter.apply(node);
+            if (id == null) {
+                throw new IllegalArgumentException("节点存在空ID: " + node);
+            }
+            if (idSet.contains(id)) {
+                throw new IllegalArgumentException("存在重复ID: " + id);
+            }
+            idSet.add(id);
+        }
+    }
+
+    /**
+     * 判断是否为根节点
+     */
+    private static <ID> boolean isRootNode(ID parentId, ID currentId, Map<ID, ?> nodeMap) {
+        return parentId == null
+                || parentId.equals(currentId)
+                || !nodeMap.containsKey(parentId);
+    }
+
+    /**
+     * 建立父子关联关系
+     */
+    private static <T> void linkParentAndChild(T parent,
+                                               T child,
+                                               BiConsumer<T, List<T>> childrenSetter) {
+        List<T> children = new ArrayList<>();
+        childrenSetter.accept(parent, children);
+        children.add(child);
+    }
+
+    /**
+     * 循环依赖检测(核心安全机制)
+     */
+    private static <T, ID> void checkCircularDependency(T parent,
+                                                        ID childId,
+                                                        Function<T, ID> idGetter,
+                                                        Function<T, ID> pidGetter,
+                                                        Map<ID, T> nodeMap) {
+        Set<ID> visited = new HashSet<>();
+        T current = parent;
+
+        while (current != null) {
+            ID currentParentId = pidGetter.apply(current);
+            if (currentParentId == null) break;
+
+            // 发现直接循环(A→B→A)
+            if (currentParentId.equals(childId)) {
+                throw new IllegalStateException("检测到循环依赖:节点 "
+                        + idGetter.apply(current) + " → " + childId);
+            }
+
+            // 发现间接循环(A→B→C→A)
+            if (visited.contains(currentParentId)) {
+                throw new IllegalStateException("检测到循环依赖路径:" + visited);
+            }
+
+            visited.add(currentParentId);
+            current = nodeMap.get(currentParentId);
+        }
+    }
+}

+ 63 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/vo/HappyEntryVo.java

@@ -0,0 +1,63 @@
+package edu.travel.education.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import edu.travel.annotation.LinkConst;
+import edu.travel.education.entity.dict.CourseTypeDict;
+import edu.travel.po.PagePO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 开心中文报名表
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class HappyEntryVo extends PagePO {
+    /**
+    * 主键id
+    */
+    private String id;
+
+    /**
+    * 姓
+    */
+    private String surname;
+
+    /**
+    * 名
+    */
+    private String name;
+
+    /**
+    * 电话号码
+    */
+    private String phone;
+
+    /**
+    * 国家区号
+    */
+    private String areaCode;
+
+    /**
+    * 课程类型
+    */
+    private Integer courseType;
+
+    /**
+    * 年龄
+    */
+    private Integer age;
+
+    private String email;
+    /**
+     * 课程类型字典
+     */
+    @TableField(exist = false)
+    @LinkConst(fieldName = "courseType",clazz = CourseTypeDict.class)
+    private Map CodeCourseTypeDict;
+}

+ 107 - 0
edu-travel-service/edu-travel-service-education/src/main/java/edu/travel/education/web/HappyEntryController.java

@@ -0,0 +1,107 @@
+package edu.travel.education.web;
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import edu.travel.education.dto.AddHappyEntryDto;
+import edu.travel.education.dto.HappyEntryDto;
+import edu.travel.education.entity.HappyEntry;
+import edu.travel.education.service.impl.HappyEntryServiceImpl;
+import edu.travel.education.vo.HappyEntryVo;
+import edu.travel.resp.BaseResponse;
+import edu.travel.resp.PageResponse;
+import edu.travel.web.BaseController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+* 开心中文报名表(happy_entry)表控制层
+*
+* @author huangwen
+*/
+@RestController
+@RequestMapping("/happyEntry")
+public class HappyEntryController extends BaseController<HappyEntry> {
+    @Autowired
+    private HappyEntryServiceImpl happyEntryService;
+
+    /**
+     * 添加报名表
+     * @param happyEntryDto
+     * @return
+     */
+    @PostMapping("/addHappyEntry")
+//    @PreAuthorize("permitAll()")
+    public BaseResponse<HappyEntryVo> addHappyEntry(@RequestBody AddHappyEntryDto happyEntryDto){
+        HappyEntryVo vo = happyEntryService.addHappyEntry(happyEntryDto);
+        return new BaseResponse<>(200,"success",vo);
+    }
+
+    /**
+     * 根据id查询报名表详情
+     * @param id
+     * @return
+     */
+   @GetMapping("/getHappyEntryById")
+   @PreAuthorize("hasRole('超级管理员')")
+    public BaseResponse<HappyEntryVo> getHappyEntryById(Long id){
+        HappyEntryVo vo = happyEntryService.getHappyEntryById(id);
+        return new BaseResponse<>(200,"success",vo);
+    }
+
+    /**
+     * 根据id修改报名表
+     * @param happyEntryDto
+     * @return
+     */
+    @PostMapping("/UpdateHappyEntryById")
+    @PreAuthorize("hasRole('超级管理员')")
+    public BaseResponse<HappyEntryVo> UpdateHappyEntryById(@RequestBody HappyEntryDto happyEntryDto){
+        HappyEntryVo vo = happyEntryService.UpdateHappyEntryById(happyEntryDto);
+        return new BaseResponse<>(200,"success",vo);
+    }
+
+    /**
+     * 根据id删除报名表
+     * @param id
+     * @return
+     */
+    @PostMapping("/deleteHappyEntryById")
+    @PreAuthorize("hasRole('超级管理员')")
+    public BaseResponse<HappyEntryVo> deleteHappyEntryById(@RequestParam Long id){
+        Set<Long> set = new HashSet<>();
+        set.add(id);
+        happyEntryService.deleteHappyEntryByIds(set);
+        return new BaseResponse<>(200,"success",null);
+    }
+
+    /**
+     * 根据ids批量删除报名表
+     * @param ids
+     * @return
+     */
+    @PostMapping("/deleteHappyEntryByIds")
+    @PreAuthorize("hasRole('超级管理员')")
+    public BaseResponse<HappyEntryVo> deleteHappyEntryById(@RequestBody List<Long> ids){
+        Set<Long> set = new HashSet<Long>(ids);
+        happyEntryService.deleteHappyEntryByIds(set);
+        return new BaseResponse<>(200,"success",null);
+    }
+
+    /**
+     * 分页查询报名表
+     * @return
+     */
+    @GetMapping("/listPageHappyEntry")
+    @PreAuthorize("hasRole('超级管理员')")
+    public BaseResponse<List<HappyEntryVo>> listPageHappyEntry( HappyEntryDto happyEntryDto){
+        IPage<HappyEntry> page = happyEntryService.listPageHappyEntry(happyEntryDto);
+        List<HappyEntryVo> happyEntryVos = BeanUtil.copyToList(page.getRecords(), HappyEntryVo.class);
+        return  PageResponse.out(200,"success",happyEntryVos,(int) page.getTotal(),(int)page.getSize());
+    }
+
+}

+ 34 - 0
edu-travel-service/edu-travel-service-education/src/main/resources/bootstrap-dev.yml

@@ -0,0 +1,34 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: localhost:8848,localhost:8849,localhost:8858
+        namespace: edu-@env@
+      config:
+        file-extension: yaml
+        server-addr: localhost:8848,localhost:8849,localhost:8858
+        refresh-enabled: true
+        namespace: edu-@env@
+        shared-configs:
+          - data-id: mybatis-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: reids-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: datasource-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: system-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: zipkin.yml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: seata.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: sentinel_tenant.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+

+ 28 - 0
edu-travel-service/edu-travel-service-education/src/main/resources/bootstrap-prod.yml

@@ -0,0 +1,28 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+#        server-addr: localhost:8848,localhost:8849,localhost:8858
+        server-addr: 0.0.0.0
+        namespace: edu-@env@
+      config:
+        file-extension: yaml
+        server-addr: localhost:8848,localhost:8849,localhost:8858
+        refresh-enabled: true
+        namespace: edu-@env@
+        shared-configs:
+          - data-id: mybatis-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: reids-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: datasource-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: system-config-dev.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+          - data-id: zipkin.yml
+            group: DEFAULT_GROUP
+            refresh: true

+ 9 - 0
edu-travel-service/edu-travel-service-education/src/main/resources/bootstrap.yml

@@ -0,0 +1,9 @@
+server:
+  port: 10005
+spring:
+  application:
+    name: education-@env@
+  profiles:
+    active: @env@
+  main:
+    allow-bean-definition-overriding: true

+ 208 - 0
edu-travel-service/edu-travel-service-education/src/main/resources/mapper/HappyEntryMapper.xml

@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="edu.travel.education.mapper.HappyEntryMapper">
+  <resultMap id="BaseResultMap" type="edu.travel.education.entity.HappyEntry">
+    <!--@mbg.generated-->
+    <!--@Table happy_entry-->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="surname" jdbcType="VARCHAR" property="surname" />
+    <result column="name" jdbcType="VARCHAR" property="name" />
+    <result column="phone" jdbcType="VARCHAR" property="phone" />
+    <result column="area_code" jdbcType="VARCHAR" property="areaCode" />
+    <result column="course_type" jdbcType="INTEGER" property="courseType" />
+    <result column="age" jdbcType="INTEGER" property="age" />
+    <result column="email" jdbcType="VARCHAR" property="email" />
+    <result column="project" jdbcType="VARCHAR" property="project" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="create_user_id" jdbcType="BIGINT" property="createUserId" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="update_user_id" jdbcType="BIGINT" property="updateUserId" />
+    <result column="delete_flag" jdbcType="INTEGER" property="deleteFlag" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--@mbg.generated-->
+    id, surname, `name`, phone, area_code, course_type, age, email, project, create_time, 
+    create_user_id, update_time, update_user_id, delete_flag
+  </sql>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+    <!--@mbg.generated-->
+    select 
+    <include refid="Base_Column_List" />
+    from happy_entry
+    where id = #{id,jdbcType=BIGINT}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+    <!--@mbg.generated-->
+    delete from happy_entry
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <insert id="insert" parameterType="edu.travel.education.entity.HappyEntry">
+    <!--@mbg.generated-->
+    insert into happy_entry (id, surname, `name`, 
+      phone, area_code, course_type, 
+      age, email, project, 
+      create_time, create_user_id, update_time, 
+      update_user_id, delete_flag)
+    values (#{id,jdbcType=BIGINT}, #{surname,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, 
+      #{phone,jdbcType=VARCHAR}, #{areaCode,jdbcType=VARCHAR}, #{courseType,jdbcType=INTEGER}, 
+      #{age,jdbcType=INTEGER}, #{email,jdbcType=VARCHAR}, #{project,jdbcType=VARCHAR}, 
+      #{createTime,jdbcType=TIMESTAMP}, #{createUserId,jdbcType=BIGINT}, #{updateTime,jdbcType=TIMESTAMP}, 
+      #{updateUserId,jdbcType=BIGINT}, #{deleteFlag,jdbcType=INTEGER})
+  </insert>
+  <insert id="insertSelective" parameterType="edu.travel.education.entity.HappyEntry">
+    <!--@mbg.generated-->
+    insert into happy_entry
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="surname != null">
+        surname,
+      </if>
+      <if test="name != null">
+        `name`,
+      </if>
+      <if test="phone != null">
+        phone,
+      </if>
+      <if test="areaCode != null">
+        area_code,
+      </if>
+      <if test="courseType != null">
+        course_type,
+      </if>
+      <if test="age != null">
+        age,
+      </if>
+      <if test="email != null">
+        email,
+      </if>
+      <if test="project != null">
+        project,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="createUserId != null">
+        create_user_id,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+      <if test="updateUserId != null">
+        update_user_id,
+      </if>
+      <if test="deleteFlag != null">
+        delete_flag,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=BIGINT},
+      </if>
+      <if test="surname != null">
+        #{surname,jdbcType=VARCHAR},
+      </if>
+      <if test="name != null">
+        #{name,jdbcType=VARCHAR},
+      </if>
+      <if test="phone != null">
+        #{phone,jdbcType=VARCHAR},
+      </if>
+      <if test="areaCode != null">
+        #{areaCode,jdbcType=VARCHAR},
+      </if>
+      <if test="courseType != null">
+        #{courseType,jdbcType=INTEGER},
+      </if>
+      <if test="age != null">
+        #{age,jdbcType=INTEGER},
+      </if>
+      <if test="email != null">
+        #{email,jdbcType=VARCHAR},
+      </if>
+      <if test="project != null">
+        #{project,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="createUserId != null">
+        #{createUserId,jdbcType=BIGINT},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateUserId != null">
+        #{updateUserId,jdbcType=BIGINT},
+      </if>
+      <if test="deleteFlag != null">
+        #{deleteFlag,jdbcType=INTEGER},
+      </if>
+    </trim>
+  </insert>
+  <update id="updateByPrimaryKeySelective" parameterType="edu.travel.education.entity.HappyEntry">
+    <!--@mbg.generated-->
+    update happy_entry
+    <set>
+      <if test="surname != null">
+        surname = #{surname,jdbcType=VARCHAR},
+      </if>
+      <if test="name != null">
+        `name` = #{name,jdbcType=VARCHAR},
+      </if>
+      <if test="phone != null">
+        phone = #{phone,jdbcType=VARCHAR},
+      </if>
+      <if test="areaCode != null">
+        area_code = #{areaCode,jdbcType=VARCHAR},
+      </if>
+      <if test="courseType != null">
+        course_type = #{courseType,jdbcType=INTEGER},
+      </if>
+      <if test="age != null">
+        age = #{age,jdbcType=INTEGER},
+      </if>
+      <if test="email != null">
+        email = #{email,jdbcType=VARCHAR},
+      </if>
+      <if test="project != null">
+        project = #{project,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="createUserId != null">
+        create_user_id = #{createUserId,jdbcType=BIGINT},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateUserId != null">
+        update_user_id = #{updateUserId,jdbcType=BIGINT},
+      </if>
+      <if test="deleteFlag != null">
+        delete_flag = #{deleteFlag,jdbcType=INTEGER},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="edu.travel.education.entity.HappyEntry">
+    <!--@mbg.generated-->
+    update happy_entry
+    set surname = #{surname,jdbcType=VARCHAR},
+      `name` = #{name,jdbcType=VARCHAR},
+      phone = #{phone,jdbcType=VARCHAR},
+      area_code = #{areaCode,jdbcType=VARCHAR},
+      course_type = #{courseType,jdbcType=INTEGER},
+      age = #{age,jdbcType=INTEGER},
+      email = #{email,jdbcType=VARCHAR},
+      project = #{project,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      create_user_id = #{createUserId,jdbcType=BIGINT},
+      update_time = #{updateTime,jdbcType=TIMESTAMP},
+      update_user_id = #{updateUserId,jdbcType=BIGINT},
+      delete_flag = #{deleteFlag,jdbcType=INTEGER}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>