一起玩儿-Spring Boot微服务(1.3 微服务的进阶改造)

1.3 微服务的进阶改造

1.3.1 在 application.yml 中加入基础配置

# Tomcat
server:
  tomcat:
    uri-encoding: UTF-8
    max-threads: 1000
    min-spare-threads: 30
  port: 8080
  connection-timeout: 5000

1.3.2 把页面等静态资源从jar包中挪出去

上一节做的微服务,将html、css、js、图片等静态资源都放在了 resources/static 文件夹下。这个文件夹是SpringBoot默认的文件夹,只要把静态文件放在这里,内置的Tomcat就可以解析这些文件,供客户端访问。
但是,这样做会把所有的前端页面等静态资源都打包到了jar包中,后续对页面的修改、维护就不是很方便了,每次都要动jar包。
那么SpringBoot是否支持将静态资源文件夹指定到jar包之外呢?
是可以的,可以按照如下操作。
修改 application.yml 文件的配置,在 spring 标签后面加入如下内容:

spring:
  resources:
    static-locations: file:/data/home/zhangzhen/IdeaProjects/core/page/

改完之后,类似这样。


注意:此处路径可以配置classpath下的内容(jar包中的路径),也可以配置当前服务器中文件系统的绝对路径(file:开头)。目前我还没找到配置相对路径的方法,如果有哪位同学研究出来了,可以通过回复告诉我,谢谢。

当前配置的含义:在我本地工程的根目录下,建立了一个名为 page的文件夹,SpringBoot所有对静态文件解析的路径,均首先从这个路径开始。
将原来在static下的文件、文件夹都挪到这个新建的page文件夹下。如下图的效果。

这个时候,还要修改一下原来的 index.html 页面的代码,调整一下引入资源文件的url。修改好的代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

  <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
        <link rel="stylesheet" href="/login/fashion/css/style.css">
  </head>

  <body>
    <div class="cont">
      <div class="demo">
        <div class="login">
            <div class="navbar-brand">
            <!-- LOGO图片尺寸:250*60;位置:images/logo/logo.png -->
            </div>
            <div class="system-name">
                <strong><h3  id="system_name" /></strong>
                <h4 id="slogan" />
            </div>

          <div class="login__form">

          <form role="form" action="../../login.do" method="post" class="registration-form">

            <div class="login__row">
              <svg class="login__icon name svg-icon" viewBox="0 0 20 20">
                <path d="M0,20 a10,8 0 0,1 20,0z M10,0 a4,4 0 0,1 0,8 a4,4 0 0,1 0,-8" />
              </svg>
              <input id="user_name" name="user_name" type="text" class="login__input name" placeholder="" onkeydown="KeyDownTab()" />
            </div>
            <div class="login__row">
              <svg class="login__icon pass svg-icon" viewBox="0 0 20 20">
                <path d="M0,20 20,20 20,8 0,8z M10,13 10,16z M4,8 a6,8 0 0,1 12,0" />
              </svg>
              <input  id="pass_word" name="pass_word" type="password" class="login__input pass" placeholder="" onkeydown="KeyDown()" />
            </div>
            <div class="">
                <select id="lang" name="lang" onchange="language_change()" class="lang_select">
                    <option value="zh_CN" selected="selected">简体中文</option>
                    <option value="zh_TW">繁体中文</option>
                    <option value="en_US">English</option>
                </select>

            </div>
            <button id="login" type="submit" class="login__submit"></button>

          </form>

          </div>
        </div>
      </div>
    </div>

    <script type="text/javascript" src="/javascripts/enter2tab.js"></script>
    <script type="text/javascript" src="/javascripts/jquery-1.11.1.js"></script>
    <script type="text/javascript" src="/javascripts/urlutil.js"></script>

    <script type="text/javascript">
        document.getElementById("user_name").focus();

        // 为“语言”选择框赋值,为传入的额语言参数值
        var lang = $.getUrlParam('lang');
         console.log("lang="+lang);
        if(lang == null){
            lang = "zh_CN";
        }
        $("#lang").val(lang);

        var mlangJson;
        $.ajaxSettings.async = false;//在执行之前加$.ajaxSettings.async = false;  (同步执行)
        // 获取后端多语言信息,更新页面标签文字
        $.getJSON("/api/login/getmsg", function(json){
            mlangJson = json;
            console.log("json=");
            console.log(json);
        });
        $.ajaxSettings.async = true;//执行你的代码之后及时恢复为$.ajaxSettings.async = true; (异步执行)

        console.log("mlangJson=");
        console.log(mlangJson);

        $("title").text(mlangJson.system_name);

        $("#system_name").text(mlangJson.system_name);
        $("#slogan").text(mlangJson.slogan);
        $("#user_name").attr("placeholder", mlangJson.user_name);
        $("#pass_word").attr("placeholder", mlangJson.pass_word);
        $("#login").text(mlangJson.login);

        // 如果用户已被锁定,则弹出提示
        if ("lock" == $.getUrlParam("err_flag") ) {
            alert(mlangJson.today_user_is_locked);
        }

        // 如果用户登录失败,则弹出提示
        if ("npe" == $.getUrlParam("err_flag") ) {
            alert(mlangJson.user_name_or_pass_word_is_error + mlangJson.login_times_left + $.getUrlParam("errtimesleft") );
            document.getElementById("user_name").focus();
        }

        // 如果用户已经超出许可,则弹出提示
        if ("userout" == $.getUrlParam("err_flag") ) {
            alert(mlangJson.online_user_is_out_reg );
            document.getElementById("user_name").focus();
        }



        function language_change(){
            window.location.href="?lang="+document.getElementById("lang").value;
        }

    </script>

  </body>
</html>

最后,修改一下我们最开始做的 Index.java ,调整一下登陆后重定向的路径,去掉原有跳转路径中的page,使其可以直接返回 login 那一层的路径。

package com.thinkdifferent.core.system.user.controller;

import com.thinkdifferent.core.system.systemset.entity.SystemSet;
import com.thinkdifferent.core.system.systemset.service.SystemSetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class Index {
    @Autowired
    private SystemSetService systemSetService;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(){
        SystemSet systemSet = systemSetService.getSystemSet();
        // 就是这句,去掉返回值中的 page 字符串,剩下如下的部分即可
        return "/login/"+systemSet.getLogin_page()+"/index.html";
    }

}

好的,我们再测试一下,能访问登录页,并且能正常切换多语言,就OK了。

1.3.3 加入log日志功能

接下来,我们给这个微服务加入日志记录的功能。

本例中,我们使用slf4j配合log4j2实现日志记录的功能。
为什么要使用slf4j呢?以下解释一下这事的原理。(下文来自互联网)

SLF4J(Simple logging Facade for Java)意思为简单日志门面,它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接口方法进行调用即可,由于它只是一个接口,并不是一个具体的可以直接单独使用的日志框架,所以最终日志的格式、记录级别、输出方式等都要通过接口绑定的具体的日志系统来实现,这些具体的日志系统就有log4j,logback,java.util.logging等,它们才实现了具体的日志系统的功能。
其实slf4j原理很简单,他只提供一个核心slf4j api(就是slf4j-api.jar包),这个包只有日志的接口,并没有实现,所以如果要使用就得再给它提供一个实现了些接口的日志包,比 如:log4j,common logging,jdk log日志实现包等,但是这些日志实现又不能通过接口直接调用,实现上他们根本就和slf4j-api不一致,因此slf4j又增加了一层来转换各日志实现包的使用。

先修改pom.xml文件,修改 dependencies 标签中的内容。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions><!-- 去掉springboot默认配置,避免冲突 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 引入log4j2依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <scope>provided</scope>
        </dependency>

这里,我们可以看到,在引入的log4j2的包之后,我们还引入了lombok的包,并没有直接引入slf4j,为什么呢?
如果不想每次都写private final Logger logger = LoggerFactory.getLogger(当前类名.class); 可以用注解@Slf4j;
而这个注解,就在lombok的包里。

然后,我们就可以在需要写日志的代码中,引入log对象了。
我们先用 Index.java 这个类做实验。

package com.thinkdifferent.core.system.user.controller;

import com.thinkdifferent.core.system.systemset.entity.SystemSet;
import com.thinkdifferent.core.system.systemset.service.SystemSetService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@Slf4j
public class Index {
    @Autowired
    private SystemSetService systemSetService;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(){
        log.debug("debug message");
        log.warn("warn message");
        log.info("info message");
        log.error("error message");
        log.trace("trace message");

        SystemSet systemSet = systemSetService.getSystemSet();
        log.debug("Login Page Path is : " + systemSet.getLogin_page()) ;

        return "/login/"+systemSet.getLogin_page()+"/index.html";
    }

}

接下来,我们修改配置文件 application.yml ,加入日志相关的设置

# log4j2设置
logging:
  config: log4j2.xml
  level:
    com.thinkdifferent.core: trace

然后,在工程的根文件夹下创建一个新的 log4j2.xml 文件(UTF-8),如下图。

配置内容如下,可供参考。

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
        <!-- 定义日志存储的路径 -->
        <property name="FILE_PATH" value="/data/home/zhangzhen/IdeaProjects/core/log" />
        <property name="FILE_NAME" value="core" />
    </Properties>

    <appenders>

        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>

        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

    </appenders>

    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>

        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--监控系统信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>

</configuration>

启动服务,好了,在我们设置的日志文件夹中,出现了不同级别的几个日志文件,证明日志功能已经正常工作了。

1.3.4 加入SpringFox(swagger),REST接口文档化

springfox是通过注解的形式自动生成API文档,利用它可以很方便的书写restful API,swagger主要用于展示springfox生成的API文档。

本例中,直接引入 SpringFox3。操作如下。

修改 pom.xml ,在dependencies中加入如下内容,更新项目。

        <!-- springfox-swagger工具包 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

修改启动类,在头部加入 @EnableOpenApi 标签。修改后的代码如下。

package com.thinkdifferent.core;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.oas.annotations.EnableOpenApi;

@SpringBootApplication
@EnableOpenApi
public class CoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(CoreApplication.class, args);
    }

}

然后在 Controller 层的代码中引入@Api、@ApiOperation、@ApiImplicitParams、@ApiImplicitParam等注解。

  • @Api:此java类的说明
  • @ApiOperation:接口方法的说明。格式如:

@ApiOperation(value = "一个测试API", notes = "第一个测试API")

  • @ApiImplicitParams、@ApiImplicitParam:接口参数说明。格式如:

@ApiImplicitParams({
@ApiImplicitParam(name = "blogArticleBeen", value = "文档对象", required = true, paramType = "body", dataType = "BlogArticleBeen"),
@ApiImplicitParam(name = "path", value = "url上的数据", required = true, paramType = "path", dataType = "Long"),
@ApiImplicitParam(name = "query", value = "query类型参数", required = true, paramType = "query", dataType = "String"),
@ApiImplicitParam(name = "apiKey", value = "header中的数据", required = true, paramType = "header", dataType = "String")
})

本例中,修改后端的 Index.java 代码如下:

package com.thinkdifferent.core.system.user.controller;

import com.thinkdifferent.core.system.systemset.entity.SystemSet;
import com.thinkdifferent.core.system.systemset.service.SystemSetService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Api(tags="登录页跳转")
@Controller
@Slf4j
public class Index {
    @Autowired
    private SystemSetService systemSetService;

    @ApiOperation("根据系统设置,跳转到对应的登录页")
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(){
        log.debug("debug message");
        log.warn("warn message");
        log.info("info message");
        log.error("error message");
        log.trace("trace message");

        SystemSet systemSet = systemSetService.getSystemSet();
        log.debug("Login Page Path is : " + systemSet.getLogin_page()) ;

        return "/login/"+systemSet.getLogin_page()+"/index.html";
    }

}

服务启动后,访问: http://127.0.0.1:8080/swagger-ui/index.html
即可看到如下界面。

可以在这里直接测试接口
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容