Web基础 -SpringBoot入门 -HTTP-分层解耦 -三层架构

发布于:2025-06-26 ⋅ 阅读:(20) ⋅ 点赞:(0)

在HTML、CSS、JS 以及图片、音频、视频等这些资源,我们都称为静态资源。 所谓静态资源,就是指在服务器上存储的不会改变的数据,通常不会根据用户的请求而变化。

那与静态资源对应的还有一类资源,就是动态资源。那所谓动态资源,就是指在服务器端上存储的,会根据用户请求和其他数据动态生成的,内容可能会在每次请求时都发生变化。 在企业开发项目中,都是基于Spring实现的。

而Spring家族旗下这么多的技术,最基础、最核心的是 SpringFramework。其他的spring家族的技术,都是基于SpringFramework的,SpringFramework中提供很多实用功能,如:依赖注入、事务管理、web开发支持、数据访问、消息服务等等。

而如果我们在项目中,直接基于SpringFramework进行开发,存在两个问题:

配置繁琐

入门难度大

所以基于此呢,spring官方推荐我们从另外一个项目开始学习,那就是目前最火爆的SpringBoot。 通过springboot就可以快速的帮我们构建应用程序,所以springboot呢,最大的特点有两个 :

  • 简化配置

  • 快速开发

Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率 。

而直接基于SpringBoot进行项目构建和开发,不仅是Spring官方推荐的方式,也是现在企业开发的主流。

一、创建SpringBoot项目

左侧生成器选择SpringBoot,右侧选择Maven依赖即可。

下一步后勾选SpringWeb,项目即创建完成。

基础项目入门

package org.example.springbootwebquickstract;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController//当前类是一个请求处理类
public class HelloController {
    @RequestMapping("/hello")
    public String hello(String name){
        System.out.println("name:"+name);
        return "hello:"+name;
    }
}

上述代码中,注解@RestController起到标注当前的类为请求处理类的作用,注解@RequestMapping起到标注请求路径的作用。括号内的hello即为请求路径。

当需要访问对应数据时,可以用请求路径访问,例如本程序可以使用http://localhost:8080/hello?name=CodeBlossom进行访问

运行程序

运行SpringBoot自动生成的引导类 (标识有@SpringBootApplication注解的类)即可自动运行引导类下的包及其子包下的程序。

HTTP协议

HTTP:Hyper Text Transfer Protocol(超文本传输协议),规定了浏览器与服务器之间数据传输的规则。

  • http是互联网上应用最为广泛的一种网络协议

  • http协议要求:浏览器在向服务器发送请求数据时,或是服务器在向浏览器发送响应数据时,都必须按照固定的格式进行数据传输

HTTP协议的特点

基于TCP:面向连接,安全

基于请求-响应模型:一次请求对应一次响应,先请求后响应。请求和响应是一一对应的关系,没有请求就没有响应。

HTTP 协议分为:请求协议和响应协议

请求协议:浏览器将数据以请求格式发送到服务器。包括:请求行、请求头 、请求体

常见的HTTP请求头

请求头

含义

Host

表示请求的主机名

User-Agent

浏览器版本。 例如:Chrome浏览器的标识类似Mozilla/5.0 ...Chrome/79 ,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...)like Gecko

Accept

表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有;

Accept-Language

表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;

Accept-Encoding

表示浏览器可以支持的压缩类型,例如gzip, deflate等。

Content-Type

请求主体的数据类型

Content-Length

数据主体的大小(单位:字节)

获取请求体数据

Web对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),使程序员可以直接用代码获取请求体数据

package org.example.springbootwebquickstract;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RequestController {
    @RequestMapping("/request")
    public String resquest(HttpServletRequest request){
        //1、获取请求方式
        String method = request.getMethod();
        System.out.println("method:"+method);
        //2、获取请求url地址
        String url = request.getRequestURL().toString();//http://localhost:8080/request
        System.out.println("请求url地址:"+url);
        String requestURI = request.getRequestURI();
        System.out.println("请求uri地址:"+requestURI);
        //3、获取请求协议
         String protocol = request.getProtocol();
         System.out.println("请求协议"+protocol);
        //4、获取请求参数 -name,age .....
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        System.out.println("name:"+name+",age:"+age);
        //5、获取请求头:Accept
        String header = request.getHeader("Accept");//指定名字的请求头
        System.out.println("请求头Accept:"+header);
        return "OK";
    }
}

HTTP响应协议

响应体的第一行为响应行 分为  协议版本:HTTP/1.1 响应状态码401 以及状态码描述

响应头:响应数据的第二行开始。格式为key:value形式

http是个无状态的协议,所以可以在请求头和响应头中设置一些信息和想要执行的动作,这样,对方在收到信息后,就可以知道你是谁,你想干什么

常见的HTTP响应头有:

Content-Type:表示该响应内容的类型,例如text/html,image/jpeg ; Content-Length:表示该响应内容的长度(字节数);

Content-Encoding:表示该响应压缩算法,例如gzip ; Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒 ;

Set-Cookie: 告诉浏览器为当前页面所在的域设置cookie ;

响应体(以上图中绿色部分): 响应数据的最后一部分。存储响应的数据

代码示例

package org.example.springbootwebquickstract;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ResponseController {
    /*
    方式一:设置响应数据基于HttpServletResponse对象
     */
    @RequestMapping("/response")
    public void response(HttpServletResponse  response) throws  Exception
    {
        //1、设置响应状态码
        response.setStatus(HttpServletResponse.SC_OK);
        //2、设置响应头
        response.setHeader("Name","CodeBlossom");

        //3、设置响应体
        response.getWriter().write("<h1>Hello World</h1>");
    }
    /*
    方式二:设置响应数据基于ResponseEntity对象(spring提供的方式)
     */
    @RequestMapping("/response2")
    public ResponseEntity< String> response2()
    {
        return ResponseEntity.status(401).header("name","javaweb").body("<h1>Hello response</h1>");
    }
}

SpringBootWeb案例

基于SpringBoot开发web程序,实现页面的渲染展示

先创建一个封装用户信息的实体类

package org.example.springbootweb01.pojo;

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

import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;//项目开发建议使用包装类型
    private String username;
    private String password;
    private String name;
    private Integer age;
    private LocalDateTime updateTime;

}

 

由于在案例中,需要读取文本中的数据,并且还需要将对象转为json格式,所以这里呢,我们在项目中再引入一个非常常用的工具包hutool。 然后调用里面的工具类,就可以非常方便快捷的完成业务操作。

在Maven带有的xml文件中添加依赖

<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.27</version>
</dependency>
package org.example.springbootweb01.controller;

import cn.hutool.core.io.IoUtil;
import jakarta.annotation.Resource;
import org.example.springbootweb01.pojo.User;
import org.example.springbootweb01.service.UserService;
import org.example.springbootweb01.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/*
用户信息Controller
 */
@RestController//@ResponseBody ->作用:将controller返回值直接作为响应体的数据直接响应;返回值是对象/集合->json->响应体
public class UserController {

    @RequestMapping("/list")
    public List<User> list()throws  Exception{
        //InputStream in = new FileInputStream(new File("src/main/resources/user.txt"));    //磁盘目录会改变,所以不推荐
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");    //类加载器,将文件放在类路径下,通过类加载器获取
        //1、加载并读取User.txt文件获取用户数据
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());
        //2、解析用户信息,将用户数据封装成User对象->List集合
        List<User> userList = lines.stream().map(line -> {

            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).toList();
        //3、返回用户数据(json)
        return userList;
    }
     
}

将前端代码放到Spring标注的静态资源库中 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户列表数据</title>
    <style>
        /*定义css,美化表格*/
        table{
            border-collapse: collapse;
            width: 100%;
            margin-top: 20px;
            border: 1px solid #ccc;
            text-align: center;
            font-size: 14px;
        }
        tr {
            height: 40px;
        }
        th,td{
            border: 1px solid #ccc;
        }
        thead{
            background-color: #e8e8e8;
        }
        h1{
            text-align: center;
            font-family: 楷体;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>用户列表数据</h1>
        <!--定义一个表格,包括6列,分别是: ID, 用户名, 密码, 姓名, 年龄, 更新时间-->
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>更新时间</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="user in userList">
                    <td>{{user.id}}</td>
                    <td>{{user.username}}</td>
                    <td>{{user.password}}</td>
                    <td>{{user.name}}</td>
                    <td>{{user.age}}</td>
                    <td>{{user.updateTime}}</td>
                </tr>
            </tbody>
        </table>
    </div>

    <!--引入axios-->
    <script src="js/axios.min.js"></script>
    <script type="module">
        import { createApp } from './js/vue.esm-browser.js'
        createApp({
            data() {
                return {
                    userList: []
                }
            },
            methods: {
                async search(){
                    const result = await axios.get('/list');
                    this.userList = result.data;
                }
            },
            mounted() {
                this.search();
            }
        }).mount('#app')
    </script>
</body>
</html>

@ResponseBody(包含在@RestController内)注解:

  • 类型:方法注解、类注解

  • 位置:书写在Controller方法上或类上

  • 作用:将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器

  1. 三层架构

在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。这样可以让代码维护更简单

那其实我们上述案例的处理逻辑呢,从组成上看可以分为三个部分:​
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。​
逻辑处理:负责业务逻辑处理的代码。​
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。

按照上述的三个组成部分,在项目开发中,可以将代码分为三层,如图所示:

  • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。

  • Service:业务逻辑层。处理具体的业务逻辑。

  • Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。

执行流程

浏览器访问页面->控制层->业务逻辑层->数据访问层,这样实现了分层解耦思想,例如对业务层进行变更,不会影响到控制层和Dao层。

分层解耦的思想

耦合:衡量软件中各个层/各个模块的依赖关联层度。

内聚:软件中各个功能模块的功能联系

软件设计原则:高内聚低耦合

上述的思想无法实现解耦思想

例如上图,在控制层需要指定new一个Uservice的实现类创建,当我们需要修改其使用的实现类时还是需要更改控制层。我们可以根据之前的内容实现这个问题。当我们定义常量时,可以专门创建一个常量的容器(类),然后当需要更改常量时在容器内将其更改即可,对调用的代码无影响。此时的解耦也可以用这个操作,将UserService的实现类放到容器中,然后将需要使用的实现类注入到对应的程序中。

们想要实现上述解耦操作,就涉及到Spring中的两个核心概念:

  • 控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

    • 对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。

  • 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。

    • 程序运行时需要某个资源,此时容器就为其提供这个资源。

    • 例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。

Bean的声明

  • bean对象:IOC容器中创建、管理的对象,称之为:bean对象。

实现类加上 @Component 注解,就代表把当前类产生的对象交给IOC容器管理。

在需要注入容器中的类的对象加上@Autowired注解,就代表把容器内的实现类交给当前的对象。

前面我们提到IOC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component

而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:

注解

说明

位置

@Component

声明bean的基础注解

不属于以下三类时,用此注解

@Controller

@Component的衍生注解

标注在控制层类上

@Service

@Component的衍生注解

标注在业务层类上

@Repository

@Component的衍生注解

标注在数据访问层类上(由于与mybatis整合,用的少)

DI详解

依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。​
在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。​
@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)

@Autowired用法

 属性注入(常用)

@RestController
public class UserController {

    //方式一: 属性注入
    @Autowired
    private UserService userService;
    
  }
  • 优点:代码简洁、方便快速开发。

  • 缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。

构造函数注入(常用)

@RestController
public class UserController {

    //方式二: 构造器注入
    private final UserService userService;
    
    @Autowired //如果当前类中只存在一个构造函数, @Autowired可以省略
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
 }   
  • 优点:能清晰地看到类的依赖关系、提高了代码的安全性。

  • 缺点:代码繁琐、如果构造参数过多,可能会导致构造函数臃肿。

  • 注意:如果只有一个构造函数,@Autowired注解可以省略。(通常来说,也只有一个构造函数)

3). setter注入

/**
 * 用户信息Controller
 */
@RestController
public class UserController {
    
    //方式三: setter注入
    private UserService userService;
    
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
}    

优点:保持了类的封装性,依赖关系更清晰。

缺点:需要额外编写setter方法,增加了代码量。

注意事项:

在IOC容器中,存在多个相同类型的bean对象,容器会不知道该给当前的对象注入哪个bean对象,程序会报错。

为了解决上述问题,Spring给了以下三种方案

  • @Primary

  • @Qualifier

  • @Resource

方案一:使用@Primary注解

当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

在想要注入的bean对象上加此注解即可默认注入该个对象

方案二:使用@Qualifier注解​(bean对象的默认名为实现类的首字母小写)
指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。 @Qualifier注解不能单独使用,必须配合@Autowired使用。例:@Qualifier("bean对象")

方案三:使用@Resource注解​
是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。例:

@Resource(name = "userServiceImpl")

项目构建

package org.example.springbootweb01.dao;

import java.util.List;

public interface UserDao {
    public List<String> findAll();
}
package org.example.springbootweb01.service;

import org.example.springbootweb01.pojo.User;

import java.util.List;

public interface UserService {
    public List<User> findAll();
}



package org.example.springbootweb01.controller;

import cn.hutool.core.io.IoUtil;
import jakarta.annotation.Resource;
import org.example.springbootweb01.pojo.User;
import org.example.springbootweb01.service.UserService;
import org.example.springbootweb01.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/*
用户信息Controller
 */
@RestController//@ResponseBody ->作用:将controller返回值直接作为响应体的数据直接响应;返回值是对象/集合->json->响应体
public class UserController {
//    1部分
    /*@RequestMapping("/list")
    public List<User> list()throws  Exception{
        //InputStream in = new FileInputStream(new File("src/main/resources/user.txt"));    //磁盘目录会改变,所以不推荐
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");    //类加载器,将文件放在类路径下,通过类加载器获取
        //1、加载并读取User.txt文件获取用户数据
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());
        //2、解析用户信息,将用户数据封装成User对象->List集合
        List<User> userList = lines.stream().map(line -> {

            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).toList();
        //3、返回用户数据(json)
        return userList;
    }
     */
//2部分
    /*private UserService userService = new UserServiceImpl();
    @RequestMapping("/list")
    public List<User> list()throws  Exception{
       List<User> userList =userService.findAll();

        //3、返回用户数据(json)
        return userList;

     */
    //方式一:属性注入
    //@Autowired        //@Autowired注解:自动注入
    //private UserService userService;
//    //方式二:构造方法注入
//    private final UserService userService;
//    @Autowired  //如果构造函数只有一个,@Autowired可以省略
//    public UserController(UserService userService) {
//        this.userService = userService;
//    }
    //方式三:setter方法注入
    private UserService userService;
    //@Qualifier("userServiceImpl")
    //@Autowired
    @Resource(name = "userServiceImpl")
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/list")
    public List<User> list()throws  Exception{
        List<User> userList =userService.findAll();

        //3、返回用户数据(json)
        return userList;

    }
}
package org.example.springbootweb01.dao.impl;

import cn.hutool.core.io.IoUtil;
import org.example.springbootweb01.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Repository("userDao")//括号内指定bean的名字,默认为类名首字母小写
//@Component  //将当前类交给ioc容器管理
public class UserDaoImpl implements UserDao {

    @Override
    public List<String> findAll() {
        //InputStream in = new FileInputStream(new File("src/main/resources/user.txt"));

        //1、加载并读取User.txt文件获取用户数据
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());
        return lines;
    }
}
package org.example.springbootweb01.service.impl;

import org.example.springbootweb01.dao.UserDao;
import org.example.springbootweb01.dao.impl.UserDaoImpl;
import org.example.springbootweb01.pojo.User;
import org.example.springbootweb01.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Service
//@Component//将当前类交给ioc容器管理
public class UserServiceImpl implements UserService {
    //1、调用dao获取数据
    //private UserDao userDao = new UserDaoImpl();
    @Autowired  //应用程序运行时会自动查找该类型的Bean对象,并赋值给该成员变量
    private UserDao userDao;
    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        //2、解析用户信息,将用户数据封装成User对象->List集合
        List<User> userList = lines.stream().map(line -> {

            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).toList();
        return userList;
    }
}
package org.example.springbootweb01.service.impl;

import org.example.springbootweb01.dao.UserDao;
import org.example.springbootweb01.pojo.User;
import org.example.springbootweb01.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
//@Primary    // 指定当前类为默认实现类
@Service
//@Component//将当前类交给ioc容器管理
public class UserServiceImpl2 implements UserService {
    //1、调用dao获取数据
    //private UserDao userDao = new UserDaoImpl();
    @Autowired  //应用程序运行时会自动查找该类型的Bean对象,并赋值给该成员变量
    private UserDao userDao;
    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        //2、解析用户信息,将用户数据封装成User对象->List集合
        List<User> userList = lines.stream().map(line -> {

            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id+200, username, password, name, age, updateTime);
        }).toList();
        return userList;
    }
}

上述代码实现了分层解耦思想。


网站公告

今日签到

点亮在社区的每一天
去签到