Spring cloud拾遗DevOps

微服务架构—JAVA打包黑科技

2019-05-18  本文已影响606人  柠檬夕

疑问:Spring Boot已经有了 spring-boot-maven-plugin 的打包方式,为什么还要自己重新实现打包方式呢?
:都各有优势吧,不过本文的方式更加强大。不过SpringBoot打包出来jar文件,没办法进行修改,如果遇到需要简单修改一些系统配置或参数时(一些非配置中心的参数,如日志配置文件中的某个系统参数等),那就必须要重新编译打包才能生效。同时,本文的方式会自动生成各种操作系统(MAC/AIX/Linux/Windows等)的脚本文件,以及一些相关的运维命令(console|start|stop|restart|status|dump),同时可以依赖版本检查、GIT历史记录、远程DEBUG、配置JVM参数、GC参数和JMX参数等。

1 背景

每个JAVA项目开发完成后都会考虑部署至各个环境(DEV、TEST、PRO等)中,选择一种好的打包方式,将会在使用中无形的减少不少工作量,同时也会带来很多方便之处。反之,没有选择好打包方式,则会带来诸多不变之处。 下面我将介绍几种JAVA工厂常见的打包方式,先上效果图:

整体目录结构和物料包 自动生成的脚本目录 自动拷贝的配置目录

2 spring-boot-maven-plugin

spring-boot-maven-plugin是Springboot提供的打包插件,直接在POM中添加一下Plugin即可:

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>cn.micro.demo.DemoApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
</build>

3 自定义打包脚本

如果你的脚本编写能力很高,那完全可以考虑自己编写一套适合自己的打包脚本出来,不过因为难度和复杂度都较大所以不建议自己编写,等常用的方式解决不了你的需求时再考虑。

4 IDEA手动打包

常用的IDEA开发工具都是可以可视化界面打包部署包的,不过这种方式不利于CI/CD流程,所以也不推荐。

5 JSW+Assembly

JSW+Assembly是JAVA工程MAVEN通用打包方式。精简方式则直接使用:appassembler-maven-plugin和maven-assembly-plugin即可完成打包。但为了更好的效果,我将引入几个好用的插件来配合完成打包。

5.1 maven-compiler-plugin

maven-compiler-plugin插件主要用于指定编译时的JDK版本。

<!-- Specify JDK compiler version -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

5.2 maven-jar-plugin

maven-jar-plugin插件主要用于项目打包时,排除配置文件不打包进jar包中(如果配置打入了jar,则每次变更配置,都需要重新打包,很不方便)。

<!-- Specify the configuration files that do not need to be packaged into the jar package -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <!-- The exclusion rule is recommended to be consistent with the import rules in the assembly.xml file -->
        <excludes>
            <!-- Custom configuration -->
            <exclude>*.yml</exclude>
            <exclude>*.xml</exclude>
            <exclude>*.properties</exclude>
            <exclude>static/**</exclude>
            <!-- Must be configured -->
            <exclude>*.conf</exclude>
            <exclude>tools/**</exclude>
        </excludes>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>jar</goal>
            </goals>
        </execution>
    </executions>
</plugin>

5.3 maven-enforcer-plugin

maven-enforcer-plugin插件主要用于打包时检测各种规范要求,如:检查JDK版本、MAVEN版本、不能依赖快照等功能。

<!-- Use enforcer to mandatory agreement rule: mvn validate -->
<!-- mvn clean install -Denforcer.skip=true -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.0.0-M2</version>
    <executions>
        <execution>
            <id>default-cli</id>
            <goals>
                <goal>enforce</goal>
            </goals>
            <phase>validate</phase>
            <configuration>
                <rules>
                    <requireMavenVersion>
                        <message>
                            <![CDATA[You are running an older version of Maven. This application requires at least Maven ${maven.version}.]]>
                        </message>
                        <version>[3.3.3,)</version>
                    </requireMavenVersion>
                    <requireJavaVersion>
                        <message>
                            <![CDATA[You are running an older version of Java. This application requires at least JDK ${java.version}.0.]]>
                        </message>
                        <version>[1.8.0,)</version>
                    </requireJavaVersion>
                    <requireReleaseVersion>
                        <message>No Snapshots Allowed!</message>
                    </requireReleaseVersion>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

5.4 git-commit-id-plugin

git-commit-id-plugin用于将当前GIT项目的Commit日志记录打包成一个文件,便于查看当前物料包的历史GIT日志(如使用场景:线上跑的代码,想看看是否提交了某个BUG的修复记录)。

<!-- Git commit change log plugin -->
<plugin>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>revision</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <verbose>true</verbose>
        <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
        <generateGitPropertiesFile>true</generateGitPropertiesFile>
        <generateGitPropertiesFilename>${project.build.directory}/dist/jsw/app/conf/git.properties</generateGitPropertiesFilename>
    </configuration>
</plugin>

5.5 appassembler-maven-plugin

appassembler-maven-plugin用于将当前项目打包成个性化的目录框架,并同时使用JSW(Java Service Wrapper)生成各种操作系统的运维脚本(启动、停止、查看状态、重启等等命令),并且赋予可执行的权限等操作。同时也可以指定JMX端口、远程DEBUG端口、GC配置、GC日志、JVM配置和内存溢出Dump等信息。

<!-- Packing command: mvn clean package appassembler:generate-daemons -Dmaven.test.skip=true -->
<!-- Using JSW services to create scaffolding for target material packages -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>appassembler-maven-plugin</artifactId>
    <version>2.0.0</version>
    <configuration>
        <encoding>UTF-8</encoding>
        <binFolder>bin</binFolder>
        <tempDirectory>tmp</tempDirectory>
        <logsDirectory>logs</logsDirectory>
        <repositoryName>lib</repositoryName>
        <repositoryLayout>flat</repositoryLayout>
        <target>${project.build.directory}/dist</target>
        <configurationDirectory>conf</configurationDirectory>
        <copyConfigurationDirectory>true</copyConfigurationDirectory>
        <configurationSourceDirectory>src/main/resources</configurationSourceDirectory>
        <daemons>
            <daemon>
                <id>app</id>
                <!-- Main Class -->
                <mainClass>cn.micro.demo.Main</mainClass>
                <platforms>
                    <!-- Java Service Wrapper -->
                    <platform>jsw</platform>
                </platforms>
                <generatorConfigurations>
                    <generatorConfiguration>
                        <generator>jsw</generator>
                        <includes>
                            <include>aix-ppc-32</include>
                            <include>aix-ppc-64</include>
                            <include>linux-ppc-64</include>
                            <include>linux-x86-32</include>
                            <include>linux-x86-64</include>
                            <include>windows-x86-32</include>
                            <include>windows-x86-64</include>
                            <include>hpux-parisc-64</include>
                            <include>solaris-x86-32</include>
                            <include>solaris-sparc-32</include>
                            <include>solaris-sparc-64</include>
                            <include>macosx-ppc-32</include>
                            <include>macosx-universal-32</include>
                            <include>macosx-universal-64</include>
                        </includes>
                        <configuration>
                            <property>
                                <name>configuration.directory.in.classpath.first</name>
                                <value>conf</value>
                            </property>
                            <property>
                                <name>wrapper.ping.timeout</name>
                                <value>120</value>
                            </property>
                            <property>
                                <name>set.default.REPO_DIR</name>
                                <value>lib</value>
                            </property>
                            <property>
                                <name>wrapper.logfile</name>
                                <value>logs/wrapper.log</value>
                            </property>
                        </configuration>
                    </generatorConfiguration>
                </generatorConfigurations>
                <jvmSettings>
                    <!-- JMX -->
                    <systemProperties>
                        <systemProperty>java.security.policy=conf/policy.all</systemProperty>
                        <systemProperty>com.sun.management.jmxremote</systemProperty>
                        <systemProperty>com.sun.management.jmxremote.port=8777</systemProperty>
                        <systemProperty>com.sun.management.jmxremote.authenticate=false</systemProperty>
                        <systemProperty>com.sun.management.jmxremote.ssl=false</systemProperty>
                    </systemProperties>
                    <!-- https://blog.csdn.net/wo541075754/article/details/75008617 -->
                    <!-- https://zhaoyanblog.com/archives/440.html -->
                    <!-- https://www.javadoop.com/post/g1 -->
                    <extraArguments>
                        <extraArgument>-server</extraArgument>
                        <!-- Remote Debug -->
                        <extraArgument>-Xdebug</extraArgument>
                        <extraArgument>-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5010</extraArgument>
                        <!-- Heap Dump -->
                        <extraArgument>-XX:+HeapDumpOnOutOfMemoryError</extraArgument>
                        <extraArgument>-XX:HeapDumpPath=logs/heap-dump.hprof</extraArgument>
                        <!-- GC Config -->
                        <extraArgument>-XX:+UseG1GC</extraArgument>
                        <extraArgument>-XX:MaxGCPauseMillis=200</extraArgument>
                        <extraArgument>-XX:InitiatingHeapOccupancyPercent=45</extraArgument>
                        <extraArgument>-XX:G1ReservePercent=10</extraArgument>
                        <extraArgument>-XX:NewRatio=2</extraArgument>
                        <extraArgument>-XX:SurvivorRatio=8</extraArgument>
                        <extraArgument>-XX:MaxTenuringThreshold=15</extraArgument>
                        <!-- GC Log -->
                        <extraArgument>-Xloggc:logs/gc.log</extraArgument>
                        <extraArgument>-XX:GCLogFileSize=10M</extraArgument>
                        <extraArgument>-XX:NumberOfGCLogFiles=10</extraArgument>
                        <extraArgument>-XX:+UseGCLogFileRotation</extraArgument>
                        <extraArgument>-XX:+PrintGCDateStamps</extraArgument>
                        <extraArgument>-XX:+PrintGCTimeStamps</extraArgument>
                        <extraArgument>-XX:+PrintGCDetails</extraArgument>
                        <extraArgument>-XX:+PrintHeapAtGC</extraArgument>
                        <extraArgument>-XX:+PrintGCApplicationStoppedTime</extraArgument>
                        <extraArgument>-XX:+DisableExplicitGC</extraArgument>
                        <extraArgument>-verbose:gc</extraArgument>
                    </extraArguments>
                </jvmSettings>
            </daemon>
        </daemons>
    </configuration>
    <executions>
        <execution>
            <id>generate-jsw-scripts</id>
            <phase>package</phase>
            <goals>
                <goal>generate-daemons</goal>
            </goals>
        </execution>
    </executions>
</plugin>

5.6 maven-assembly-plugin

maven-assembly-plugin插件用于将上述appassembler-maven-plugin插件打包后的目录框架,再次打包为压缩包,便于在不同环境中进行拷贝操作。

<!-- Use assembly to package the scaffolding directory into compressed packets -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <descriptors>
            <descriptor>src/main/resources/tools/assembly.xml</descriptor>
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

完整Plugin如下:

<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <!-- Specify JDK compiler version -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <!-- Specify the configuration files that do not need to be packaged into the jar package -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- The exclusion rule is recommended to be consistent with the import rules in the assembly.xml file -->
                    <excludes>
                        <!-- Custom configuration -->
                        <exclude>*.yml</exclude>
                        <exclude>*.xml</exclude>
                        <exclude>*.properties</exclude>
                        <exclude>static/**</exclude>
                        <!-- Must be configured -->
                        <exclude>*.conf</exclude>
                        <exclude>tools/**</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- Use enforcer to mandatory agreement rule: mvn validate -->
            <!-- mvn clean install -Denforcer.skip=true -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>3.0.0-M2</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                        <phase>validate</phase>
                        <configuration>
                            <rules>
                                <requireMavenVersion>
                                    <message>
                                        <![CDATA[You are running an older version of Maven. This application requires at least Maven ${maven.version}.]]>
                                    </message>
                                    <version>[${maven.version},)</version>
                                </requireMavenVersion>
                                <requireJavaVersion>
                                    <message>
                                        <![CDATA[You are running an older version of Java. This application requires at least JDK ${java.version}.0.]]>
                                    </message>
                                    <version>[${java.version}.0,)</version>
                                </requireJavaVersion>
                                <!--
                                <requireReleaseVersion>
                                    <message>No Snapshots Allowed!</message>
                                </requireReleaseVersion>
                                -->
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- Git commit change log plugin -->
            <plugin>
                <groupId>pl.project13.maven</groupId>
                <artifactId>git-commit-id-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>revision</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <verbose>true</verbose>
                    <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
                    <generateGitPropertiesFile>true</generateGitPropertiesFile>
                    <generateGitPropertiesFilename>${project.build.directory}/dist/jsw/app/conf/git.properties
                    </generateGitPropertiesFilename>
                </configuration>
            </plugin>
            <!-- Packing command: mvn clean package appassembler:generate-daemons -Dmaven.test.skip=true -->
            <!-- Using JSW services to create scaffolding for target material packages -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
                <version>2.0.0</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <binFolder>bin</binFolder>
                    <tempDirectory>tmp</tempDirectory>
                    <logsDirectory>logs</logsDirectory>
                    <repositoryName>lib</repositoryName>
                    <repositoryLayout>flat</repositoryLayout>
                    <target>${project.build.directory}/dist</target>
                    <configurationDirectory>conf</configurationDirectory>
                    <copyConfigurationDirectory>true</copyConfigurationDirectory>
                    <configurationSourceDirectory>src/main/resources</configurationSourceDirectory>
                    <daemons>
                        <daemon>
                            <id>app</id>
                            <!-- Main Class -->
                            <mainClass>cn.micro.biz.Main</mainClass>
                            <platforms>
                                <!-- Java Service Wrapper -->
                                <platform>jsw</platform>
                            </platforms>
                            <generatorConfigurations>
                                <generatorConfiguration>
                                    <generator>jsw</generator>
                                    <includes>
                                        <include>aix-ppc-32</include>
                                        <include>aix-ppc-64</include>
                                        <include>linux-ppc-64</include>
                                        <include>linux-x86-32</include>
                                        <include>linux-x86-64</include>
                                        <include>windows-x86-32</include>
                                        <include>windows-x86-64</include>
                                        <include>hpux-parisc-64</include>
                                        <include>solaris-x86-32</include>
                                        <include>solaris-sparc-32</include>
                                        <include>solaris-sparc-64</include>
                                        <include>macosx-ppc-32</include>
                                        <include>macosx-universal-32</include>
                                        <include>macosx-universal-64</include>
                                    </includes>
                                    <configuration>
                                        <property>
                                            <name>configuration.directory.in.classpath.first</name>
                                            <value>conf</value>
                                        </property>
                                        <property>
                                            <name>wrapper.ping.timeout</name>
                                            <value>120</value>
                                        </property>
                                        <property>
                                            <name>set.default.REPO_DIR</name>
                                            <value>lib</value>
                                        </property>
                                        <property>
                                            <name>wrapper.logfile</name>
                                            <value>logs/wrapper.log</value>
                                        </property>
                                    </configuration>
                                </generatorConfiguration>
                            </generatorConfigurations>
                            <jvmSettings>
                                <!-- JMX -->
                                <!--
                                <systemProperties>
                                    <systemProperty>java.security.policy=conf/policy.all</systemProperty>
                                    <systemProperty>com.sun.management.jmxremote</systemProperty>
                                    <systemProperty>com.sun.management.jmxremote.port=8777</systemProperty>
                                    <systemProperty>com.sun.management.jmxremote.authenticate=false</systemProperty>
                                    <systemProperty>com.sun.management.jmxremote.ssl=false</systemProperty>
                                </systemProperties>
                                 -->
                                <!-- https://blog.csdn.net/wo541075754/article/details/75008617 -->
                                <!-- https://zhaoyanblog.com/archives/440.html -->
                                <!-- https://www.javadoop.com/post/g1 -->
                                <extraArguments>
                                    <extraArgument>-server</extraArgument>
                                    <!-- Remote Debug -->
                                    <extraArgument>-Xdebug</extraArgument>
                                    <!--
                                    <extraArgument>-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5010</extraArgument>
                                    -->
                                    <!-- Heap Dump -->
                                    <extraArgument>-XX:+HeapDumpOnOutOfMemoryError</extraArgument>
                                    <extraArgument>-XX:HeapDumpPath=logs/heap-dump.hprof</extraArgument>
                                    <!-- GC Config -->
                                    <extraArgument>-XX:+UseG1GC</extraArgument>
                                    <extraArgument>-XX:MaxGCPauseMillis=200</extraArgument>
                                    <extraArgument>-XX:InitiatingHeapOccupancyPercent=45</extraArgument>
                                    <extraArgument>-XX:G1ReservePercent=10</extraArgument>
                                    <extraArgument>-XX:NewRatio=2</extraArgument>
                                    <extraArgument>-XX:SurvivorRatio=8</extraArgument>
                                    <extraArgument>-XX:MaxTenuringThreshold=15</extraArgument>
                                    <!-- GC Log -->
                                    <extraArgument>-Xloggc:logs/gc.log</extraArgument>
                                    <extraArgument>-XX:GCLogFileSize=10M</extraArgument>
                                    <extraArgument>-XX:NumberOfGCLogFiles=10</extraArgument>
                                    <extraArgument>-XX:+UseGCLogFileRotation</extraArgument>
                                    <extraArgument>-XX:+PrintGCDateStamps</extraArgument>
                                    <extraArgument>-XX:+PrintGCTimeStamps</extraArgument>
                                    <extraArgument>-XX:+PrintGCDetails</extraArgument>
                                    <extraArgument>-XX:+PrintHeapAtGC</extraArgument>
                                    <extraArgument>-XX:+PrintGCApplicationStoppedTime</extraArgument>
                                    <extraArgument>-XX:+DisableExplicitGC</extraArgument>
                                    <extraArgument>-verbose:gc</extraArgument>
                                </extraArguments>
                            </jvmSettings>
                        </daemon>
                    </daemons>
                </configuration>
                <executions>
                    <execution>
                        <id>generate-jsw-scripts</id>
                        <phase>package</phase>
                        <goals>
                            <goal>generate-daemons</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- Use assembly to package the scaffolding directory into compressed packets -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <descriptors>
                        <descriptor>src/main/resources/tools/assembly.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

assembly.xml文件内容为:

<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
          http://maven.apache.org/xsd/assembly-2.0.0.xsd">
    <id>dist</id>
    <formats>
        <format>tar.gz</format>
    </formats>
    <includeBaseDirectory>true</includeBaseDirectory>

    <fileSets>
        <fileSet>
            <directory>target/dist/jsw/app/bin</directory>
            <outputDirectory>bin</outputDirectory>
            <!-- Set executable permissions -->
            <fileMode>0755</fileMode>
            <directoryMode>0755</directoryMode>
        </fileSet>
        <fileSet>
            <directory>target/dist/jsw/app/conf/tools</directory>
            <outputDirectory>bin</outputDirectory>
            <fileMode>0755</fileMode>
            <directoryMode>0755</directoryMode>
            <includes>
                <include>**.sh</include>
                <include>**.bat</include>
            </includes>
        </fileSet>
        <fileSet>
            <directory>target/dist/jsw/app/conf</directory>
            <outputDirectory>conf</outputDirectory>
            <!-- The import rules here recommend consistency with exclusionary rules in the pom.xml file -->
            <includes>
                <!-- Custom configuration -->
                <include>*.yml</include>
                <include>*.xml</include>
                <include>*.properties</include>
                <include>static/**</include>
                <!-- Must be configured -->
                <include>*.conf</include>
            </includes>
            <excludes>
                <!-- Must be configured -->
                <exclude>tools/**</exclude>
            </excludes>
            <fileMode>0644</fileMode>
            <directoryMode>0744</directoryMode>
        </fileSet>
        <fileSet>
            <directory>target/dist/jsw/app/lib</directory>
            <outputDirectory>lib</outputDirectory>
            <fileMode>0644</fileMode>
            <directoryMode>0744</directoryMode>
        </fileSet>
        <fileSet>
            <directory>target/dist/jsw/app/logs</directory>
            <outputDirectory>logs</outputDirectory>
            <fileMode>0644</fileMode>
            <directoryMode>0744</directoryMode>
        </fileSet>
        <fileSet>
            <directory>target/dist/jsw/app/tmp</directory>
            <outputDirectory>tmp</outputDirectory>
            <fileMode>0644</fileMode>
            <directoryMode>0744</directoryMode>
        </fileSet>
    </fileSets>
</assembly>

5.7 打包命令

mvn clean package appassembler:generate-daemons -Dmaven.test.skip=true

打包之后的目录结构为:

整体目录结构和物料包 自动生成的脚本目录 自动拷贝的配置目录
上一篇下一篇

猜你喜欢

热点阅读