ThreadLocal
ThreadLocal 并不是一个Thread,而是Thread的局部变量。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
**常用方法:**
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的线程局部变量
通过代码验证:当tomcat接受到请求后,拦截器 -> Controller -> Service -> Mapper(是不是同一个线程)
//通过线程ID来验证
System.out.println("当前线程ID:" + Thread.currentThread().getId());
实现记录操作人的id
初始工程在sky-common模块中已经封装了 ThreadLocal 操作的工具类:
package com.sky.context;
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
● 在拦截器中解析出当前登录员工id,并放入线程局部变量中:
package com.sky.interceptor;
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor 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 {
//.............................
//2、校验令牌
try {
//.................
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:{}", empId);
/将用户id存储到ThreadLocal
BaseContext.setCurrentId(empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//......................
}
}
@Override
//请求结束之前执行,清理ThreadLocal存储的ID
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
BaseContext.removeCurrentId();
}
}
分页查询(一看就会)
返回数据:
,引入依赖
controller层
Service层
mapper层
mapper对应的xml文件层:
还要配置一下mapper扫描的路径:
这是PageResult 的代码:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //总记录数
private List records; //当前页数据集合
}
解决日期格式问题:
第一种:
第二种:
扩展springmvc的消息转化器,统一对日期类型进行格式化处理:
源码:(写在mvc 的配置类中)
/**
* 扩展SpringMVC框架的消息转化器
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){
log.info("扩展消息转换器。。。");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter =new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转化器加入容器中
converters.add(0,converter);
}
对象转换器:
JacksonObjectMapper
package com.sky.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
公共字段自动填充
自定义枚举类:
自定义注解:
AOP编程
给有参数有公共字段的方法上添加注解
同过AOP面向切面编程 给要填充公共字段的 方法绑定通知:
package com.sky.aspect;
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
//切入点 在。。。的方法上并且有注解
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autofill(){};
/**
* 通知
*/
@Before("autofill()")
public void fill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充");
//获取到当前被拦截的方法上的数据库操作类型(反射的知识)
MethodSignature signature= (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);//通过签名对象 获得方法上的注解对象
OperationType value = annotation.value();//通过注解对象 获得数据库操作类型是 insert 还是update
//获取到当前被拦截的方法的参数---实体参数
Object[] args = joinPoint.getArgs();//获得参数
//如果方法没有参数返回
if(args==null||args.length==0){
return;
}
//方法里的参数(传递的实体类约定放在第一位)
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();//当前时间
Long currentId = BaseContext.getCurrentId();//当前id
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(value == OperationType.INSERT){ //如果数据库操作类型是insert
//为四个公共字段赋值
try {
//获得实体类的set方法 SET_CREATE_TIME = "setCreateTime"; setCreateTime被定义成了常量
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射给实体类赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(value == OperationType.UPDATE){
//为两个公共字段赋值
try {
//获得实体类的set方法
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射给实体类赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
文件上传到阿里云
1 配置阿里云的方法
1,首先引入阿里云的工具类
package com.sky.utils;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
/**
* 文件上传
*
* @param bytes
* @param objectName
* @return
*/
public String upload(byte[] bytes, String objectName) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 创建PutObject请求。
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
//文件访问路径规则 https://BucketName.Endpoint/ObjectName
StringBuilder stringBuilder = new StringBuilder("https://");
stringBuilder
.append(bucketName)
.append(".")
.append(endpoint)
.append("/")
.append(objectName);
log.info("文件上传到:{}", stringBuilder.toString());
return stringBuilder.toString();
}
}
2,配置阿里云的属性--因为不同的环境可能用到不同的账号,所以用${}引用的方式配置
引用开发环境的属性配置:
3,创建一个配置属性类,上一步写的时候就会给出提示,并且创建工具类对象时也会用到
也可以写成这种形式(与配置属性类的参数对应)但不符合yml文件的规范:
4,创建一个配置类,配置阿里云工具类
使用:
2 文件上传
上传成功不显示更改权限为公共读
新增菜品:
有几个重要的点:
获取数据库操作返回的主键值
xml文件中这样配:
Service层就可以获取啦:
把集合遍历存入表中
还有遍历存入数据库数据;(把集合遍历存入表中)
Redis(基础)
1,redis 入门
安装看这个:
Window下Redis的安装和部署详细图文教程(Redis的安装和可视化工具的使用)_redis windows-CSDN博客
2,redis数据类型
3,redis常用命令
字符串操作命令
哈希操作命令
列表操作命令
集合操作命令
有序集合操作命令
通用命令(对key操作)
4,在Java中操作redis
客户端:
SpringBoot配置redis:
1,导入spring Data Redis 的maven 坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2,配置Redis数据源
主配置文件:
开发环境的配置文件:
3,编写配置类,创建RedisTemplate对象 Template(模版的意思)
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模版对象。。。");
RedisTemplate redisTemplate =new RedisTemplate();
//设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
4,通过Redis Template对象操作Redis
可以在测试类操作:
操作字符串:
//对字符串操作
@Test
public void testString(){
//set get setex setnx 操作字符串
//设置指定key的值 set
redisTemplate.opsForValue().set("name","小米");
//获取指定key的值 get
String name = (String) redisTemplate.opsForValue().get("name");
System.out.println(name);
//设置指定key的值,并设置过期时间 setex
redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);
//只有在key不存在时设置key的值: setnx
redisTemplate.opsForValue().setIfAbsent("lock","1");
redisTemplate.opsForValue().setIfAbsent("lock","2");
}
操作哈希类型数据:
//对哈希类型数据操作
@Test
public void testhash(){
//hset hget hdel hkeys hvals
//将哈希表key中的字段field的值设置为value
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.put("key1","name","小明");
hashOperations.put("key1","age","12");
hashOperations.put("key1","gender","男");
//获取哈希表key1中name字段的值
String o = (String) hashOperations.get("key1", "name");
System.out.println(o);
//获取哈希表Key1中所有字段
Set key1 = hashOperations.keys("key1");
System.out.println(key1);
//获取哈希表Key1中所有值
List key11 = hashOperations.values("key1");
System.out.println(key11);
//删除哈希表key1中指定字段
hashOperations.delete("key1","gender");
}
操作列表:
//操作列表类型的数据
@Test
public void testlist(){
//lpush lrange rpop llen
ListOperations listOperations = redisTemplate.opsForList();
//将列表mylist从左边插入一些元素
listOperations.leftPushAll("mylist","a","b","c");
//将列表mylist从左边插入一个元素
listOperations.leftPush("mylist","d");
List mylist = listOperations.range("mylist", 0, -1);
System.out.println(mylist);
//移除并获取列表最后一个元素(最右边的元素)
Object mylist1 = listOperations.rightPop("mylist");
//获取列表长度
Long size = listOperations.size("mylist");
System.out.println(size);
}
操作集合:
//操作集合类型数据
@Test
public void testset(){
// sadd amembers scard sinter sunion srem
SetOperations setOperations = redisTemplate.opsForSet();
//创建两个集合列表
setOperations.add("set1","a","b","c","d");
setOperations.add("set2","a","b","x","y");
//获得集合中的所有成员
Set set1 = setOperations.members("set1");
System.out.println(set1);
//获取set1集合成员数
Long set11 = setOperations.size("set1");
System.out.println(set11);
//获取两个集合的交集
Set intersect = setOperations.intersect("set1", "set2");
System.out.println(intersect);
//获取两个集合的并集
Set union = setOperations.union("set1", "set2");
System.out.println(union);
//移除集合中的元素(一个或多个)
setOperations.remove("set1","a","b");
}
操作有序集合:
//操作有序集合类型数据
@Test
public void testZset(){
// zadd arange zincrby zrem
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
//向有序集合添加多个成员 分数越大索引值越小
zSetOperations.add("zset1","a",10);
zSetOperations.add("zset1","b",12);
zSetOperations.add("zset1","c",9);
//通过索引返回区间的成员
Set zset1 = zSetOperations.range("zset1", 0, -1);
System.out.println(zset1);
//对有序集合中指定成员的分数加上增量
zSetOperations.incrementScore("zset1","c",10);
//移除一个或多个成员
zSetOperations.remove("zset1","a","b");
}
通用命令(对Key操作):
//通用命令操作
@Test
public void testcommon(){
// keys exists type del
//查询所有的key(相当于数据库里的表名字)
Set keys = redisTemplate.keys("*");
System.out.println(keys);
//检查给定的key是否存在
Boolean name = redisTemplate.hasKey("name");
Boolean set1 = redisTemplate.hasKey("set1");
//将Keys遍历
for (Object key : keys) {
DataType type = redisTemplate.type(key);
//打印每一个key的储存类型
System.out.println(type.name());
}
//删除key
redisTemplate.delete("mylist");
}
店铺营业状态设置
管理端:(用户端类似,不过没有设置营业状态功能)
HttpClient
介绍;
在Java中通过编码的方式来发送http请求
1,引入依赖
入门案例发送get请求:
/**
* 测试通过httpclient发送get方式的请求
*/
@Test
public void testGet()throws Exception{
//创建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();;
}
发送post请求:
与get的区别是需要设置请求参数。
/**
* 测试通过httpclient发送post方式的请求
*/
@Test
public void testPost()throws Exception{
//创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象
HttpPost httpPost=new HttpPost("http://localhost:8080/admin/employee/login");
//通过JSONObject(fastjson)创建json数据
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 responseEntity = response.getEntity();
String s = EntityUtils.toString(responseEntity);
System.out.println("服务器返回的数据为:"+s);
//关闭资源
response.close();
httpClient.close();;
}
工具类:HttpClientUtil
package com.sky.utils;
import com.alibaba.fastjson.JSONObject;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Http工具类
*/
public class HttpClientUtil {
static final int TIMEOUT_MSEC = 5 * 1000;
/**
* 发送GET方式请求
* @param url
* @param paramMap
* @return
*/
public static String doGet(String url,Map<String,String> paramMap){
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
String result = "";
CloseableHttpResponse response = null;
try{
URIBuilder builder = new URIBuilder(url);
if(paramMap != null){
for (String key : paramMap.keySet()) {
builder.addParameter(key,paramMap.get(key));
}
}
URI uri = builder.build();
//创建GET请求
HttpGet httpGet = new HttpGet(uri);
//发送请求
response = httpClient.execute(httpGet);
//判断响应状态
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
response.close();
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (paramMap != null) {
List<NameValuePair> paramList = new ArrayList();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
httpPost.setConfig(builderRequestConfig());
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
if (paramMap != null) {
//构造json格式数据
JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
jsonObject.put(param.getKey(),param.getValue());
}
StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
//设置请求编码
entity.setContentEncoding("utf-8");
//设置数据类型
entity.setContentType("application/json");
httpPost.setEntity(entity);
}
httpPost.setConfig(builderRequestConfig());
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
private static RequestConfig builderRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MSEC)
.setConnectionRequestTimeout(TIMEOUT_MSEC)
.setSocketTimeout(TIMEOUT_MSEC).build();
}
}
微信小程序开发
注册小程序
完善小程序信息
下载开发者工具
了解小程序目录结构
快速入门:
index.wxml
index.js
效果:
上传小程序:
导入项目:
微信登录功能:
1,调用 wx.login() 获取 临时登录凭证code
postman
调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
接口地址:
https://api.weixin.qq.com/sns/jscode2session
代码操作:微信登录功能
mapper层很简单就不说了
serviceimpl层方法:
HttpClient相关的知识
controller层:
jwt令牌详解:
还要配置jwt令牌校验的拦截器
校验令牌:
/**
* jwt令牌校验的拦截器
*/
@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;
}
//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);
//把当前id存到ThreadLocal当中去
BaseContext.setCurrentId(userId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
注册拦截器:
导入用户端商品浏览功能。
缓存菜品
用户端:
先依赖注入:
@Autowired private RedisTemplate redisTemplate;
清理缓存:
管理端:
Spring Cache
Spring Cache就是一个框架。它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以3秒钟就使用上一个很不错的缓存功能。
pom文件里面有哪个缓存实现,就会使用哪个。
1,导入坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
当然还要有Redis等。。的坐标。
案例在这:
2,使用
@EnableCaching
在启动类上开启缓存注解功能:
@CachePut
将方法的返回值放入缓存中
生成的key是一个动态的值: userCache::user.id
@Cacheable
在方法执行前先查询缓存中是否有数据,如果有直接返回缓存数据;如果没有,调用方法,并将方法返回值放到缓存中。
@CacheEvict
将一条或多条数据从缓存中删除
删除所有数据
缓存套餐:
1,导入依赖
2,开启缓存注解功能
3,用户端
4,管理端
添加购物车:
//添加购物车
public void add(ShoppingCartDTO shoppingCartDTO) {
//判断当前加入到购物车中的商品是否已经存在了
ShoppingCart shoppingCart=new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
Long userId = BaseContext.getCurrentId();
shoppingCart.setUserId(userId);
//查询购物车里的数据
List<ShoppingCart> list=shoppingCartMapper.list(shoppingCart);
//如果已经存在了,只需要将数量加一
if(list!=null && list.size()>0){
ShoppingCart shoppingCart1 = list.get(0);
shoppingCart1.setNumber(shoppingCart1.getNumber()+1);
shoppingCartMapper.update(shoppingCart1);
}else {
//如果不存在,需要插入一条新的购物车数据
//判断是套餐还是菜品,查询出来它的名字和图片和金额
Long dishId = shoppingCartDTO.getDishId();
if (dishId != null) {
//是菜品
Dish byId = dishMapper.getById(dishId);
shoppingCart.setName(byId.getName());
shoppingCart.setAmount(byId.getPrice());
shoppingCart.setImage(byId.getImage());
} else {
//是套餐
Long setmealId = shoppingCartDTO.getSetmealId();
Setmeal byId = setmealMapper.findById(setmealId);
shoppingCart.setName(byId.getName());
shoppingCart.setAmount(byId.getPrice());
shoppingCart.setImage(byId.getImage());
}
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCart.setNumber(1);
shoppingCartMapper.insert(shoppingCart);
}
用户下单:
/**
* 用户提交订单
* @param ordersSubmitDTO
* @return
*/
@Transactional
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
//处理各种业务异常(地址簿为空,购物车数据为空
Long addressBookId = ordersSubmitDTO.getAddressBookId();
AddressBook addressBook = addressBookMapper.getById(addressBookId);
if(addressBook==null){
throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
Long userId = BaseContext.getCurrentId();
List<ShoppingCart> userShoppingCart = shoppingCartMapper.getAll(userId);
if(userShoppingCart==null || userShoppingCart.size()==0){
//抛出业务异常
throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//向订单表插入1条数据
Orders orders=new Orders();
BeanUtils.copyProperties(ordersSubmitDTO,orders);
orders.setOrderTime(LocalDateTime.now());
orders.setPayStatus(Orders.UN_PAID);
orders.setStatus(Orders.PENDING_PAYMENT);
orders.setNumber(String.valueOf(System.currentTimeMillis()));
orders.setPhone(addressBook.getPhone());
orders.setConsignee(addressBook.getConsignee());
orders.setUserId(userId);
orderMapper.insert(orders);
//向订单明细表插入n条数据
List<OrderDetail> OrderDetaillist=new ArrayList<>();
//遍历购物车的数据
for (ShoppingCart shoppingCart : userShoppingCart) {
OrderDetail orderDetail=new OrderDetail();//订单明细
BeanUtils.copyProperties(shoppingCart,orderDetail);
orderDetail.setOrderId(orders.getId());//设置当前订单明细关联的订单id
OrderDetaillist.add(orderDetail);
}
orderDetailMapper.insertBatch(OrderDetaillist);
//清空当前用户的购物车数据
shoppingCartMapper.clean(userId);
//封装VO返回结果
OrderSubmitVO orderSubmitVO =OrderSubmitVO.builder()
.id(orders.getId())
.orderNumber(orders.getNumber())
.orderAmount(orders.getAmount())
.orderTime(orders.getOrderTime()).build();
return orderSubmitVO;
}
微信支付:
了解流程,学会如何使用接口
微信小程序支付时序图:
上图的第5步:调用微信下单接口
商户系统先调用该接口 在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识
此接口详细信息:微信支付-开发者文档 (qq.com)
部分信息:
请求示例:
返回示例:
上图第10步:小程序端吊起微信支付:
微信支付准备工作
cpolar
如何解决当前电脑获得公网ip地址,最终让微信后台调用到我们外卖系统的后端服务,需要获得一个临时域名,临时域名对应的就是公网ip,支付成功后微信服务通过该域名回调我们的程序。
使用cpolar软件,cpolar是一种安全的内网穿透云服务,它将内网下的本地服务器通过安全隧道暴露至公网。使得公网用户可以正常访问内网服务。只需一行命令,就可以将内网站点发布至公网,方便给客户演示。高效调试微信公众号、小程序、对接支付宝网关等云端服务,提高编程效率。
内网穿透工具给当前电脑生成一个域名:
进入官网:
cpolar - secure introspectable tunnels to localhost
在路径下cmd命令,运行复制的代码:
获取临时的域名:映射到本地的8080端口
还需要有微信支付平台证书,商户私钥文件:(我们还没有资质)
代码导入
1,首先在yml文件里配置属性:
这是它的配置属性类:
2,第4步中:微信小程序申请微信支付,就会请求到商户系统中这个接口
3,订单支付
重点是这个weChatPayUtil工具类:里面包括了:
时序图中第5步:调用微信下单接口,6:返回预支付交易标识,7:将组合数据再次签名
package com.sky.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.HttpHeaders;
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.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
/**
* 微信支付工具类
*/
@Component
public class WeChatPayUtil {
//微信支付下单接口地址
public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
//申请退款接口地址
public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
@Autowired
private static WeChatProperties weChatProperties;
/**
* 获取调用微信接口的客户端工具对象
*
* @return
*/
private static CloseableHttpClient getClient() {
PrivateKey merchantPrivateKey = null;
try {
//merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
//加载平台证书文件
X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
//wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
.withWechatPay(wechatPayCertificates);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
return httpClient;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
* 发送post方式请求
*
* @param url
* @param body
* @return
*/
private static String post(String url, String body) throws Exception {
CloseableHttpClient httpClient = getClient();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
httpPost.setEntity(new StringEntity(body, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
return bodyAsString;
} finally {
httpClient.close();
response.close();
}
}
/**
* 发送get方式请求
*
* @param url
* @return
*/
private String get(String url) throws Exception {
CloseableHttpClient httpClient = getClient();
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());
return bodyAsString;
} finally {
httpClient.close();
response.close();
}
}
/**
* jsapi下单
*
* @param orderNum 商户订单号
* @param total 总金额
* @param description 商品描述
* @param openid 微信用户的openid
* @return
*/
private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("appid", weChatProperties.getAppid());
jsonObject.put("mchid", weChatProperties.getMchid());
jsonObject.put("description", description);
jsonObject.put("out_trade_no", orderNum);
jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
JSONObject amount = new JSONObject();
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("currency", "CNY");
jsonObject.put("amount", amount);
JSONObject payer = new JSONObject();
payer.put("openid", openid);
jsonObject.put("payer", payer);
String body = jsonObject.toJSONString();
return post(JSAPI, body);
}
/**
* 小程序支付
*
* @param orderNum 商户订单号
* @param total 金额,单位 元
* @param description 商品描述
* @param openid 微信用户的openid
* @return
*/
public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
//统一下单,生成预支付交易单
String bodyAsString = jsapi(orderNum, total, description, openid);
//解析返回结果
JSONObject jsonObject = JSON.parseObject(bodyAsString);
System.out.println(jsonObject);
String prepayId = jsonObject.getString("prepay_id");
if (prepayId != null) {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomStringUtils.randomNumeric(32);
ArrayList<Object> list = new ArrayList<>();
list.add(weChatProperties.getAppid());
list.add(timeStamp);
list.add(nonceStr);
list.add("prepay_id=" + prepayId);
//二次签名,调起支付需要重新签名
StringBuilder stringBuilder = new StringBuilder();
for (Object o : list) {
stringBuilder.append(o).append("\n");
}
String signMessage = stringBuilder.toString();
byte[] message = signMessage.getBytes();
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
signature.update(message);
String packageSign = Base64.getEncoder().encodeToString(signature.sign());
//构造数据给微信小程序,用于调起微信支付
JSONObject jo = new JSONObject();
jo.put("timeStamp", timeStamp);
jo.put("nonceStr", nonceStr);
jo.put("package", "prepay_id=" + prepayId);
jo.put("signType", "RSA");
jo.put("paySign", packageSign);
return jo;
}
return jsonObject;
}
/**
* 申请退款
*
* @param outTradeNo 商户订单号
* @param outRefundNo 商户退款单号
* @param refund 退款金额
* @param total 原订单金额
* @return
*/
public static String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("out_trade_no", outTradeNo);
jsonObject.put("out_refund_no", outRefundNo);
JSONObject amount = new JSONObject();
amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("currency", "CNY");
jsonObject.put("amount", amount);
jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());
String body = jsonObject.toJSONString();
//调用申请退款接口
return post(REFUNDS, body);
}
}
订单支付运行完后返回给微信小程序vo对象,对应时序图的第8步:返回支付参数。
小程序继续运行第9步:用户确认支付
用户支付完后,小程序运行第10步:吊起微信支付,11:返回支付结果,12:显示支付结果。
支付成功后,微信后台进行第13:推送支付结果:
在第5步中调用工具类中pay方法中又调用 jsapi方法
这个地址就是13步微信后台发出请求(推送支付结果)给商户系统的地址
导入这个controller接口接收微信后台推送的数据:
package com.sky.controller.notify;
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import com.sky.service.OrderService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* 支付回调相关接口
*/
@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();
}
}
这个接口中接收微信后台传来的数据进行一系列操作
修改代码
由于我们个人没有资质(资料中并没有提供视频中所提到的支付证书和商户密钥)不能像视频里真的去支付,所以此处需要修改代码逻辑
对前端页面的修改:
在小程序前端修改pages.pay.index.js文件,
ctrl +F 搜索 handleSave定位:
注释原先代码:
只留下支付成功的代码:
对后端进行修改:
校验收货地址是否超出配送范围
1,注册账号
百度地图开放平台 | 百度地图API SDK | 地图开发 (baidu.com)
2,创建应用
3,配置外卖商家店铺地址和百度地图的AK:
4 改造OrderServiceImpl,注入上面的配置项:
@Value("${sky.shop.address}")
private String shopAddress;
@Value("${sky.baidu.ak}")
private String ak;
5,在OrderServiceImpl中提供校验方法:
/**
* 检查客户的收货地址是否超出配送范围
* @param address
*/
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");
//路线规划
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("超出配送范围");
}
}
6,在OrderServiceImpl的submitOrder方法中调用上面的校验方法:
Spring Task
Spring Task 是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。
作用:定时自动执行某段Java代码,只要是需要定时处理的场景都可以使用Spring Task
cron表达式:cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间,是由若干数字、空格、符号按一定的规则,组成一组字符串,从而表达时间的信息。与正则表达式类似,都是一个字符串表示一些信息。
构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义
每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)
例:
cron表达式在线生成器:在线Cron表达式生成器 (qqe2.com)
使用步骤 :
1,导入maven坐标spring-context(已存在)
2,启动类添加注解@EnableScheduling开启任务调度
3,自定义定时任务类:
订单状态定时处理:
WebSocket:
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它允许在客户端和服务器之间进行实时的双向数据传输。与传统的 HTTP 请求-响应模式不同,WebSocket 在连接建立后,客户端和服务器可以随时互相发送数据,而不需要每次都发起新的请求。
全双工通信是一种通信方式,允许两个通信实体在同一时间内同时发送和接收数据,而不需要交替进行。在全双工通信中,通信双方可以同时进行双向数据传输,无需等待对方完成传输。
这种通信方式类似于人们进行对话的方式,每个人都可以同时说话和听取对方的回应,而不需要轮流交替。全双工通信在许多不同领域和技术中都有应用,包括无线通信、有线通信、网络通信等。
举例来说,电话通信中的典型例子就是全双工通信。当您与他人通话时,您可以同时说话和听取对方的回应,而不需要等待对方完成说话后才能回应。
应用场景:
•视频弹幕
•网页聊天
•体育实况更新
•股票基金报价实时更新
入门案例:
相关资料:
1,客户端代码:
<!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">
var websocket = null;
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);
}
//连接关闭的回调方法
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>
2,导入坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
3,用于和客户端连接的类:
package com.sky.websocket;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 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();
}
}
}
}
4,导入配置类,注册WebSocket的服务
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
5,导入定时任务类,定时向客户端推送数据:
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
}
来单提醒功能:
需要注意的一点:
其实是先请求到nginx,然后由nginx反向代理转向后端。
前提是NGINX配置好这个路径:
客户催单:
Apache ECharts:
Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。
使用Echarts,重点在于研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据,然后响应给前端来展示图表。
官网地址:Apache ECharts
这里有一点需要注意,近七日数据(类似这样的近几日的数据)是截至到前一天的,如果想看今天的数据需要选本周或者本月即可。
快速入门:
快速上手 - Handbook - Apache ECharts
营业额统计:
![](https://img-blog.csdnimg.cn/073b64cd0d2f40388838a41bec25ba45.png)
public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {
//创建集合存放begin到end范围内每天的日期
List<LocalDate> dateList=new ArrayList<>();
dateList.add(begin);
while (!begin.equals(end)){
begin = begin.plusDays(1);
dateList.add(begin);
}
//存放每天的营业额
List<Double> turnoverList=new ArrayList<>();
for (LocalDate localDate : dateList) {
LocalDateTime beginTime = LocalDateTime.of(localDate, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(localDate, LocalTime.MAX);
//select sum(amount) from orders where order_time > beginTime and order_time<endTime and status =5
Map map=new HashMap();
map.put("begin",beginTime);
map.put("end",endTime);
map.put("status", Orders.COMPLETED);//5:已完成状态
Double turnover= reportMapper.sumByMap(map);//查询出来每天的营业额
//判断当天的营业额是否等于空
turnover=turnover==null?0.0 :turnover;
turnoverList.add(turnover);
}
return TurnoverReportVO.builder()
.dateList(StringUtils.join(dateList,","))
.turnoverList(StringUtils.join(turnoverList,","))
.build();
}
如果查询的参数是一个map集合,那么SQL语句就用map集合 的key:
销量前十统计:
![](https://img-blog.csdnimg.cn/988876fd52ed47daa432f97403d84eed.png)
sql语句较为复杂:
Apache POI:
Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。一般情况下,POI 都是用于操作 Excel 文件。
Apache POI 的应用场景:
•银行网银系统导出交易明细
•各种业务系统导出Excel报表
•批量导入业务数据
我们理论上可以直接在代码设置导出的Excel文件格式,但是为了方便我们一般都提前设计好Excel模板,然后用代码直接往里填数据就行,Excel模板文件课程资料里有大家直接放到视频中所提到的位置下即可,注意必须在代码中要把模板文件的名字及其后缀都写上才行。
入门案例:
导入坐标:
<!-- poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> </dependency>
入门案例-写入数据
public class POItest {
public static void main(String[] args) throws Exception {
write();
}
//写入数据
public static void write() throws Exception{
//在内存中创建一个Excel文件
XSSFWorkbook excel = new XSSFWorkbook();
//在Excel文件中创建一个sheet页
XSSFSheet sheet = excel.createSheet("info");
//在sheet页中创建对象一行,rownum编号从0开始
XSSFRow row = sheet.createRow(1);
//在这一行的第2个单元格中写入“姓名” 第3个“城市”
row.createCell(1).setCellValue("姓名");
row.createCell(2).setCellValue("城市");
//创建一个新行
row = sheet.createRow(2);
row.createCell(1).setCellValue("张三");
row.createCell(2).setCellValue("北京");
//创建一个新行
row = sheet.createRow(3);
row.createCell(1).setCellValue("李四");
row.createCell(2).setCellValue("郑州");
//通过输出流将内存中的Excel文件写入到磁盘
FileOutputStream out = new FileOutputStream(new File("C:\\Users\\liang\\Desktop\\info.xlsx"));
excel.write(out);
//关闭资源
out.close();
excel.close();
}
入门案例-读取数据
//读取数据
public static void read() throws Exception{
//输入流
FileInputStream in = new FileInputStream(new File("C:\\Users\\liang\\Desktop\\info.xlsx"));
//在内存中读取这个个Excel文件
XSSFWorkbook excel = new XSSFWorkbook(in);
//读取Excel文件的第一个sheet页
XSSFSheet sheet = excel.getSheetAt(0);
//获取sheet中最后一行的行号
int lastRowNum = sheet.getLastRowNum();
//从第二行向下遍历
for (int i=1;i<=lastRowNum;i++){
//获得每一行
XSSFRow row = sheet.getRow(i);
//获得单元格对象 第2个单元格,第3个单元格
String cellValue1 = row.getCell(1).getStringCellValue();
String cellValue2 = row.getCell(2).getStringCellValue();
System.out.println(cellValue1+" "+cellValue2);
}
//关闭资源
in.close();
excel.close();
}
导出运营数据Excel报表
功能本质上是下载文件。
需求分析:
业务规则:
导出Excel形式的报表文件
导出最近三十天的运营数据
操作步骤:
1,设计Excel模板文件
2,查询最近30天的运营数据
3,将查询到的运营数据写入模板文件
4,通过输出流将Excel文件下载到客户端浏览器