苍穹外卖笔记

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();
    }

}

微信小程序开发

公众号 (qq.com)

注册小程序

完善小程序信息

下载开发者工具

了解小程序目录结构

快速入门:

index.wxml

index.js

效果:

上传小程序:

导入项目:

微信登录功能:

开放能力 / 用户信息 / 小程序登录 (qq.com)

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步:小程序端吊起微信支付:

详情:微信支付-开发者文档 (qq.com)

微信支付准备工作

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

营业额统计:

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:

销量前十统计:

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文件下载到客户端浏览器

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Winter.169

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值