SpringMVC框架

SpringMVC框架

課程目標
理解SpringMVC核心框架流程
掌握SpringMVC框架配置方式
掌握SpringMVC的處理器方法參數與返回值的使用
掌握SpringMVC實現文件上傳功能
掌握SpringMVC攔截器的使用
了解RESTful互聯網軟件架構規(guī)范
掌握SSM框架集成的配置
課程重點
SpringMVC的配置的相關注解
SpringMVC框架處理器方法的參數及返回值的使用
SSM框架整合的集成配置

1.SpringMVC框架簡介

1.1.框架簡介

Spring MVC 是 Spring 提供給 Web 應用的框架設計。。
Spring MVC 角色劃分清晰,分工明細,并且和 Spring 框架無縫結合。作為當今業(yè)界最主流的 Web 開發(fā)框架,Spring MVC 已經成為當前javaWeb框架事實上的標準。

1.2.SpringMVC核心架構流程


springMVC核心架構的具體流程步驟如下:

  1. 首先用戶發(fā)送請求——>DispatcherServlet(前端控制器),前端控制器收到請求后自己不進行處理,而是委托給其他的解析器進行處理,作為統(tǒng)一訪問點,進行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping將會把請求映射為HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象、多個HandlerInterceptor攔截器)對象,通過這種策略模式,很容易添加新的映射策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter將會把處理器包裝為適配器,從而支持多種類型的處理器,即適配器設計模式的應用,從而很容易支持很多類型的處理器;
  4. HandlerAdapter——>處理器功能處理方法的調用,HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理;并返回一個ModelAndView對象(包含模型數據、邏輯視圖名);
  5. ModelAndView的邏輯視圖名——> ViewResolver, ViewResolver將把邏輯視圖名解析為具體的View,通過這種策略模式,很容易更換其他視圖技術;
  6. View——>渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構,因此很容易支持其他視圖技術;
  7. 返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶,到此一個流程結束。

1.3.前后端分離架構下的SpringMVC

那么,在前后端分離模式下,視圖層要分離出去,成為一個獨立工程;或者說:視圖層不在由服務器端控制。 所以,在前后端分離模式下,SpringMVC的核心架構流程修改如下:
wechat_2025-08-25_160304_202.png

2.SpringMVC框架實例

2.1.創(chuàng)建工程 項目類型-Jakarta EE 項目名: springmvc

編輯部署設置
1.應用程序上下文: /springmvc
2.HTTP 端口: 3333

2.2.在pom.xml文件中添加依賴 在resources下新建logback.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.1.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>

            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.9</version> <!-- 建議使用最新穩(wěn)定版 -->
        </dependency>
        <!-- Logback 實現 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.11</version>
        </dependency>
        <!-- Logback 核心 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.4.11</version>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定義日志輸出格式 -->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [springmvc] %-5level - %msg%n" />

    <!-- 控制臺輸出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 設置日志級別 -->
    <logger name="com.neuedu.springmvc" level="INFO" />
    <logger name="org.springframework" level="INFO" />

    <!-- 根日志配置 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

2.3.在config包下創(chuàng)建MvcConfig.java類

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;


@Configuration
@ComponentScan("com.neuedu.springmvc")
@EnableWebMvc //使用EnableWebMvc開啟springmvc注解方式配置
public class MvcConfig implements WebMvcConfigurer {

    /**
     * 跨域設置
     */
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedHeaders("*")
                .allowedOrigins("*");
    }

    /**
     * 添加攔截器(可以添加多個)
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //registry.addInterceptor(null).addPathPatterns("/**");
    }

    /**
     * 配置文件上傳解析器
     */
    @Bean
    public MultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        StringHttpMessageConverter converter = new StringHttpMessageConverter();
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        List<MediaType> list = new ArrayList<MediaType>();
        list.add( MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(list);
        converters.add(converter);
        converters.add( new MappingJackson2HttpMessageConverter() );
    }
    
}

2.4.在config包下創(chuàng)建MvcInit.java類

import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import jakarta.servlet.Filter;

/**
 * 1、創(chuàng)建Mvc初始化類,需要繼承AbstractAnnotationConfigDispatcherServletInitializer類
 * springmvc容器的父容器spring配置類
 * 實際工作中我們的項目比較復雜,可以將controller層放在springmvc容器中
 * 其他層,如service層、dao層放在父容器了,bean管理起來更清晰一些
 * 也可以沒有父容器,將所有bean都放在springmvc容器中
 */
public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
   
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    /**
     * 2、設置springmvc容器的spring配置類
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MvcConfig.class};
    }

    /**
     * 3、配置DispatcherServlet的url-pattern
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 4、注冊攔截器
     */
    @Override
    protected Filter[] getServletFilters() {
        //添加攔截器,解決亂碼問題
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceRequestEncoding(true);
        characterEncodingFilter.setForceResponseEncoding(true);
        return new Filter[]{characterEncodingFilter};
    }
    /**
     * 5、設置文件上傳相關配置
     */
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        //設置允許上傳的單個文件大小 5M
        long maxFileSize = 5 * 1024 * 1024;
        //設置允許上傳的總文件大小 20M
        long maxRequestSize = 20 * 1024 * 1024;
        //設置文件上傳閾值
        int fileSizeThreshold =0;
        registration.setMultipartConfig(
                new MultipartConfigElement(null,maxFileSize,maxRequestSize,fileSizeThreshold));
    }

}

2.5.創(chuàng)建Controller控制器

在controller包下創(chuàng)建 HelloController控制器。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
    @RequestMapping("/hello")
    @ResponseBody
    public String hello() throws Exception {
        return "hello world!";
    }
}

@Controller:此注解聲明在類上,表示此類是一個控制器類,并被納入到 Spring 容器中;
@RequestMapping:此注解明在類上,或者方法上;表示將一個請求url映射給控制器方法。
@ResponseBody:此注解聲明在類上,或者方法上;表示處理器方法直接返回數據。

2.6.測試

啟動Tomcat服務器,使用Apifox發(fā)送請求測試

http://localhost:3333/springmvc/hello

2.7.相關注解詳解

2.7.1.@RequestMapping注解

用于建立請求URL和控制器方法之間的對應關系。

  1. @RequestMapping 應用在處理器類上: 設置請求URL的第一級訪問目錄,使我們的URL可以按照模塊化管理
@RequestMapping("/user")
@Controller
public class HelloController {
    @ResponseBody
    @RequestMapping("/hello")
    public String hello() throws Exception {
        return "hello world";
    }
}

上面處理器方法請求url應該這樣寫:http://localhost:3333/springmvc/user/hello
2.@RequestMapping注解中常用屬性有:
value:用于指定請求的URL。 method:用于指定請求的方式。

@RequestMapping(value="/hello",method=RequestMethod.POST)
public String hello() throws Exception {
    return "hello world";
}

如果使用 get 方式訪問此方法時,就會出現異常。

2.7.2.@ResponseBody注解

@ResponseBody注解的核心作用是告訴 Spring MVC 框架:不要將方法的返回值解析為一個視圖(View)名稱去渲染頁面,而是直接將這個返回值作為 HTTP 響應體(Response Body)的內容。它會自動根據客戶端的請求頭(如 Accept)來決定使用哪個 HttpMessageConverter 將方法的返回值轉換成合適的格式(如 JSON、HTML、Text),并寫入到響應體中,同時設置相應的 Content-Type 頭。

  1. @ResponseBody 應用在處理器類上:此處理器類中的所有方法都直接返回數據。
  2. @ResponseBody 應用在處理器類的某個方法上:此處理器類中的某個方法直接返回數據。

2.7.3.@GetMapping與@PostMapping

@GetMapping是一個組合注解,是@RequestMapping(method = RequestMethod.GET)的縮寫。
@PostMapping是一個組合注解,是@RequestMapping(method = RequestMethod.POST)的縮寫。

3.控制器方法的參數與返回值

3.1.控制器方法參數

1.使用@RequestParam注解,將請求參數自動綁定到控制器方法參數

public String p1(@RequestParam("name") String name, 
                    @RequestParam("age") int age)  {}

@RequestParam("name")中的name,必須和請求參數名一致
@RequestParam注解可以根據控制器方法參數類型,自動完成類型轉換

2.控制器中方法可以使用對象接收多個請求參數數據

@Data
public class User {
    private String name;
    private Integer age;
}
public String p2(User user)  {}

實體類屬性名必須和請求參數名一致

3.接收AJAX請求提交json數據

public String p3(@RequestBody  User user)  {}

@RequestBody 注解的主要作用是:將 HTTP 請求體(Body)中的 JSON/數據,自動綁定到后端的 Java 對象上。
實體類屬性名必須和JSON數據屬性名一致

3.2.控制器方法返回值

在前后端分離模式中,SpringMVC的控制器方法直接返回數據。

使用@ResponseBody注解

4.SpringMVC文件上傳

4.1.創(chuàng)建上傳文件控制器

import java.io.File;
import java.util.UUID;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
public class UploadController {
    @ResponseBody
    @RequestMapping("/upload")
    // 必須要有MultipartFile類型參數
    public String upload(@RequestParam("file") MultipartFile myFile) {
        //上傳圖片存儲目錄
        String path = "d:/upload";
        //獲取文件名并使用UUID生成新文件名
        String fileName = myFile.getOriginalFilename();
        String newFileName = UUID.randomUUID() + fileName.substring(fileName.lastIndexOf("."));
        //在指定上傳圖片存儲目錄中創(chuàng)建新文件
        File targetFile = new File(path, newFileName);
        //如果找不到指定目錄和文件,就新創(chuàng)建此目錄和文件
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }
        //將文件寫入硬盤(myFile在內存中)
        try {
            myFile.transferTo(targetFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "ok";
    }
}

注意:

  1. 文件上傳;只有使用了multipart/form-data,才能上傳二進制數據;
  2. method:必須為post方式提交。

5.SpringMVC攔截器

什么是攔截器?
攔截器是 Spring MVC 框架提供的一種機制,允許你在請求到達 Controller 之前和響應返回客戶端之后插入自定義邏輯。它類似于 Servlet 中的 Filter,但更深度地集成到 Spring MVC 的核心流程中。

典型應用場景:

  • 身份認證 (Authentication):檢查用戶是否登錄。
  • 授權 (Authorization):檢查用戶是否有權限訪問某個資源。
  • 日志記錄 (Logging):記錄請求信息、執(zhí)行時間等。
  • 主題更改 (Theme Changing):根據用戶偏好設置主題。
  • 性能監(jiān)控:計算 Controller 方法的執(zhí)行時間

實現步驟

  • 實現一個攔截器需要三個步驟:
  • 創(chuàng)建攔截器類:實現 HandlerInterceptor 接口
  • 重寫關鍵方法:主要是 preHandle, postHandle, afterCompletion。
    1. preHandle:該方法在 controller 執(zhí)行前執(zhí)行,可以實現對數據的預處理。 如果方法返回 true ,則繼續(xù)調用下一個資源。否則不在繼續(xù)調用。
    2. postHandle:該方法在處理器執(zhí)行后,生成視圖前執(zhí)行。這里有機會修改視圖層數據。
    3. afterCompletion:最后執(zhí)行,通常用于記錄日志,釋放資源,處理異常。
  • 注冊攔截器:通過配置類將攔截器添加到 Interceptor Registry 中。
    示例:實現一個日志攔截器
    第 1 步:創(chuàng)建攔截器類 LogInterceptor:記錄每個請求的開始和結束時間,并計算處理耗時
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 日志攔截器
 */
public class LogInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
    // 使用 ThreadLocal 來保存線程安全的變量(每個請求的開始時間)
    private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 在 Controller 方法執(zhí)行之前調用
        long startTime = System.currentTimeMillis();
        startTimeThreadLocal.set(startTime);
        logger.info("[preHandle] 請求 URL: '{}'", request.getRequestURL());
        logger.info("[preHandle] 請求開始時間: {}", sdf.format(new Date()));
        // 返回 true 表示繼續(xù)流程,調用下一個攔截器或 Controller
        // 返回 false 則中斷流程,需要自己通過 response 產生響應
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 2. 在 Controller 方法執(zhí)行之后,但在視圖渲染之前調用
        logger.info("[postHandle] 請求 URL: '{}' 控制器處理完畢", request.getRequestURL());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 3. 在整個請求結束之后(視圖渲染完畢)調用,常用于資源清理
        long startTime = startTimeThreadLocal.get();
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        logger.info("[afterCompletion] 請求處理完畢時間: {}", sdf.format(new Date()));
        logger.info("[afterCompletion] 請求 URL: '{}' 總耗時: {}ms", request.getRequestURL(), executeTime);
        // 非常重要:清理 ThreadLocal,避免內存泄漏
        startTimeThreadLocal.remove();

    }
}

第 2 步:注冊攔截器(使用配置類)
修改 MvcConfig.java

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        //registry.addInterceptor(null).addPathPatterns("/**");
        // 注冊日志攔截器,攔截所有請求
        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns("/**"); // 攔截所有路徑
    }

第 3 步:發(fā)送請求測試

6.RESTful互聯網軟件架構

6.1.什么是REST

什么是 RESTful 架構?
REST(Representational State Transfer,表述性狀態(tài)轉移)是一種軟件架構風格,而不是標準或協(xié)議。它由 Roy Fielding 博士在 2000 年提出,是一組設計原則和約束條件。
RESTful 是指遵循 REST 原則的 Web 服務或 API。
REST 的六個核心約束

  1. 客戶端-服務器架構 (Client-Server)
    分離關注點:客戶端負責用戶界面和用戶體驗,服務器負責數據處理和存儲
    優(yōu)勢:提高可移植性、簡化服務器組件、允許各部分獨立演進

  2. 無狀態(tài) (Stateless)
    每個請求必須包含處理該請求所需的所有信息
    服務器不存儲客戶端上下文,會話狀態(tài)完全保存在客戶端
    優(yōu)勢:提高可見性、可靠性、可擴展性

  3. 可緩存 (Cacheable)
    響應必須明確標識是否可緩存
    減少客戶端-服務器交互,提高性能
    優(yōu)勢:減少網絡延遲,提高效率

  4. 統(tǒng)一接口 (Uniform Interface)
    資源標識:每個資源都有唯一的標識符(URI)
    通過表述操作資源:客戶端操作資源的表述而不是資源本身
    自描述消息:每個消息包含足夠信息來描述如何處理

  5. 分層系統(tǒng) (Layered System)
    系統(tǒng)可以由多個層次組成,每個層次只知道相鄰層次
    優(yōu)勢:提高可擴展性、簡化系統(tǒng)復雜度

  6. 按需代碼 (Code-On-Demand,可選)
    服務器可以臨時擴展客戶端功能

RESTful API 設計原則

  1. 資源導向設計
    一切皆資源(名詞,而不是動詞)
    使用 URI 標識資源:
    /user - 用戶集合
    /users/123 - 特定用戶
    /users/123/orders - 用戶的訂單
  2. HTTP 方法的使用
  • GET 查詢資源
  • POST 創(chuàng)建新資源
  • PUT 更新或創(chuàng)建資源
  • DELETE 刪除資源

RESTful API 示例
用戶管理 API

獲取所有用戶

METHOD: GET
URL: /user
Content-Type: application/json

獲取特定用戶

METHOD: GET
URL: /user/123
Content-Type: application/json

創(chuàng)建新用戶

METHOD:POST
URL: /user
Content-Type: application/json

更新用戶

METHOD:PUT
URL: /user
Content-Type: application/json

刪除用戶

METHOD: DELETE
URL: /user/123
Content-Type: application/json

響應結果封裝類

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class Result<T> {
    private int code;       // 狀態(tài)碼
    private String msg;     // 消息
    private T data;         // 數據

    // 成功靜態(tài)方法
    public static <T> Result<T> success(T data) {
        return Result.<T>builder()
                .code(200)
                .msg("success")
                .data(data)
                .build();
    }

    // 失敗靜態(tài)方法
    public static <T> Result<T> error(int code, String msg) {
        return Result.<T>builder()
                .code(code)
                .msg(msg)
                .build();
    }
}

User類

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String  name;
    private Integer age;
    private String  email;
}

UserController

import com.neuedu.springmvc.po.Result;
import com.neuedu.springmvc.po.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController//相當于@Controller+@ResponseBody
@RequestMapping("/user")//一級URL
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    //1. 查詢所有用戶
    @GetMapping
    public Result<List<User>> queryAllUsers() {
        List<User> users = new ArrayList<>();
        users.add(new User(1,"建宇",22,"23@11.com"));
        users.add(new User(2,"卓航",22,"24@11.com"));
        return  Result.success(users);
    }
    // 2. 根據ID查詢用戶
    @GetMapping("/{id}")
    public Result<User> queryUserById(@PathVariable("id") int id) {
        return Result.success(new User(id,"建宇",22,"23@11.com"));
    }
    // 3. 新增用戶
    @PostMapping
    public Result<String> addUser(@RequestBody User user) {
        logger.info("[新增用戶信息]: '{}'",user);
        return Result.success("新增成功");
    }
    // 4. 修改用戶
    @PutMapping
    public Result<String> updateUser(@RequestBody User user) {
        logger.info("[修改用戶信息]: '{}'",user);
        return Result.success("修改成功");
    }

    // 5. 刪除用戶
    @DeleteMapping("/{id}")
    public Result<String>deleteUser(@PathVariable("id") int id) {
        logger.info("[修改用戶Id]: '{}'",id);
        return Result.success("刪除用戶");
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容