亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

Spring Boot企業級開發教程:從入門到實踐

標簽:
SpringBoot
概述

Spring Boot企业级开发教程介绍了从环境搭建到项目配置的全过程,包括Java环境安装、IDE配置、Maven或Gradle的使用以及创建第一个Spring Boot应用。文章还详细讲解了Spring Boot的核心概念、数据库集成、Web应用开发,以及安全与权限管理等方面的知识。此外,文章还涵盖了日志与监控的配置,帮助开发者全面掌握Spring Boot企业级开发。

Spring Boot企业级开发教程:从入门到实践
Spring Boot简介与环境搭建

Spring Boot概述

Spring Boot 是由 Pivotal 团队提供的一个基于 Spring 框架的快速开发框架。它支持约定优于配置的原则,旨在简化新 Spring 应用的初始搭建以及开发过程。Spring Boot 自动配置了许多常用的开发场景,如数据库连接、Web 服务器、安全性和缓存等,开发者只需添加必要的依赖和配置即可快速启动项目。

开发环境搭建

要开始使用 Spring Boot 进行开发,需要先搭建好开发环境。

  1. Java 环境安装
    • 确保安装了 Java 开发工具包 (JDK),版本要求在 Java 8 及以上。
    • 检查 Java 版本:
      java -version
    • 设置环境变量:
      export JAVA_HOME=/path/to/jdk
      export PATH=$JAVA_HOME/bin:$PATH
  2. IDE 安装

    • 推荐使用 IntelliJ IDEA 或 Eclipse。
    • 安装完成后,确保 IDE 支持 Spring Boot 插件。
  3. Maven 或 Gradle 安装

    • Maven 是一个项目管理和构建工具,用于管理和构建 Java 项目。
    • Gradle 是基于 Groovy 语言的构建工具,与 Maven 相比提供了更为灵活的构建方式。
    • 安装完成后,在 IDE 中配置好 Maven 或 Gradle 环境。
  4. 搭建 Spring Boot 项目
    • 使用 Spring Initializr 创建新的 Spring Boot 项目。
    • 通过 IDE 插件或者访问 Spring Initializr 网站生成项目,并下载到本地。

项目创建与配置

创建一个新的 Spring Boot 项目:

  1. 使用 Maven 或 Gradle 创建项目:

    mvn archetype:generate -DgroupId=com.example -DartifactId=hello-spring-boot -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

    或者直接使用 IntelliJ IDEA 或 Eclipse 创建一个 Maven 项目。

  2. 添加 Spring Boot 依赖:

    <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
           <version>2.5.4</version>
       </dependency>
    </dependencies>
  3. 创建主应用类:

    package com.example.hello;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class HelloSpringBootApplication {
       public static void main(String[] args) {
           SpringApplication.run(HelloSpringBootApplication.class, args);
       }
    }
  4. 启动应用并测试:
    • 在 IDE 中运行 HelloSpringBootApplication 类。
    • 打开浏览器访问 http://localhost:8080,查看应用是否成功启动。

项目配置文件示例

配置文件 pom.xml 示例:

<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>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>HelloSpringBoot</name>
    <properties>
        <java.version>1.8</java.version>
        <spring.boot.version>2.5.4</spring.boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
核心概念与常用注解

自动配置与starter

Spring Boot 提供了自动配置功能,它会根据项目依赖自动配置应用程序。starter 是一组预定义的依赖集合,简化了添加新功能的过程。

创建一个简单的控制器

  1. 创建一个新的控制器类:

    package com.example.hello.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
       @GetMapping("/hello")
       public String hello() {
           return "Hello, World!";
       }
    }
  2. 测试控制器功能:
    • 访问 http://localhost:8080/hello,查看返回是否为 Hello, World!

常用注解详解

@SpringBootApplication

  • @SpringBootApplication 是一个组合注解,包含了 @Configuration@EnableAutoConfiguration@ComponentScan
  • @Configuration 表示这是一个配置类。
  • @EnableAutoConfiguration 启用自动配置。
  • @ComponentScan 扫描组件。

@Controller

  • @Controller 用于定义一个控制器类,接收 HTTP 请求并返回适当的响应。

@Service

  • @Service 用于定义一个服务类,通常用于实现业务逻辑。

@Repository

  • @Repository 用于定义一个数据访问层组件,通常用于操作数据库。

@Component

  • @Component 是通用组件注解,适用于任何组件类。

配置文件使用

Spring Boot 支持多种配置文件,如 application.propertiesapplication.yml。配置文件可以用于设置环境变量、数据库连接等。

  1. 创建 application.properties 文件:

    spring.application.name=HelloSpringBoot
    spring.datasource.url=jdbc:mysql://localhost:3306/mydb
    spring.datasource.username=root
    spring.datasource.password=root
  2. 在配置类中读取属性:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Component
    public class AppConfig {
       @Value("${spring.datasource.url}")
       private String dbUrl;
    
       public String getDbUrl() {
           return dbUrl;
       }
    }
数据库集成与操作

数据库连接配置

配置数据库连接是 Spring Boot 应用的关键步骤。以下是一些常见数据库的配置示例。

  1. MySQL 配置

    spring.datasource.url=jdbc:mysql://localhost:3306/mydb
    spring.datasource.username=root
    spring.datasource.password=root
  2. PostgreSQL 配置
    spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
    spring.datasource.username=root
    spring.datasource.password=root

JPA与MyBatis使用

使用JPA操作数据库

  1. 添加 JPA 依赖:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
       <version>2.5.4</version>
    </dependency>
  2. 创建实体类:

    package com.example.hello.entity;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    
    @Entity
    public class User {
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
       private String name;
       private String email;
    
       public User() {
       }
    
       public User(String name, String email) {
           this.name = name;
           this.email = email;
       }
    
       public Long getId() {
           return id;
       }
    
       public String getName() {
           return name;
       }
    
       public String getEmail() {
           return email;
       }
    }
  3. 创建 Repository 接口:

    package com.example.hello.repository;
    
    import com.example.hello.entity.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface UserRepository extends JpaRepository<User, Long> {
    }
  4. 创建服务类:

    package com.example.hello.service;
    
    import com.example.hello.entity.User;
    import com.example.hello.repository.UserRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserService {
       @Autowired
       private UserRepository userRepository;
    
       public List<User> getAllUsers() {
           return userRepository.findAll();
       }
    
       public User saveUser(User user) {
           return userRepository.save(user);
       }
    }
  5. 创建控制器类:

    package com.example.hello.controller;
    
    import com.example.hello.entity.User;
    import com.example.hello.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/users")
    public class UserController {
       @Autowired
       private UserService userService;
    
       @GetMapping
       public List<User> getAllUsers() {
           return userService.getAllUsers();
       }
    
       @PostMapping
       public User saveUser(@RequestBody User user) {
           return userService.saveUser(user);
       }
    }

使用MyBatis操作数据库

  1. 添加 MyBatis 依赖:

    <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>2.1.4</version>
    </dependency>
  2. 创建 MyBatis 配置文件:

    <?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="com.example.hello.mapper.UserMapper">
       <select id="selectUser" resultType="com.example.hello.entity.User">
           SELECT id, name, email FROM user WHERE id = #{id}
       </select>
    </mapper>
  3. 创建 MyBatis Mapper 接口:

    package com.example.hello.mapper;
    
    import com.example.hello.entity.User;
    import org.apache.ibatis.annotations.Select;
    
    public interface UserMapper {
       @Select("SELECT id, name, email FROM user WHERE id = #{id}")
       User selectUser(Long id);
    }
  4. 创建 MyBatis 配置类:

    package com.example.hello.config;
    
    import com.example.hello.mapper.UserMapper;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    
    @Configuration
    @MapperScan("com.example.hello.mapper")
    public class MyBatisConfig {
       @Bean
       public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
           SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
           factoryBean.setDataSource(dataSource);
           factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
           return factoryBean.getObject();
       }
    }

数据库事务管理

Spring Boot 使用 Spring 的事务管理支持来管理数据库事务。你可以在服务类中使用 @Transactional 注解来定义事务。

  1. 服务类示例:

    package com.example.hello.service;
    
    import com.example.hello.entity.User;
    import com.example.hello.repository.UserRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    public class UserService {
       @Autowired
       private UserRepository userRepository;
    
       @Transactional
       public void addUser(User user) {
           userRepository.save(user);
       }
    
       @Transactional
       public void deleteUser(Long id) {
           userRepository.deleteById(id);
       }
    }
Web应用开发

RESTful API设计

RESTful API 设计是现代 Web 应用的重要组成部分。以下是 RESTful API 的一些基本原则:

  1. 资源
    • 资源是 API 的核心,通常使用名词来表示,如 userproduct 等。
  2. 标识符
    • 使用 URL 来标识资源,如 http://example.com/users/1
  3. 动词
    • 使用 HTTP 动词来表示对资源的操作,如 GET 获取资源,POST 创建资源,PUT 更新资源,DELETE 删除资源。
  4. 状态码
    • 使用标准 HTTP 状态码来表示操作结果,如 200 OK201 Created404 Not Found 等。
  5. 超媒体
    • 包含链接来帮助客户端导航到相关资源。

控制器@Controller使用

控制器是处理 HTTP 请求并返回适当响应的组件。以下是一个简单的控制器示例:

  1. 创建控制器类:

    package com.example.hello.controller;
    
    import com.example.hello.entity.User;
    import com.example.hello.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/users")
    public class UserController {
       @Autowired
       private UserService userService;
    
       @GetMapping
       public List<User> getAllUsers() {
           return userService.getAllUsers();
       }
    
       @PostMapping
       public User addUser(@RequestBody User user) {
           return userService.saveUser(user);
       }
    
       @PutMapping("/{id}")
       public User updateUser(@PathVariable Long id, @RequestBody User user) {
           user.setId(id);
           return userService.saveUser(user);
       }
    
       @DeleteMapping("/{id}")
       public void deleteUser(@PathVariable Long id) {
           userService.deleteUser(id);
       }
    }

常用过滤器与拦截器

过滤器

过滤器可以在请求到达控制器之前或响应返回客户端之后执行一些操作。以下是一个简单的过滤器示例:

  1. 创建过滤器:

    package com.example.hello.filter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import java.io.IOException;
    
    @Component
    public class CustomFilter extends OncePerRequestFilter {
       @Override
       protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
           String userAgent = request.getHeader("User-Agent");
           System.out.println("User-Agent: " + userAgent);
           filterChain.doFilter(request, response);
       }
    }

拦截器

拦截器可以在请求到达控制器之前或返回响应之前执行一些操作。以下是一个简单的拦截器示例:

  1. 创建拦截器:

    package com.example.hello.interceptor;
    
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Component
    public class CustomInterceptor implements HandlerInterceptor {
       @Override
       public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
           System.out.println("Pre-handle");
           return true;
       }
    
       @Override
       public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
           System.out.println("Post-handle");
       }
    }
安全与权限管理

Spring Security入门

Spring Security 是一个强大且灵活的安全框架,用于保护 Web 应用程序。以下是一个简单的 Spring Security 配置示例:

  1. 添加 Spring Security 依赖:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
       <version>2.5.4</version>
    </dependency>
  2. 创建 Spring Security 配置类:

    package com.example.hello.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http
               .authorizeRequests()
                   .antMatchers("/users").permitAll()
                   .anyRequest().authenticated()
               .and()
               .formLogin()
                   .loginPage("/login")
                   .permitAll()
               .and()
               .logout()
                   .permitAll();
       }
    
       @Bean
       public PasswordEncoder passwordEncoder() {
           return new BCryptPasswordEncoder();
       }
    }
  3. 创建用户认证配置类:

    package com.example.hello.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
       @Autowired
       private UserDetailsService userDetailsService;
    
       @Override
       protected void configure(AuthenticationManagerBuilder auth) throws Exception {
           auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
       }
    
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http
               .authorizeRequests()
                   .antMatchers("/users").permitAll()
                   .anyRequest().authenticated()
               .and()
               .formLogin()
                   .loginPage("/login")
                   .permitAll()
               .and()
               .logout()
                   .permitAll();
       }
    
       @Bean
       public PasswordEncoder passwordEncoder() {
           return new BCryptPasswordEncoder();
       }
    }
  4. 创建用户服务类:

    package com.example.hello.service;
    
    import com.example.hello.entity.User;
    import com.example.hello.repository.UserRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.Optional;
    
    @Service
    public class UserService implements UserDetailsService {
       @Autowired
       private UserRepository userRepository;
    
       @Override
       public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
           Optional<User> user = userRepository.findByUsername(username);
           if (user.isPresent()) {
               return new org.springframework.security.core.userdetails.User(user.get().getUsername(), user.get().getPassword(), new ArrayList<>());
           } else {
               throw new UsernameNotFoundException("User not found");
           }
       }
    }
  5. 创建用户数据访问层 (DAO) 示例:

    package com.example.hello.repository;
    
    import com.example.hello.entity.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface UserRepository extends JpaRepository<User, Long> {
       User findByUsername(String username);
    }
  6. 创建控制器类:

    package com.example.hello.controller;
    
    import com.example.hello.entity.User;
    import com.example.hello.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class UserController {
       @Autowired
       private UserService userService;
    
       @GetMapping("/users")
       public String getUsers() {
           return "Hello, User!";
       }
    }

配置与使用

  1. 创建登录页面(login.html):

    <!DOCTYPE html>
    <html>
    <head>
       <title>Login</title>
       <link rel="stylesheet"  />
    </head>
    <body>
    <div class="container">
       <h2>Login</h2>
       <form action="/login" method="post">
           <div class="form-group">
               <label for="username">Username:</label>
               <input type="text" class="form-control" id="username" name="username" required>
           </div>
           <div class="form-group">
               <label for="password">Password:</label>
               <input type="password" class="form-control" id="password" name="password" required>
           </div>
           <button type="submit" class="btn btn-primary">Login</button>
       </form>
    </div>
    </body>
    </html>
  2. 创建注销页面(logout.html):
    <!DOCTYPE html>
    <html>
    <head>
       <title>Logout</title>
       <link rel="stylesheet"  />
    </head>
    <body>
    <div class="container">
       <h2>Logout</h2>
       <form action="/logout" method="post">
           <button type="submit" class="btn btn-primary">Logout</button>
       </form>
    </div>
    </body>
    </html>
日志与监控

日志管理配置

Spring Boot 使用日志框架 SLF4J 和 Logback 进行日志管理。你可以通过配置 application.properties 文件来更改日志级别和输出格式。

  1. 配置日志级别:

    logging.level.root=INFO
    logging.level.com.example=DEBUG
  2. 配置日志格式:
    logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n

应用监控与健康检查

Spring Boot 提供了内置的监控和健康检查功能。你可以通过 actuator 模块来启用这些功能。

  1. 添加 actuator 依赖:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-actuator</artifactId>
       <version>2.5.4</version>
    </dependency>
  2. 配置健康检查:
    management.endpoints.enabled-by-default=true
    management.endpoint.health.show-details=always

使用这些配置后,你可以访问 /actuator/health 来获取应用的健康状态。

异常处理与日志输出

Spring Boot 提供了 @ControllerAdvice@ExceptionHandler 注解来处理全局异常。

  1. 创建全局异常处理器:

    package com.example.hello.controller;
    
    import com.example.hello.exception.CustomException;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
       @ExceptionHandler(CustomException.class)
       public ResponseEntity<String> handleCustomException(CustomException ex) {
           return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
       }
    }
  2. 创建自定义异常:

    package com.example.hello.exception;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public class CustomException extends RuntimeException {
       public CustomException(String message) {
           super(message);
       }
    }
  3. 抛出自定义异常:

    package com.example.hello.controller;
    
    import com.example.hello.exception.CustomException;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class UserController {
       @GetMapping("/error")
       public String throwError() {
           throw new CustomException("Custom error occurred");
       }
    }
點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消