SpringCloud客户端项目构建(使用Freemarker)

背景

作为一个具备java基础的前端工程师,了解到Spring boot是下一代的web框架,而Spring Cloud则是最新最火的微服务的翘楚,有必要在此记录他的项目构建方案,本文适用于前端开发小组的项目搭建,该项目的特性是不作为服务端发布,作为客户端发布,主要基于Spring Cloud,围绕Freemarker技术进行相关的配置,使用讲解。

前提

请务必确保你的机器上安装了Eclipse Java EE IDE for Web Develipers.我的版本是Mars.2 Release(4.5.2),这将是特别重要的关键的一步, Eclipse在手,天下我有。

请务必一定程度的了解Spring Cloud模式和概念。

请务必一定程度的了解Freemarker功能使用方法。

步骤

下载

在Help-Eclipse MarketPlace的搜索下载sts(Spring Tools(aka Spring IDE and Spring Tool Suite)), 安装等待,成功后重启Eclipse。

构建项目

此时已经成功的下载sts,

第一步 创建基于MAVEN的SpringCloud项目

在Java EE模式下的Project Ecplorer窗口中,右键new-Other-Spring-Spring Starter Project。

作为前端小组开发的基于客户端的项目,构建的时候必须加载Eureka Discovery组件(服务注册,用于向Eureka Server注册,将客户端信息发送到Eureka Server),Feign组件(作为HTTP客户端调用远程HTTP服务),Ribbon组件(负载均衡)。

作为服务提供开发组的微服务项目,构建时在上面的基础上还需要加载Eureka Server组件(用于发布可调用的服务),Zuul组件(路由 )等…具体根据项目实际需求决定。

加载的组件,可在新建项目的第二步搜索勾选组件,也可自行配置pom.xml文件,如下(客户端开发小组项目配置示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
...
</dependencies>

客户端项目开发还需要使用Freemarker等其他基本库,则需要在pom.xml中添加配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- Freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Spring boot basic -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- alibaba -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.15</version>
</dependency>
<!-- java developers' basic jar 1-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!-- java developers' basic jar 2-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<!-- 企业封装工具包 -->
<dependency>
<groupId>com.emin</groupId>
<artifactId>base</artifactId>
<version>v1.8</version>
</dependency>

项目创建成功之后,选中项目名右键Maven-Update Project-勾选项目-ok。

Tips

a. 删除项目路径下的mvnw文件和mvnw.cmd文件

b. Maven配置找不到包,需要配置Maven的settings

配置User Settings的文件settings.xml,文件内容如下:

settings.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>E:/repo</localRepository>
<interactiveMode>true</interactiveMode>
<offline>false</offline>
<pluginGroups></pluginGroups>
<proxies></proxies>
<servers>
<server>
<id>dz-publish</id>
<username>dz-publish</username>
<password>dzpublish</password>
</server>
</servers>
<mirrors>
<mirror>
<id>dz-server-mirror</id>
<mirrorOf>*</mirrorOf>
<name>dz inner maven nexus server</name>
<url>http://192.168.0.250:10004/nexus/content/groups/public/</url>
</mirror>
</mirrors>
<profiles></profiles>
</settings>

您需要关心localRepository节点,mirrors-mirror-url节点的配置。
localRepository节点配置Maven将jar包加载在本地的那个磁盘路径里;
url节点配置Maven从何处去下载jar包;
配置信息根据企业实际项目情况约定。

修改生成你的配置文件,放置在本地的指定目录下;
Eclipse状态栏-Windows-Preferences-Maven-User Settings,找到你的配置文件详细路径配置在User Settings的文本框中-Apply-ok。

第二步 项目配置(客户端项目的应用配置)

项目创建成功后,你会发现该项目自动生成了一些文件,没用的我已经在上面建议你删除掉了,现在先介绍我们没有删掉的有用的文件。

目录1 src/main/javaz

该目录下的生成了一个XXXAppication.java的文件,这个文件用于项目启动。

启动方式 :选中该文件,右键Run As-Spring Boot App(建议在完成了第三步 目录资源整理后再尝试)

目录2 sec/main/resources

该目录下生成了一个application.properties的文件,这个文件用于项目配置。

配置方式:

为了配置信息使用树状结构展示更加清晰了然,建议您将properties后缀改成yml后缀。

配置项目合作的微服务中心地址
1
2
3
4
eureka:
client:
serviceUrl:
defaultZone: http://192.168.0.223:8761/eureka/
配置项目的端口号
1
2
server:
port: 8765
配置项目名称,Freemarker信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
application:
name: wxbase
freemarker:
allow-request-override: false
cache: true
checkTemplateLocation: true
charSet: UTF-8
contentType: text/html
exposeRequestAttributes: true
exposeSessionAttributes: true
exposeSpringMacroHelpers: false
suffix: .html
templateLoaderPath: classpath:/
prefer-file-system-access: false
settings:
templateExceptionHandler: ignor

++Freemarker配置说明++

templateLoaderPath对应的值(classpath:/)表示的是根目录,它用于后台服务返回页面的路径使用,在此有一个稍微的概念,后面具体使用了解。

suffix对应的值(.html)表示的是页面模板的后缀,有的项目使用ftl后缀,后台服务返回页面的时候就会找ftl后缀的页面,也可以理解为后台服务返回页面时自动加上了配置好的后缀。

至此,项目配置完成。

第三步 目录资源整理

重点1:我强烈的建议你将前端的css、js、图片等文件作为静态资源放置在src/main/resources下新建文件夹static目录下。

在XXXXApplication.java文件的同级目录下,新建我们此处命名为WebMvcConfig.java文件,它将是我们手动的编写一个拦截器,目的是在项目启动的时候,将静态资源的访问路径注册到根目录下(你也可以有其它的目的,根据你的项目实际情况来),内容如下:

WebMvcConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.emin.platform.wxbase;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configurable
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**")
.addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/css/**")
.addResourceLocations("classpath:/static/css/");
registry.addResourceHandler("/img/**")
.addResourceLocations("classpath:/static/img/");
}
}

完成了这个文件的编写,我们可以在前端的页面上通过/js、/css、/img去访问我们的静态资源,不会暴露它具体在哪个路径下。

@Configurable、@EnableWebMvc这两个注解会确保该文件在项目启动的时候被执行。

重点2:我们要在src/main下面新建webapp的文件夹,点击webapp文件夹,右键-Build Path-Use as Source Folder。

webapp文件夹下将会放置我们的项目开发的模块或者页面代码。

至于Use as Source Folder这个操作可以让我们后台通过’classpath:/‘直接访问webapp下面的文件,意思是:
用户发送请求到后台服务,后台服务经过一系列的数据处理,通过访问页面路径的形式将处理结果信息返回到页面上,’classpath:/‘将会是webapp这个文件夹的路径。
而上文中我们在application.yml中配置了templateLoaderPath的值为’classpath:/‘,那么此时webapp文件夹的访问路径就等于该项目的根’/‘。

举例说明:

在src/main/weapp目录下新建index.html, 内容如下:

index.html
1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
<title>主页</title>
</head>
<body>
hello world
</body>
</html>

在XXXXApplication.java的同级目录下新建controller包,在该包下new一个IndexController.java文件。内容如下:

IndexController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.emin.platform.wxbase.controller;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController
public class IndexController{
@ResponseBody
@RequestMapping(value = "/",method = RequestMethod.GET)
public ModelAndView index(HttpServletRequest request) throws UnsupportedEncodingException{
ModelAndView mv = new ModelAndView("index");
return mv;
}
}

该Controller将项目的根目录访问指向了index.html页面,’.html’的后缀是项目配置里配置好了的,能找到webapp下的index.html页面,也是因为我们将webapp文件夹配置为Use as Source Folder。

微服务合作开发

在项目配置里,我们配好了项目合作的微服务中心地址。
现在我们在controller同级目录建立一个interfaces包,此包下的文件用于合作服务接口开发。

举例

WxApiFeign.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.emin.platform.wxbase.interfaces;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "service-zuul")
public interface WxApiFeign {
/**
* 保存微信公众号
* @param wxOfficialAccount 微信公众实体JSON字符串
* @return
*/
@RequestMapping(value = "/api-wx/woa/createOrUpdate",method = RequestMethod.POST)
String saveWxoa(@RequestBody String wxOfficialAccount);
/**
* 加载微信公众号列表
* @param companyId 公司id
* @return
*/
@RequestMapping(value = "/api-wx/woa/{companyId}/woalist",method = RequestMethod.GET)
String loadWxoas(@PathVariable(value="companyId") Long companyId);
}

说明

@FeignClient(value = “service-zuul”)中的value值是合作方提供给你的,这是合作微服务项目的网关名称。

@RequestMapping(value = “/api-wx/woa/createOrUpdate”,method = RequestMethod.POST)中的value值是调用的具体接口路径,ip前缀地址就是我们已经项目配置中的++配置项目合作的微服务中心地址++,method值制定接口的访问方式。

到此,我们就建立好了微服务合作开发的连接。

然后我们新建WxoaController中使用WxApiFeign中的接口

代码片

WxoaController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
@Controller
@RequestMapping("/wxoa")
public class WxoaController extends BaseController {
@Autowired
WxApiFeign wxApiFeign;
@RequestMapping("/index")
@ResponseBody
public ModelAndView goManage(){
ModelAndView mv = new ModelAndView("modules/wxoa/manage");
String res = wxApiFeign.loadWxoas(1l);
JSONObject data = JSONObject.parseObject(res);
mv.addObject("data", data);
return mv;
}
@RequestMapping("/saveWxoa")
@ResponseBody
public JSONObject saveWxoa(String jsonStr){
String res = wxApiFeign.saveWxoa(jsonStr);
JSONObject json = JSONObject.parseObject(res);
return json;
}
}

以上,我们完成了微服务合作开发。

Freemarker在项目中的使用

Freemarker在项目中是一个数据模板开发的概念,如上WxoaController的goManage方法将会返回根目录下modules/wxoa/manage.html页面模板,通过ModelAndView对象装载了页面模板的地址和服务器的处理结果数据,我们在modules/wxoa/manage.html页面上解析处理结果数据的过程则使用了Freemarker,如下:

modules/wxoa/manage.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<style>
.devicesMsg{
padding:2px;
margin-right:15px;
background:#DADADA;
}
</style>
<#setting datetime_format='yyyy-MM-dd HH:mm:ss'>
<#setting date_format='yyyy-MM-dd'>
<div class="wrapper-content">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>微信公众号管理</h5>
<div class="ibox-tools">
<a class="btn btn-primary btn-xs" href="javascript:goPage('form')">
<i class="fa fa-plus"></i>添加
</a>
</div>
</div>
<div class="ibox-content">
<div class="filter-line">
<form method="get" class="dr-search-form" id="wxoaSearchForm">
<input type="hidden" name="page">
<input placeholder="关键字查询" class="form-control" type="text" name="keyword" value="${keyword!''}" role="user-params">
<button type="button" class="btn btn-primary" role="submit">搜索</button>
<button type="button" class="btn btn-default" role="reset">重置</button>
</form>
</div>
<table class="footable table table-stripped toggle-arrow-tiny" id="wxoaTable" data-paging="false">
<thead>
<tr>
<th data-toggle="true">企业名称</th>
<th>微信号</th>
<th>类型</th>
<th>appId</th>
<th>appSecret</th>
<th>Token</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<#if data?? && data.success>
<#list data.result as d>
<tr>
<td>${d.companyName}</td>
<td>${d.companyCode}</td>
<td><#if d.type==1>订阅<#elseif d.type==2>服务<#elseif d.type==3>企业<#else>小程序</#if></td>
<td>${d.appId}</td>
<td>${d.appSecret}</td>
<td>${d.token}</td>
<td>
<a href="javascript: goPage('form?id=${d.id}')" class="ipc-edit"><i class="fa fa-pencil text-navy"></i> 编辑&nbsp;&nbsp;</a>
<a href="javascript: remove('${d.id}', 'wxoa');" class="ipc-remove"><i class="fa fa-trash text-navy" ></i> 停用</a>
</td>
</tr>
</#list>
<#else>
<tr>
<td colspan="7">暂无数据</td>
</tr>
</#if>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

‘<#’开头的标签则是Freemarker语法。在上面的示例中Freemarker遍历了服务器的结果数据,将数据展示在了页面上,更多Freemarker的语法请参考更专业的文章。

结语

感谢您的阅读。