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
即可看到如下界面。

