- new -> Moudule> maven 创建子项目
jl-web
- 在pom.xml 里添加 :
war - 添加
jl-common
依赖和跳过测试类打包的插件- 配置
application.yml
设置端口为8092
,定义springmvc视图解析器
- 导入静态资源文件
- 修改redis 配置文件 : 将
jl-manage
的redis.properties
文件赋值到jl-web中
- 设置
webapp
- 在springBootRun启动类中修改在启动时不要加载数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
- 在
Run Configurations
中添加jl-web的springboot启动项,并将working directory
设置为$MODULE_DIR$
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jlartifactId>
<groupId>com.jlgroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>jl-webartifactId>
<packaging>warpackaging>
<dependencies>
<dependency>
<groupId>com.jlgroupId>
<artifactId>jl-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-surefire-pluginartifactId>
<configuration>
<skip>trueskip>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8092
spring: #定义springmvc视图解析器
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
修改之后重启nginx
#配置前台服务器
server {
listen 80;
server_name www.jl.com;
location / {
proxy_pass http://localhost:8092;
}
}
2.2.2 修改hosts文件
127.0.0.1 www.jl.com
1.浏览器加载静态资源的速度更快. 浏览器自己有缓存.
2.静态页面更加容易被搜索引擎收录. 百度/谷歌
3.用户的交互性弱.
百度通过
倒排索引
的方式实现秒杀级查询
伪静态技术是指展示出来的是以html一类的静态页面形式,但其实是用ASP一类的动态脚本来处理的,即
以html结尾的动态页面
3.4 修改servlet拦截策略
Springboot中默认不拦截为.html结尾的请求.但是根据伪静态的要求,必须拦截以.html为结尾的请求.则需要修改默认的拦截配置
在jl-web中添加该配置
@Configuration //表示配置类
public class MvcConfigurer implements WebMvcConfigurer{
//开启匹配后缀型配置
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
}
}
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包
HttpClient 提供的主要的功能 :
(1)实现了所有 HTTP 的方法(GET,POST,PUT,DELETE 等)
(2)支持自动转向
(3)支持 HTTPS 协议
(4)支持代理服务器等
SpringCloud中服务和服务之间的调用全部是使用HttpClient 传送门
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
1.实例化httpClient对象
2.准备url请求地址 https://www.baidu.com/
3.封装请求方式对象 GET/POST/PUT/DELETE
4.发起http请求.获取服务器响应.
5.判断返回值状态码信息 200.
6.从响应对象中获取服务器返回值数据.
public class TestHttpClient {
@Test
public void testGet() throws ClientProtocolException, IOException {
CloseableHttpClient client = HttpClients.createDefault();
String url = "https://www.baidu.com";
HttpGet get = new HttpGet(url);
CloseableHttpResponse response = client.execute(get);
if(response.getStatusLine().getStatusCode() == 200) {
//表示请求服务正确
HttpEntity entity = response.getEntity();//返回值实体对象
String result = EntityUtils.toString(entity, "UTF-8");
System.out.println(result);
}
}
}
HttpClientConfig.java
和HttpClientClose.java
编辑
httpClient.properties
#最大连接数
http.maxTotal = 1000
#并发数
http.defaultMaxPerRoute = 20
#创建连接的最长时间
http.connectTimeout=5000
#从连接池中获取到连接的最长时间
http.connectionRequestTimeout=500
#数据传输的最长时间
http.socketTimeout=5000
#提交请求前测试连接是否可用
http.staleConnectionCheckEnabled=true
package com.jl.web.config;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource(value="classpath:/properties/httpClient.properties")
public class HttpClientConfig {
@Value("${http.maxTotal}")
private Integer maxTotal; //最大连接数
@Value("${http.defaultMaxPerRoute}")
private Integer defaultMaxPerRoute; //最大并发链接数
@Value("${http.connectTimeout}")
private Integer connectTimeout; //创建链接的最大时间
@Value("${http.connectionRequestTimeout}")
private Integer connectionRequestTimeout; //链接获取超时时间
@Value("${http.socketTimeout}")
private Integer socketTimeout; //数据传输最长时间
@Value("${http.staleConnectionCheckEnabled}")
private boolean staleConnectionCheckEnabled; //提交时检查链接是否可用
//定义httpClient链接池
@Bean(name="httpClientConnectionManager")
public PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
manager.setMaxTotal(maxTotal); //设定最大链接数
manager.setDefaultMaxPerRoute(defaultMaxPerRoute); //设定并发链接数
return manager;
}
//定义HttpClient
/**
* 实例化连接池,设置连接池管理器。
* 这里需要以参数形式注入上面实例化的连接池管理器
@Qualifier 指定bean标签进行注入
*/
@Bean(name = "httpClientBuilder")
public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){
//HttpClientBuilder中的构造方法被protected修饰,所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionManager(httpClientConnectionManager);
return httpClientBuilder;
}
/**
* 注入连接池,用于获取httpClient
* @param httpClientBuilder
* @return
*/
@Bean
public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder){
return httpClientBuilder.build();
}
/**
* Builder是RequestConfig的一个内部类
* 通过RequestConfig的custom方法来获取到一个Builder对象
* 设置builder的连接信息
* @return
*/
@Bean(name = "builder")
public RequestConfig.Builder getBuilder(){
RequestConfig.Builder builder = RequestConfig.custom();
return builder.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(connectionRequestTimeout)
.setSocketTimeout(socketTimeout)
.setStaleConnectionCheckEnabled(staleConnectionCheckEnabled);
}
/**
* 使用builder构建一个RequestConfig对象
* @param builder
* @return
*/
@Bean
public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){
return builder.build();
}
}
package com.jl.web.config;
import javax.annotation.PreDestroy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component //交给spring容器管理
public class HttpClientClose extends Thread{
@Autowired
private PoolingHttpClientConnectionManager manage;
private volatile boolean shutdown; //开关 volatitle表示多线程可变数据,一个线程修改,其他线程立即修改
public HttpClientClose() {
///System.out.println("执行构造方法,实例化对象");
//线程开启启动
this.start();
}
@Override
public void run() {
try {
//如果服务没有关闭,执行线程
while(!shutdown) {
synchronized (this) {
wait(5000); //等待5秒
//System.out.println("线程开始执行,关闭超时链接");
//关闭超时的链接
PoolStats stats = manage.getTotalStats();
int av = stats.getAvailable(); //获取可用的线程数量
int pend = stats.getPending(); //获取阻塞的线程数量
int lea = stats.getLeased(); //获取当前正在使用的链接数量
int max = stats.getMax();
//System.out.println("max/"+max+": av/"+av+": pend/"+pend+": lea/"+lea);
manage.closeExpiredConnections();
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
super.run();
}
//关闭清理无效连接的线程
@PreDestroy //容器关闭时执行该方法.
public void shutdown() {
shutdown = true;
synchronized (this) {
//System.out.println("关闭全部链接!!");
notifyAll(); //全部从等待中唤醒.执行关闭操作;
}
}
}
编辑工具API目的简化代码,实现松耦合
作用:帮助用户发起http请求,获取正确的结果返回给用户
参数设计:1.用户url地址 2.Map<参数名,参数值> 3.字符编码
package com.jl.web.util;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Service
public class HttpClientService {
@Autowired
private CloseableHttpClient httpClient;
@Autowired
private RequestConfig requestConfig;
public String doGet(String url,Map<String,String> params,String charset){
String result = null;
//1.判断字符集编码是否为空 如果为空则给定默认值utf-8
if(StringUtils.isEmpty(charset)){
charset = "UTF-8";
}
//2.判断用户是否需要传递参数
if(params != null){
try {
URIBuilder uriBuilder = new URIBuilder(url);
for (Map.Entry<String,String> entry : params.entrySet()) {
uriBuilder.addParameter(entry.getKey(),entry.getValue());
}
//url?id=1&name=tom
url = uriBuilder.build().toString();
} catch (Exception e) {
e.printStackTrace();
}
}
//3.定义参数提交对象
HttpGet get = new HttpGet(url);
//4.为请求设定超时时间
get.setConfig(requestConfig);
//5.通过httpClient发送请求
try {
CloseableHttpResponse response = httpClient.execute(get);
if(response.getStatusLine().getStatusCode() == 200){
//表示程序调用成功
result = EntityUtils.toString(response.getEntity(),charset);
}else{
System.out.println("调用异常:状态信息:"+response.getStatusLine().getStatusCode());
throw new RuntimeException();
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public String doGet(String url){
return doGet(url, null, null);
}
public String doGet(String url,Map<String,String> params){
return doGet(url, params, null);
}
public String doGet(String url,String charset){
return doGet(url, null, charset);
}
//实现httpClient POST提交
public String doPost(String url,Map<String,String> params,String charset){
String result = null;
//1.定义请求类型
HttpPost post = new HttpPost(url);
post.setConfig(requestConfig); //定义超时时间
//2.判断字符集是否为null
if(StringUtils.isEmpty(charset)){
charset = "UTF-8";
}
//3.判断用户是否传递参数
if(params !=null){
//3.2准备List集合信息
List<NameValuePair> parameters =
new ArrayList<>();
//3.3将数据封装到List集合中
for (Map.Entry<String,String> entry : params.entrySet()) {
parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
//3.1模拟表单提交
try {
UrlEncodedFormEntity formEntity =
new UrlEncodedFormEntity(parameters,charset); //采用u8编码
//3.4将实体对象封装到请求对象中
post.setEntity(formEntity);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//4.发送请求
try {
CloseableHttpResponse response =
httpClient.execute(post);
//4.1判断返回值状态
if(response.getStatusLine().getStatusCode() == 200) {
//4.2表示请求成功
result = EntityUtils.toString(response.getEntity(),charset);
}else{
System.out.println("获取状态码信息:"+response.getStatusLine().getStatusCode());
throw new RuntimeException();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
public String doPost(String url){
return doPost(url, null, null);
}
public String doPost(String url,Map<String,String> params){
return doPost(url, params, null);
}
public String doPost(String url,String charset){
return doPost(url, null, charset);
}
}
@Test
public void test01() throws ClientProtocolException, IOException{
CloseableHttpClient httpClient = HttpClients.createDefault(); //创建HTTPClient的实例
String url = "https://item.jd.com/5236335.html?dist=jd"; //定义访问IP
HttpPost httpPost = new HttpPost(url); //设定请求
CloseableHttpResponse response = httpClient.execute(httpPost); //获取response对象
String html = EntityUtils.toString(response.getEntity()); //获取页面信息
System.out.println(html);
}
@Test
public void testGet() throws ClientProtocolException, IOException{
//定义httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
String uri = "https://item.jd.com/5236335.html?dist=jd";
HttpGet httpGet = new HttpGet(uri);
//获取请求的全部参数 GET https://item.jd.com/5236335.html?dist=jd HTTP/1.1
RequestLine requestLine = httpGet.getRequestLine();
//获取请求方式 GET/POST
requestLine.getMethod();
//获取浏览器 http协议 HTTP/1.1
requestLine.getProtocolVersion();
//请求 uri:https://item.jd.com/5236335.html?dist=jd
requestLine.getUri();
//获取HTTP响应
CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
System.out.println("获取请成功");
//获取HTML
String html = EntityUtils.toString(httpResponse.getEntity());
System.out.println(html);
}
}
- 用户通过jl-web访问商品信息
- jl-web不连接数据库,需要通过jl-manage的服务器获取数据
- jl-manage接收到前台web的url请求时,向数据库获取数据
- 数据库成功获取数据之后,将数据转化为JSON返回给web.
实现商品页面跳转
页面url地址分析 :
www.jl.com/items/562379.html
编辑前台的Itemcontroller : 根据商品ID号查询后台商品数据
package com.jl.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.jl.pojo.Item;
import com.jl.pojo.ItemDesc;
import com.jl.service.ItemService;
@Controller //需要跳转页面.
@RequestMapping("/items")
public class ItemController {
@Autowired
private ItemService itemService;
@RequestMapping("/{itemId}")
public String toItems(@PathVariable Long itemId,Model model) {
//根据商品ID号查询后台商品数据
Item item = itemService.findItemById(itemId);
model.addAttribute("item", item);
ItemDesc itemDesc = itemService.findItemDescById(itemId);
model.addAttribute("itemDesc", itemDesc);
return "item"; //动态的商品展现页面
}
}
编辑前台ItemService接口中添加方法
编辑前台ItemServiceImpl实现
package com.jl.service;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.jl.anno.CacheFind;
import com.jl.pojo.Item;
import com.jl.pojo.ItemDesc;
import com.jl.util.HttpClientService;
import com.jl.util.ObjectMapperUtil;
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private HttpClientService httpClient;
//jl-web服务器
@Override
@CacheFind
public Item findItemById(Long itemId) {
//连接jl-manage中服务
String url = "http://manage.jl.com/web/item/findItemById";
Map<String,String> params = new HashMap<String, String>();
params.put("itemId", itemId+"");
String itemJson = httpClient.doGet(url,params);
return ObjectMapperUtil.toObject(itemJson, Item.class);
}
@Override
@CacheFind
public ItemDesc findItemDescById(Long itemId) {
//连接jl-manage中服务
String url = "http://manage.jl.com/web/item/findItemDescById";
Map<String,String> params = new HashMap<String, String>();
params.put("itemId", itemId+"");
String itemDescJSON = httpClient.doGet(url,params);
return ObjectMapperUtil.toObject(itemDescJSON,ItemDesc.class);
}
}
url请求 :
http://manage.jl.com/web/item/findItemById?itemId=562379
编辑后台的WebItemController
编辑jl-manage获取Item和itemdesc的JSON数据
@RestController
@RequestMapping("/web/item")
public class WebItemController {
@Autowired
private ItemService itemService;
@RequestMapping("/findItemById")
public Item findItemById(Long itemId) {
return itemService.findItemById(itemId);
}
//查询商品详情信息
@RequestMapping("/findItemDescById")
public ItemDesc findItemDescById(Long itemId) {
return itemService.findItemDescById(itemId);
}
}
增加findItemById方法,通过mybatis-plus实现
@Override
public Item findItemById(Long itemId) {
return itemMapper.selectById(itemId);
}
在前台的ItemServiceImpl里的
findItemById
和findItemDescById
方法上加@CacheFind
注解
利用浏览器解析JS,发起AJax请求,之后从后台服务器中获取数据
- 域名相同时可以实现访问
- 当域名与访问网址域名不同时,发现服务无法正常调用
后面的单点登录系统
SSO
将使用JSONP来实现跨域问题
浏览规定:如果满足 协议名称://域名:端口号等都相同时表示同域访问
但是如果上述条件中有一项不同.则表示跨域访问.浏览器不予解析返回值.(安全策略)
如下图是同源策略案例
- 利用javaScript中的src属性实现跨域.
- 定义回调函数.
- 将返回值结果进行特殊格式封装 callback(JSON数据)
传统跨域存在的问题:
- 发送跨域请求时利用javaScript中的src属性.这样的操作不通用.
- 自己手动定义回调函数.
- 返回的数据必须经过回调函数名称封装.但是名称如何保证一致
所以使用JSONP实现跨越
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。
利用
- 可以利用传统ajax实行实现数据请求
- 利用js自动生成一个回调函数.并且名称随意,发送请求时自动携带回调函数名称.
url?callback="XXXXXXXX"
- 动态获取回调函数名称,将数据进行封装
callback(JSON);
<script type="text/javascript">
$(function () {
alert("测试访问开始!!!!!")
$.ajax({
url: "http://manage.jl.com/web/testJSONP",
type: "get", //jsonp只能支持get请求
dataType: "jsonp", //dataType表示返回值类型
jsonp: "callback", //指定参数名称
jsonpCallback: "hello", //指定回调函数名称
success: function (data) { //data经过jQuery封装返回就是json串
alert(data.id);
alert(data.name);
}
});
})
script>
调用
JSONPObject(String function, Object value)
方法
function
为回调函数名称;value
为返回的对象数据这个API底层实现了 :
1.将对象转换为JSON :
String json = ObjectMapperUtil.toJSON(itemDesc);
2.拼接字符串 :
callback+"("+json+")"
//利用API实现JSONP跨域访问
@RequestMapping("/web/testJSONP")
public JSONPObject testJSONP(String callback) {
ItemDesc itemDesc = new ItemDesc();
itemDesc.setItemId(10001L).setItemDesc("商品详情信息!!!!!!");
return new JSONPObject(callback, itemDesc);
}
- 在浏览器地址栏中输入
view-source:sURL
就可以查看当前网页的源码- JS用法 :
window.location="view-source:" + window.location
异常信息:
解决方案:
//告知springBoot程序,启动时不要加载数据源配置.
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringBootRun {
public static void main(String[] args) {
SpringApplication.run(SpringBootRun.class,args);
}
}
说明 : 浏览器默认将http协议转化为https协议.
解决方案:
浏览器输入 :chrome://net-internals/#hsts
输入域名 点击delete 清空缓存 重启谷歌浏览器即可
说明:当tomcat服务器启动时,会启动spring容器要求立即注入对象
如果项目中有可能不需要该对象时,可以配置required = false 懒加载
@Autowired(required = false)
private JedisCluster jedis; //连接redis集群
说明 : 对象转化JSON数据时,必须调用对象的get方法.
getXXXX()----->去除get前置和()后缀------>XXXXX名称(小写)说明 : JSON串转化为对象时,调用对象的Set方法
总结:POJO中的get/set方法必须成对出现,则JSON转化正确
说明 : 通过@JsonIgnoreProperties注解实现忽略转化
在pojo类上加入
@JsonIgnoreProperties(ignoreUnknown=true)
表示JSON转化时忽略未知属性
JSONP 用于
get
请求,查询功能 ,不能用于post
请求,后台使用这个cros 手机端用的多,可以用于get和post请求,目前用的很少了
目前,前端都用
Vue
框架,直接调用工具就可以很简单的解决跨越问题
idea:
Ctrl+shift+F
Eclipse :
Ctrl+H
Ctrl+P