1.小程序端
1.1 HttpClient
1.1.1 介绍
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
1.1.2 HttpClient作用
-
发送HTTP请求
-
接收响应数据
1.1.3 如何使用
HttpClient的maven坐标: 在之前在使用OSS时导入相关依赖,其中已经包含了HttpClient相关依赖,所以这部分可导可不导
java
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
HttpClient的核心API:
-
HttpClient: Http客户端对象类型,使用该类型对象可发起Http请求。
-
HttpClients:可认为是构建器,可创建HttpClient对象。
-
CloseableHttpClient:实现类,实现了HttpClient接口。
-
HttpGet :Get方式请求类型。
-
HttpPost: Post方式请求类型。
HttpClient发送请求步骤:
-
创建HttpClient对象
-
创建Http请求对象
-
调用HttpClient的execute方法发送请求
1.1.4 GET方式请求实现
-
创建HttpClient对象
-
创建请求对象
-
发送请求,接受响应结果
-
解析结果
-
关闭资源
编写测试代码:
java
@SpringBootTest
public class HttpClientTest {
/*
*测试通过httpclient发送GET请求
*/
@Test
public void testGET() throws IOException {
//创建httpclient对象
/*CloseableHttpClient是一个实现类,实现了接口httpClient中的方法*/
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象
HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
//发送请求,接受响应结果
CloseableHttpResponse response = httpClient.execute(httpGet);
//获取服务端返回的状态码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("服务端返回的状态码"+statusCode);
//获取服务端返回的数据
HttpEntity entity = response.getEntity();
String body = EntityUtils.toString(entity);
System.out.println("服务端返回的数据"+body);
//关闭资源
response.close();
httpClient.close();
}
1.1.5 POST方式请求实现
在HttpClientTest中添加POST方式请求方法,相比GET请求来说,POST请求若携带参数需要封装请求体对象,并将该对象设置在请求对象中。
实现步骤:
-
创建HttpClient对象
-
创建请求对象
-
发送请求,接收响应结果
-
解析响应结果
-
关闭资源
编写测试代码:
java
/*
*测试通过httpclient发送POST请求
*/
@Test
public void testPOST() throws IOException {
//创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象----设置参数
HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");
JSONObject jsonObject = new JSONObject();
jsonObject.put("username","admin");
jsonObject.put("password","123456");
StringEntity entity=new StringEntity(jsonObject.toString());
//指定请求编码方式
entity.setContentEncoding("utf-8");
//指定数据格式
entity.setContentType("application/json");
httpPost.setEntity(entity);
//发送请求
CloseableHttpResponse response = httpClient.execute(httpPost);
//解析返回结果
//获取响应码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("响应状态码"+statusCode);
//获取相应数据
HttpEntity entity1 = httpPost.getEntity();
String body = EntityUtils.toString(entity1);
System.out.println("响应数据"+body);
//关闭资源
response.close();
httpClient.close();
}
1.2 微信小程序开发
1.2.1介绍
**首先,**在进行小程序开发时,需要先去注册一个小程序,在注册的时候,它实际上又分成了不同的注册的主体。我们可以以个人的身份来注册一个小程序,当然,也可以以企业政府、媒体或者其他组织的方式来注册小程序。那么,不同的主体注册小程序,最终开放的权限也是不一样的。比如以个人身份来注册小程序,是无法开通支付权限的。若要提供支付功能,必须是企业、政府或者其它组织等。所以,不同的主体注册小程序后,可开发的功能是不一样的。
**然后,**微信小程序我们提供的一些开发的支持,实际上微信的官方是提供了一系列的工具来帮助开发者快速的接入 并且完成小程序的开发,提供了完善的开发文档,并且专门提供了一个开发者工具,还提供了相应的设计指南,同时也提供了一些小程序体验DEMO,可以快速的体验小程序实现的功能。
**最后,**开发完一个小程序要上线,也给我们提供了详细地接入流程。
1.2.2 小程序目录结构
小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。主体部分由三个文件组成,必须放在项目的根目录。
文件说明:
-
app.js : 必须存在,主要存放小程序的逻辑代码
-
app.json :必须存在,小程序配置文件,主要存放小程序的公共配置
-
app.wxss: 非必须存在,主要存放小程序公共样式表,类似于前端的CSS样式
-
js文件:必须存在,存放页面业务逻辑代码,编写的js代码。
-
json文件:非必须,存放页面相关的配置。
-
wxml 文件:必须存在,存放页面结构,主要是做页面布局,页面效果展示的,类似于HTML页面。
-
wxss文件:非必须,存放页面样式表,相当于CSS文件。
1.2.3 微信登录流程
- 微信登录流程 微信登录:小程序登录 | 微信开放文档
-
小程序端,调用wx.login()获取code,就是授权码。
-
小程序端,调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。
-
开发者服务端,通过HttpClient向微信接口服务发送请求,并携带appId+appsecret+code三个参数。
-
开发者服务端,接收微信接口服务返回的数据,session_key+opendId等。opendId是微信用户的唯一标识。
-
开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
-
小程序端,收到自定义登录态,存储storage。
-
小程序端,后绪通过wx.request()发起业务请求时,携带token。
-
开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
-
开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
说明:
1.调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
2.调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID (若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
1.2.4 配置微信登录所需配置项
注意!!!:为微信用户生成jwt令牌时使用的配置项:
java
sky:
jwt:
#user端
#设置jwt签名加密时使用的秘钥
user-secret-key: itheima
# 设置jwt过期时间
user-ttl: 144000000
# 设置前端传递过来的令牌名称
user-token-name: authentication
配置user端拦截器:
java
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/*
* 校验jwt
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//System.out.println("当前线程id为:"+Thread.currentThread().getId());
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getUserTokenName());
//2、校验令牌
try {
//log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前用户id:", userId);
//3、通过,放行
BaseContext.setCurrentId(userId);
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
在配置文件中配置拦截路径:
java
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
1.2.5 订单支付
1.2.5.1 微信支付相关接口:
JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)
下单请求示例:
XML
{
"mchid": "1900006XXX",
"out_trade_no": "1217752501201407033233368318",
"appid": "wxdace645e0bc2cXXX",
"description": "Image形象店-深圳腾大-QQ公仔",
"notify_url": "https://www.weixin.qq.com/wxpay/pay.php",
"amount": {
"total": 1,
"currency": "CNY"
},
"payer": {
"openid": "o4GgauInH_RCEdvrrNGrntXDuXXX"
}
}
微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)
调起支付请求示例:
XML
wx.requestPayment
(
{
"timeStamp": "1414561699",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx201410272009395522657a690389285100",
"signType": "RSA",
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",
"success":function(res){},
"fail":function(res){},
"complete":function(res){}
}
)
1.2.5.2 完成微信支付有两个关键的步骤:
-
第一个 就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。
-
第二个 就是支付成功之后微信后台会给推送消息。
解决:微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。
1.2.5.3 调用到商户系统
微信后台会调用到商户系统给推送支付的结果 ,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。
目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。
解决: 内网穿透**。通过** cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip**,这样,微信后台就可以请求到商户系统了。
1.2.6 内网穿透
1.2.6.1 cpolar指定authtoken
复制authtoken:
cpolar - secure introspectable tunnels to localhost
--->注册登录 --->验证---->复制自己的隧道 Authtoken
1.2.6.2 执行命令:
命令行输入: cpolar.exe authtoken +你的隧道authtoken
获取临时域名
执行命令:cpolar.exe http 8080
将得到的公网ip复制,放到配置文件中
java
sky:
wechat:
appid:
secret:
mchid : 1561414331
mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606
privateKeyFilePath: D:\apiclient_key.pem
apiV3Key: CZBK51236435wxpay435434323FFDuv3
weChatPayCertFilePath: D:\wechatpay_166D9公网ip+6F876F45C7D07CE98952A96EC980368ACFC.pem
#支付成功的回调地址
notifyUrl: 公网ip/notify/paySuccess
#退款成功的回调地址
refundNotifyUrl: 公网ip/notify/refundSuccess
验证临时域名有效性
启动项目,访问接口文档:http://localhost:8080/doc.html 使用临时域名访问,证明临时域名生效
1.2.7 微信支付代码
WeChatProperties.java:读取配置
java
@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {
private String appid; //小程序的appid
private String secret; //小程序的秘钥
private String mchid; //商户号
private String mchSerialNo; //商户API证书的证书序列号
private String privateKeyFilePath; //商户私钥文件
private String apiV3Key; //证书解密的密钥
private String weChatPayCertFilePath; //平台证书
private String notifyUrl; //支付成功的回调地址
private String refundNotifyUrl; //退款成功的回调地址
}
在OrderServiceImpl.java中实现payment和paySuccess两个方法
java
/*
* 订单支付
* @param ordersPaymentDTO
* @return
*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
User user = userMapper.getById(userId);
/* //调用微信支付接口,生成预支付交易单
JSONObject jsonObject = weChatPayUtil.pay(
ordersPaymentDTO.getOrderNumber(), //商户订单号
new BigDecimal(0.01), //支付金额,单位 元
"苍穹外卖订单", //商品描述
user.getOpenid() //微信用户的openid
);
if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
throw new OrderBusinessException("该订单已支付");
}
*/
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","ORDERPAID");
OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
vo.setPackageStr(jsonObject.getString("package"));
Integer OrderPaidStatus = Orders.PAID;//支付状态,已支付
Integer OrderStatus = Orders.TO_BE_CONFIRMED; //订单状态,待接单
LocalDateTime check_out_time = LocalDateTime.now();//更新支付时间
orderMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, this.orders.getId());
return vo;
}
/*
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
public void paySuccess(String outTradeNo) {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
// 根据订单号查询当前用户的订单
Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
}
注意:!!! 由于小程序支付需要商家注册,所以我把小程序调用微信支付接口进行支付相关代码注释掉,然后重新把订单状态进行修改,使其支付成功。或者,我们直接操作数据库,改变订单状态。
支付回调相关接口:
java
/**
* 支付回调相关接口
*/
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {
@Autowired
private OrderService orderService;
@Autowired
private WeChatProperties weChatProperties;
/**
* 支付成功回调
*
* @param request
*/
@RequestMapping("/paySuccess")
public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
//读取数据
String body = readData(request);
log.info("支付成功回调:{}", body);
//数据解密
String plainText = decryptData(body);
log.info("解密后的文本:{}", plainText);
JSONObject jsonObject = JSON.parseObject(plainText);
String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
log.info("商户平台订单号:{}", outTradeNo);
log.info("微信支付交易号:{}", transactionId);
//业务处理,修改订单状态、来单提醒
orderService.paySuccess(outTradeNo);
//给微信响应
responseToWeixin(response);
}
/**
* 读取数据
*
* @param request
* @return
* @throws Exception
*/
private String readData(HttpServletRequest request) throws Exception {
BufferedReader reader = request.getReader();
StringBuilder result = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
}
/**
* 数据解密
*
* @param body
* @return
* @throws Exception
*/
private String decryptData(String body) throws Exception {
JSONObject resultObject = JSON.parseObject(body);
JSONObject resource = resultObject.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String associatedData = resource.getString("associated_data");
AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//密文解密
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}
/**
* 给微信响应
* @param response
*/
private void responseToWeixin(HttpServletResponse response) throws Exception{
response.setStatus(200);
HashMap<Object, Object> map = new HashMap<>();
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}
1.3 定时任务SpringTask
1.3.1介绍
Spring Task是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。
定位:定时任务框架 作用:定时自动执行某段Java代码
应用场景:
1). 信用卡每月还款提醒
2). 银行贷款每月还款提醒
3). 火车票售票系统处理未支付订单
4). 入职纪念日为用户发送通知
强调:只要是需要定时处理的场景都可以使用Spring Task
1.3.2 cron表达式
-
cron其实就是一个字符串,通过cron表达式可以定义任务触发的时间
-
构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义:秒、分钟、小时、日、月、周、年(可选),每部分的含义如下表所示:
cron表达式在线生成器:在线Cron表达式生成器
1.3.3 开发步骤
1). 导入maven坐标 spring-context
2). 启动类添加注解 @EnableScheduling 开启任务调度
3). 自定义定时任务类
在本项目中,利用Spring Task技术主要是为了解决订单一直处于未支付状态user未下单、订单一直处于派送中状态商家未完成订单两个问题。
java
/*
*定时任务类,定时处理订单状态
*/
@Slf4j
@Component
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/*
*处理超时订单的方法
*/
@Scheduled(cron = "0 * * * * ? ")//每分钟触发一次
public void processTimeoutOrder(){
log.info("定时处理超时订单:{}", LocalDateTime.now());
//处理超时订单---select from orders where status=未支付 and order_time >15min
//time是现在时间的15分钟前
LocalDateTime time=LocalDateTime.now().plusMinutes(-15);
List<Orders> ordersList =orderMapper.getByStatusAndOrdertimeLT(Orders.PENDING_PAYMENT,time);
if(ordersList!=null && ordersList.size() > 0){
for (Orders orders : ordersList) {
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("支付超时,自动取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
/*
* 处理"派送中"状态的订单
*/
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨1点执行一次
public void processDeliveryOrder(){
log.info("处理"派送中"状态的订单:{}", LocalDateTime.now());
//处理"派送中"状态的订单---可以利用getByStatusAndOrdertimeLT方法查询,查询的内容是零点之前
LocalDateTime time=LocalDateTime.now().plusMinutes(-60);
List<Orders> ordersList =orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS,time);
if(ordersList!=null && ordersList.size() > 0){
for (Orders orders : ordersList) {
orders.setStatus(Orders.COMPLETED);
orderMapper.update(orders);
}
}
}
}
1.4 WebSocket全双工通信
1.4.1介绍
WebSocket 是基于 TCP 的一种新的网络协议 。它实现了浏览器与服务器全双工通信------浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性 的连接, 并进行双向数据传输。
HTTP协议和WebSocket协议对比:
-
HTTP是短连接
-
WebSocket是长连接
-
HTTP通信是单向 的,基于请求响应模式(一次请求--一次响应)
-
WebSocket支持双向通信
-
HTTP和WebSocket底层都是TCP连接
WebSocket缺点:
服务器长期维护长连接需要一定的成本 各个浏览器支持程度不一 WebSocket 是长连接,受网络限制比较大,需要处理好重连
结论: WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用
WebSocket应用场景:
1). 直播弹幕
2). 网页聊天
3). 体育实况更新
4). 股票基金报价实时更新
1.4.2入门案例
下面通过一个入门案例来详细了解WebSocket
- 需求:实现浏览器与服务器全双工通信。浏览器既可以向服务器发送消息,服务器也可主动向浏览器推送消息。
实现步骤:
1). 直接使用websocket.html页面作为WebSocket客户端
2). 导入WebSocket的maven坐标
3). 导入WebSocket服务端组件WebSocketServer,用于和客户端通信
4). 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件
5). 导入定时任务类WebSocketTask,定时向客户端推送数据
1.4.2.1 定义websocket.html页面
html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Demo</title>
</head>
<body>
<input id="text" type="text" />
<button onclick="send()">发送消息</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
function abc() {
//code
}
var websocket = null;
//Math.random()返回一个0(包含)到 1(不包含)的浮点数
//toString(32): 转成字符串
//substr(): 从指定位置开始截取字符串
var clientId = Math.random().toString(36).substr(2);
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
}else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(){
setMessageInnerHTML("连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data); //event.data是服务器返回的数据
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件:
//当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
</script>
</html>
1.4.2.2 定义WebSocket服务端组件
java
/*
* WebSocket服务
*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/*
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/*
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/*
* 连接关闭调用的方法
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/*
* 群发
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
1.4.2.3 定义配置类,注册WebSocket的服务端组件
java
/*
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
1.4.2.4 定义定时任务类,定时向客户端推送数据
java
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
/*
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("这是来自服务端的消息:" + LocalTime.now());
}
}
1.5 注册百度地图服务
1.5.1 注册百度地图服务
1.基于百度地图开放平台实现(https://lbsyun.baidu.com/)
2.注册账号--->控制台--->我的应用-->创建应用获取AK(服务端应用)--->调用接口
创建应用时:
类型:选服务端
#IP白名单:0.0.0.0/0
3.相关接口
地理编码服务:https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding
地理编码服务(又名Geocoder)是一类Web API接口服务;
地理编码服务提供将结构化地址数据(如:北京市海淀区上地十街十号)转换为对应坐标点(经纬度)功能;
GET: https://api.map.baidu.com/geocoding/v3/?address=北京市海淀区上地十街10号&output=json&ak=您的ak
在url中传递3个参数即可,返回数据格式如下:Java中将返回的json字符串转成JSONObject
XML
{ // JSONObject
"status": 0, // jsonObject.getIntValue("status")
"result": { //对象: jsonObject.getJSONObject("result")
"location": { //对象: jsonObject.getJSONObject("location")
"lng": 116.3076223267197, //经度 getString("lng")
"lat": 40.05682848596073 //纬度 getString("lat")
},
"precise": 1,
"confidence": 80,
"comprehension": 100,
"level": "门址"
}
}
路线规划服务:轻量级路线规划 | 百度地图API SDK (baidu.com)
轻量级路线规划服务(又名DirectionLite API )是一套REST风格的Web服务API,以HTTP/HTTPS形式提供了路线规划服务。相较于Direction API,DirectionLite API更注重服务的高性能和接口的轻便简洁,满足基础的路线规划需求,并不具备Direciton API中的驾车多路线/未来出行和公交跨城规划等高级功能。DirectionLite API支持驾车、骑行、步行、公交路线规划,支持中国大陆地区。
传递4个参数即可--origin、destination、steps_info、ak,返回数据如下:
XML
{
"status": 0, //getIntValue
"message": "ok",
"result": { //对象: getJSONObject
"origin": {
"lng": 116.33929505188,
"lat": 40.011157363344
},
"destination": {
"lng": 116.45255341058,
"lat": 39.936401378723
},
"routes": [ //数组: result.getJSONArray("routes");
{
"distance": 18129, //getString 得到的两地间的距离
"duration": 6193
}
]
}
}
代码如下:
java
/*
* 功能优化
* 优化用户下单功能,加入校验逻辑,如果用户的收货地址距离商家门店超出配送范围(配送范围为5公里内),则下单失败。
*/
private void checkOutOfRange(String address){
Map map = new HashMap();
map.put("address",shopAddress);
map.put("output","json");
map.put("ak",ak);
//获取店铺的经纬度坐标
String shopCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);
JSONObject jsonObject = JSON.parseObject(shopCoordinate);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("店铺地址解析失败");
}
//数据解析
JSONObject location = jsonObject.getJSONObject("result").getJSONObject("location");
String lat = location.getString("lat");
String lng = location.getString("lng");
//店铺经纬度坐标
String shopLngLat = lat + "," + lng;
map.put("address",address);
//获取用户收货地址的经纬度坐标
String userCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);
jsonObject = JSON.parseObject(userCoordinate);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("收货地址解析失败");
}
//数据解析
location = jsonObject.getJSONObject("result").getJSONObject("location");
lat = location.getString("lat");
lng = location.getString("lng");
//用户收货地址经纬度坐标
String userLngLat = lat + "," + lng;
map.put("origin",shopLngLat);
map.put("destination",userLngLat);
map.put("steps_info","0");
//路线规划
//从map集合中自动拿出4个参数进行请求访问---origin、destination、steps_info、ak
String json = HttpClientUtil.doGet("https://api.map.baidu.com/directionlite/v1/driving", map);
jsonObject = JSON.parseObject(json);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("配送路线规划失败");
}
//数据解析
JSONObject result = jsonObject.getJSONObject("result");
JSONArray jsonArray = (JSONArray) result.get("routes");
Integer distance = (Integer) ((JSONObject) jsonArray.get(0)).get("distance");
if(distance > 5000){
//配送距离超过5000米
throw new OrderBusinessException("超出配送范围");
}
}
最后在用户下单方法中调用checkOutOfRange方法来验证距离是否符合规定
java
//校验收货地址是否超出配送范围
checkOutOfRange(addressBook.getCityName()+addressBook.getDistrictName()+addressBook.getDetail());
1.6 Apache POI
1.6.1介绍
Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。 一般情况下,POI 都是用于操作 Excel 文件。
Apache POI 的应用场景:
1)银行网银系统导出交易明细
2)各种业务系统导出Excel报表
3)批量导入业务数据
注!!!:开发导出数据--Excel模块时,牢记怎么在Windows上操作excel,就怎么在代码开发中操作。
java
/*
* 导出运营数据报表--要下载到客户端浏览器--需要一个response输出流
* @param response
*/
public void exportBusinessData(HttpServletResponse response) {
LocalDate begin = LocalDate.now().minusDays(30);
LocalDate end = LocalDate.now().minusDays(1);
//查询概览运营数据,提供给Excel模板文件
BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(begin,LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模版.xlsx");
try {
//基于提供好的模板文件创建一个新的Excel表格对象
XSSFWorkbook excel = new XSSFWorkbook(inputStream);
//获得Excel文件中的一个Sheet页
XSSFSheet sheet = excel.getSheet("Sheet1");
sheet.getRow(1).getCell(1).setCellValue(begin + "至" + end);
//获得第4行
XSSFRow row = sheet.getRow(3);
//获取单元格
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(6).setCellValue(businessData.getNewUsers());
//获取第5行
row = sheet.getRow(4);
row.getCell(2).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getUnitPrice());
for (int i = 0; i < 30; i++) {
LocalDate date = begin.plusDays(i);
//准备明细数据
businessData = workspaceService.getBusinessData(LocalDateTime.of(date,LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));
row = sheet.getRow(7 + i);
row.getCell(1).setCellValue(date.toString());
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(3).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(5).setCellValue(businessData.getUnitPrice());
row.getCell(6).setCellValue(businessData.getNewUsers());
}
//通过输出流将文件下载到客户端浏览器中
ServletOutputStream out = response.getOutputStream();
excel.write(out);
//关闭资源
out.flush();
out.close();
excel.close();
}catch (IOException e){
e.printStackTrace();
}
}