1、部分效果演示

  • 车辆列表
    list
  • 租车详情
    zuche
  • 订单列表
    order
  • jmeter压力测试
    jmeter
  • 抢单错误信息持久化到redis
    mqcount
  • 控制台信息
    console

2、技术栈

功能或特点名称版本
父项目依赖spring-boot-starter-parent2.2.5.RELEASE
微服务架构spring-cloud-dependenciesHoxton.RELEASE
admin监控服务spring-boot-admin-starter-server2.2.4
eureka管理服务spring-cloud-starter-netflix-eureka-server2.2.0.RELEASE
config配置服务spring-cloud-config-server2.2.0.RELEASE
gateway网关服务spring-cloud-starter-gateway2.2.0.RELEASE
链路追踪服务spring-cloud-starter-zipkin2.2.0.RELEASE
redis限流spring-boot-starter-data-redis-reactive2.2.5.RELEASE
组件调用(已集成熔断,负载均衡)spring-cloud-starter-openfeign2.2.0.RELEASE
hutool工具类hutool-all5.8.22
数据库curdmybatis-plus-boot-starter3.5.3.2
短信榛子云短信zhenzisms2.0.2
email邮件发送spring-boot-starter-mail2.2.5.RELEASE
mq消息队列rocketmq-spring-boot-starter2.2.1
阿里云oos对象储存aliyun-sdk-oss3.15.1
支付宝支付alipay-sdk-java4.38.111.ALL
分布式图片管理minio8.2.2

3、后端集成

3.2、用户登录

  • UserController.java
package com.llh.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWTUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.llh.domain.User;
import com.llh.service.UserService;
import com.llh.utils.ZzyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * User: lilinhan
 * DateTime: 2023/10/20 11:57
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    JavaMailSender javaMailSender;

    @RequestMapping("/test")
    public String test(){
        return "hhhhhh";
    }


    // 验证码接口
    @RequestMapping("/getCode")
    public void getCode(HttpServletResponse response) throws IOException {
        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(150, 60, 4, 20);
        // 重新生成code
        captcha.createCode();
        captcha.write(response.getOutputStream());

        redisTemplate.opsForValue().set("yzm", captcha.getCode(), 1, TimeUnit.MINUTES);
    }

    @RequestMapping("/sendSms")
    public Map<String,Object> sendSms(String phone){
        Map<String,Object> map = new HashMap<>();

        boolean tel = phone.matches("(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}");
        if (!tel) {
            map.put("code", 1005);
            map.put("msg", "手机号格式错误");
            return map;
        }

        String code = RandomUtil.randomNumbers(4);
        int time = 5;
        boolean b = ZzyUtils.sendSms(phone, code, time);
        if (!b) {
            map.put("code", 1005);
            map.put("msg", "短信发送失败");
            return map;
        } else {
            redisTemplate.opsForValue().set(phone, code, time, TimeUnit.MINUTES);
            map.put("code", 1001);
            map.put("msg", "短信发送成功");
            return map;
        }
    }

    @RequestMapping("/login")
    public Map<String,Object> login(User user){
        Map<String,Object> map = new HashMap<>();

        QueryWrapper  wrapper = new QueryWrapper();
        wrapper.eq("name",user.getName());

        User userDB = userService.getOne(wrapper);
        if(userDB==null){
            map.put("code",1005);
            map.put("msg","该用户不存在");
            return map;
        }

        String md5Hex = DigestUtil.md5Hex(user.getPwd() + userDB.getSalt());
        if(!userDB.getPwd().equals(md5Hex)){
            map.put("code",1005);
            map.put("msg","密码错误");
            return map;
        }

        String code = (String) redisTemplate.opsForValue().get("yzm");
        if(StrUtil.isNotEmpty(code)){
            if (!code.equalsIgnoreCase(user.getCode())) {
                map.put("code", 1005);
                map.put("msg", "验证码错误");
                return map;
            }
        }else {
            map.put("code", 1005);
            map.put("msg", "验证码已过期");
            return map;
        }

        Map<String, Object> map1 = new HashMap<>();
        map1.put("user", JSONUtil.toJsonStr(userDB));
        map1.put("loginName", userDB.getName());
        map1.put("userId", userDB.getId());

        String token = JWTUtil.createToken(map1, "llh".getBytes());
        map.put("token", token);
        map.put("loginName", userDB.getName());
        map.put("userId", userDB.getId());
        map.put("code", 1001);
        map.put("msg", "登录成功!");

        return map;
    }

    @RequestMapping("/phoneLogin")
    public Map<String,Object> phoneLogin(User user){
        Map<String,Object> map = new HashMap<>();

        QueryWrapper  wrapper = new QueryWrapper();
        wrapper.eq("name",user.getName());

        User userDB = userService.getOne(wrapper);
        if(userDB==null){
            map.put("code",1005);
            map.put("msg","该用户不存在");
            return map;
        }

        String md5Hex = DigestUtil.md5Hex(user.getPwd() + userDB.getSalt());
        if(!userDB.getPwd().equals(md5Hex)){
            map.put("code",1005);
            map.put("msg","密码错误");
            return map;
        }

        String code = (String) redisTemplate.opsForValue().get(user.getPhone());
        if(StrUtil.isNotEmpty(code)){
            if (!code.equals(user.getCode())) {
                map.put("code", 1005);
                map.put("msg", "验证码错误");
                return map;
            }
        }else {
            map.put("code", 1005);
            map.put("msg", "验证码已过期");
            return map;
        }

        Map<String, Object> map1 = new HashMap<>();
        map1.put("user", JSONUtil.toJsonStr(userDB));
        map1.put("loginName", userDB.getName());
        map1.put("userId", userDB.getId());

        String token = JWTUtil.createToken(map1, "llh".getBytes());
        map.put("token", token);
        map.put("loginName", userDB.getName());
        map.put("userId", userDB.getId());
        map.put("code", 1001);
        map.put("msg", "登录成功!");

        return map;
    }

    // 注册接口
    @RequestMapping("/register")
    public Map<String, Object> register(User user) {
        Map<String, Object> map = new HashMap<>();

        boolean qq = user.getEmail().matches("[1-9][0-9]{4,10}");
//        boolean email = user.getEmail().matches("^[A-Za-z0-9]+([._%+-][A-Za-z0-9]+)*@[A-Za-z0-9]+(\\\\.[A-Za-z0-9]+)*(\\\\.[A-Za-z]{2,})$");
        if(!qq){
            map.put("code", 1005);
            map.put("msg", "QQ号格式错误");
            return map;
        }

        boolean phone2 = user.getPhone().matches("(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}");
        if (!phone2) {
            map.put("code", 1005);
            map.put("msg", "手机号格式错误");
            return map;
        }

        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("phone",user.getPhone());
        User userDB = userService.getOne(wrapper);
        if(userDB!=null){
            map.put("code", 1005);
            map.put("msg", "手机号已存在");
            return map;
        }

        if(StrUtil.isNotBlank(user.getCode())){
            String code = (String) redisTemplate.opsForValue().get("yzm");
            if (!code.equalsIgnoreCase(user.getCode())) {
                map.put("code", 1005);
                map.put("msg", "验证码错误");
                return map;
            }
        }else {
            map.put("code", 1005);
            map.put("msg", "验证码已过期");
            return map;
        }

        // 发送短信
//        sendSms(user.getPhone());

        String initPwd = "123";
        String initName = "张三";
        String salt = UUID.randomUUID().toString().substring(0, 4);

        // 邮件发送
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setFrom("llh.fool@qq.com");
        mailMessage.setTo(user.getEmail()+"@qq.com");
        mailMessage.setSentDate(new Date());
        mailMessage.setSubject("注册成功提示");
        mailMessage.setText("恭喜:"+user.getEmail()+"注册成功!\n初始用户名:"+initName+",初始密码为:"+initPwd);
        javaMailSender.send(mailMessage);


        user.setPwd(DigestUtil.md5Hex(initPwd + salt));
        user.setName(initName);
        user.setSalt(salt);
        boolean save = userService.save(user);
        if (save) {
            map.put("code", 1001);
            map.put("msg", "注册成功");
            return map;
        }

        return map;
    }
}

3.2、车辆基本crud

  • 车辆curd,CarController.java
package com.llh.controller;

import cn.hutool.core.lang.Snowflake;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.llh.domain.Car;
import com.llh.service.CarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * User: lilinhan
 * DateTime: 2023/10/30 18:39
 */
@RestController
@RequestMapping("/car")
public class CarController {
    @Autowired
    CarService carService;

    // 更新车辆
    @RequestMapping("/update")
    public Map<String,Object> update(Car car){
        Map<String,Object> map = new HashMap<>();

        boolean b = carService.updateById(car);
        if(b){
            map.put("flag",b);
            map.put("msg","修改成功");
            return map;
        }else {
            map.put("flag",b);
            map.put("msg","修改失败");
            return map;
        }
    }

    // 车辆下架
    @RequestMapping("/del")
    public Map<String,Object> del(Integer id){
        Map<String,Object> map = new HashMap<>();

        boolean b = carService.removeById(id);
        if(b){
            map.put("flag",b);
            map.put("msg","删除成功");
            return map;
        }else {
            map.put("flag",b);
            map.put("msg","删除失败");
            return map;
        }
    }

    // 通过订单编号获取车辆信息
    @RequestMapping("/updateCarByNo")
    public void updateCarByNo(String no){
        carService.updateCarByNo(no);
    }

    // 车辆列表+查询
    @RequestMapping("/list")
    public Page<Car> list(Page<Car> page, Car car){
        return carService.getList(page,car);
    }

    // 车辆录入
    @RequestMapping("/save")
    public Map<String,Object> save(Car car){
        Map<String,Object> map = new HashMap<>();
        // 编号使用雪花算法
        car.setNum(new Snowflake().nextIdStr());
        car.setCreateTime(new Date());
        car.setSpeed("手自一体");
        car.setRent(new BigDecimal(2000));

        boolean save = carService.save(car);
        map.put("flag",save);
        map.put("msg","添加成功");
        return map;
    }

}
  • 下拉框数据,DistController.java
package com.llh.controller;

import com.llh.domain.Dist;
import com.llh.service.DistService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * User: lilinhan
 * DateTime: 2023/10/30 18:42
 */
@RestController
@RequestMapping("/dist")
public class DistController {
    @Autowired
    DistService distService;

    @GetMapping("/list")
    public Map<String, List<Dist>> list(){
        Map<String,List<Dist>> map = new HashMap<>();
        List<Dist> list = distService.list();
        // 获取车系、车型、品牌和颜色下拉框
        map.put("models",list.stream().filter(dist -> dist.getDistType().equals("car_model")).collect(Collectors.toList()));
        map.put("brands",list.stream().filter(dist -> dist.getDistType().equals("car_brand")).collect(Collectors.toList()));
        map.put("dists",list.stream().filter(dist -> dist.getDistType().equals("car_dist")).collect(Collectors.toList()));
        map.put("colors",list.stream().filter(dist -> dist.getDistType().equals("car_color")).collect(Collectors.toList()));
        return map;
    }
}

3.3、图片上传

  • 使用minio上传,UploadController.java
package com.llh.controller;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * User: lilinhan
 * DateTime: 2023/10/24 10:34
 */
@RestController
@RequestMapping("/upload")
public class UploadController {
    public static final String MINIO_SERVER = "http://127.0.0.1:9000";
    private static final String BUCKET_NAME = "images";

    @RequestMapping("/minio")
    public Map<String, Object> minio(MultipartFile file) {
        Map<String, Object> map = new HashMap<>();

        // 创建客户端
        MinioClient minioClient = MinioClient.builder()
              .endpoint(MINIO_SERVER)      // 访问url
              .credentials("EGQVyIa2qbjWAKZzGZ8T","SqEHCH3elRayyazcTXESAsrozAWTOmtrIX7SssSB")   // 密钥
              .build();
        String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename();
        try {
            // 使用自带方法 putObject
            minioClient.putObject(PutObjectArgs.builder()
                  .bucket(BUCKET_NAME)     // 桶的名称
                  .object(fileName)   // 文件名称
                  .contentType(file.getContentType())     // 文件类型
                  .stream(file.getInputStream(), file.getSize(), -1)        // 文件流传输
                  .build());

            map.put("code",1001);
            map.put("msg","上传成功");
            map.put("url",MINIO_SERVER+"/"+BUCKET_NAME+"/"+fileName);
            return map;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  • 使用阿里云oss对象储存,AlyOssUtils.java
package com.llh.utils;

import cn.hutool.core.lang.UUID;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CannedAccessControlList;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.Map;

/**
 * User: lilinhan
 * DateTime: 2023/11/2 10:07
 */
public class AlyOssUtils {
    private static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com";// oss实例的地域节点
    private static final String ACCESS_KEY_ID = "LTAI5t8M6dewZALaTRb3QEC7";// 阿里云密钥ID
    private  static final String ACCESS_KEY_SECRET = "9iG7iCRwATM0cVB8kPxmPSTbhqotqk";// 阿里云密钥
    private static final String BUCKET_NAME = "llh-images";// oss实例的桶名

    /**
     * 图片上传(包含文件夹)
     * @param file 文件
     * @param model   阿里云的文件夹名
     * @return  map集合
     */
    public static Map<String,Object> uploadToModel(MultipartFile file,String model){
        Map<String,Object> map = new HashMap<>();
        String url = "";
        try {
            // 创建ossClient实例
            OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID,ACCESS_KEY_SECRET);
            // 判断oss实例是否存在
            if(!ossClient.doesBucketExist(BUCKET_NAME)){
                // 创建桶名
                ossClient.createBucket(BUCKET_NAME);
                // 设置oss实例的访问权限:公共读
                ossClient.setBucketAcl(BUCKET_NAME, CannedAccessControlList.PublicRead);
            }
            // 获取文件名
            String filename = UUID.randomUUID() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
            // 文件根路径
            String key = model+"/"+filename;
            // 将文件上传到阿里云
            ossClient.putObject(BUCKET_NAME,key,file.getInputStream());
            // 获取url地址
            url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("//")+2)+"/"+key;
            // 关闭客户端
            ossClient.shutdown();

            map.put("code",1001);
            map.put("url",url);
            map.put("msg","上传成功");
            return map;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 图片上传
     * @param file   上传的文件
     * @return  map集合
     */
    public static Map<String,Object> upload(MultipartFile file){
        Map<String,Object> map = new HashMap<>();
        String url = "";
        try {
            // 创建ossClient实例
            OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID,ACCESS_KEY_SECRET);
            // 判断oss实例是否存在
            if(!ossClient.doesBucketExist(BUCKET_NAME)){
                // 创建桶名
                ossClient.createBucket(BUCKET_NAME);
                // 设置oss实例的访问权限:公共读
                ossClient.setBucketAcl(BUCKET_NAME, CannedAccessControlList.PublicRead);
            }
            // 获取文件名
            String filename = UUID.randomUUID() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
            // 将文件上传到阿里云
            ossClient.putObject(BUCKET_NAME,filename,file.getInputStream());
            // 获取url地址
            url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("//")+2)+"/"+filename;
            // 关闭客户端
            ossClient.shutdown();

            map.put("code",1001);
            map.put("url",url);
            map.put("msg","上传成功");
            return map;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3.4、出租功能

  • 订单的curd,OrderController.java
package com.llh.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.crypto.digest.DigestUtil;
import com.llh.domain.Order;
import com.llh.service.CarService;
import com.llh.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * User: lilinhan
 * DateTime: 2023/10/31 15:25
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    OrderService orderService;

    @Resource
    CarService carService;

    @Autowired
    RedisTemplate redisTemplate;

    // 通过编号更新订单
    @RequestMapping("/updateOrderByNo")
    public void updateOrderByNo(String no){
        orderService.updateOrderByNo(no);
    }

    // 生成订单
    @RequestMapping("/save")
    public Map<String,Object> save(Order order){
        Map<String,Object> map = new HashMap<>();

        String md5Hex = DigestUtil.md5Hex(order + "");
        Boolean b = redisTemplate.opsForValue().setIfAbsent(md5Hex, md5Hex, 1, TimeUnit.MINUTES);
        if(!b){
            map.put("flag",false);
            map.put("msg","一分钟禁止重复提交");
            return map;
        }

        // 订单编号
        String no = new Snowflake().nextIdStr();
        // 计算租期
        long days = DateUtil.betweenDay(order.getCarStart(), order.getCarEnd(), false);
        // 租金的费用
        order.setCarPrice(order.getCarRent().multiply(new BigDecimal(days)));
        order.setNum(no);
        order.setCreateTime(new Date());
        // 录入订单
        boolean save = orderService.save(order);
        // 修改车辆表状态
        carService.updateCarByNo(order.getCarNo());

        map.put("flag",save);
        map.put("msg","生成订单成功");
        return map;
    }

    // 订单列表
    @RequestMapping("/list")
    public List<Order> list(){
        return orderService.list();
    }
}

3.5、支付功能

  • PayController.java
package com.llh.controller;

import cn.hutool.extra.servlet.ServletUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.llh.domain.Order;
import com.llh.service.OrderService;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
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.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: lilinhan
 * DateTime: 2023/10/31 18:51
 */
@RestController
@RequestMapping("/pay")
public class PayController {

    String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgmJ5v66LB3uJiOa+/J+m1B3sgn1TNqO1Lh3gcfEoY3VIZCzqrGMCRrq0zk+EpOuchCZZJolN72AAWor97aLxCZzsVebhWZNvywCYW0fkbUF25uMKaIEQoNasPnLMToflhyzzmS4bLnamt+kv8xQf6XSM5FerT4J6w2JFFi1Vmmz0gTfGfqU6xpWDCjP6QZ7eOj/ujWvTgtavpG8cnsaqnakl1VeYkMW5jXGsOq16ms5W1/a9IsCw/Oe1Ez2vPKwEjxv+xxNpTdxcQm+Oq7Dc8/gAxkJBUy7SbI88II54MbY7qWDM+xKuY+WigOjNWiX/3m/M7RCrduLOV3Y8aGiBdQIDAQAB";

    @Autowired
    OrderService orderService;

    @Autowired
    RocketMQTemplate rocketMQTemplate;

    @Autowired
    HttpServletResponse servletResponse;

    @Autowired
    HttpServletRequest request;

    // 前往支付
    @RequestMapping("/goPay")
    public void goPay(String no) throws AlipayApiException, IOException {
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("num", no);
        Order orderDB = orderService.getOne(wrapper);

        Message<String> message = MessageBuilder.withPayload(no).build();
        // 异步发送mq
        rocketMQTemplate.asyncSend("car2", message, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
                    System.err.println("发送成功");
                }
            }

            @Override
            public void onException(Throwable e) {
                System.err.println("连接失败");
            }
        });

        String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCLHDjvEn6fjWNOV/SGgkNv7B0AsgTA1WuKypedU2WIJ4hE5ZvJI5NiYgyh/O/HeFPYPhv+P7wv1+ATKGc5yyvb7I2b8/3MZhxFYMgn/4HjgJ9EGGfSSIkM5JqoZFHRuUNu5I1a9PqTW7SaReRdmXs3axVuHd+MYLh9KVGJyAiueAtQwq040EZHHz5lQ9oSj/R7zPEl4jXtXKHq4MC/tRl/wqAYeKwMeati9iUhK9BHYzxeTiV08l6UrMZteQ7duNxJTIg/uLQdgb7DwaWcBgy514Ra50PKmpXlHasIwhXfnW58aSpVjzdnbYu290JsGXW9h5CRp1VxZlqDfeg7zC95AgMBAAECggEAemldWFNIBZLfXiNb07lxKl31r5T3RdEID1vzSsgGQme3LBl4muipWxu4zhrLzRV/gnw1Gehv6xHl5jXZkCvO7nocqq1sGp+IJSzjNP9MByw5+iwXwB2ALE5GIgQVU53ZTw3jrbSBg4ZhSJhUOmFC4iNi+kFjZB4AenMPg1T4/rzqPPI17FO8MsW1CYiRmQcajkSDm37WXEayOaeSUXbDbxwUU96sNYU96kOuS92rX05hiZ+mX4XtKLqPuUVp0OKHe7WVEnf4sQp0nnZV4FjIS1MFk/dG24RfXBNtAkkA9m0NRxC0MTPEc2bwbjJ6bRyaETLMdzEN5d2qgCBYQzwJWQKBgQDQnHtx9Sg43XeSgqssRNaRkqC+naqyccorSfa5MTzxYfU1ByWlI4/SFfqHAbGTv2i6M191tOqffGHRHQUnyv8pSNwEbTUaQ7qZO03KiRc5cKw21U9UPrhUM8Lb5l7W2hrkpcLf5HOHOwqyMy215M84G8AsMb2vFhTD0+zh4fWHWwKBgQCqtf+kvhrO8bQQ69lgow/RB1g06VCM7I8a8WCWeLjbNwJujpcBmZA+ozVydmTsa3RNqaBfcUe6V5qQ8HCfLU4M9mKAd1cZSpP0/gwxjXBanT0pW2IaqfCJnwSZE602hNJhaDcIo0UEa94EK0TD67bpjqbPmrkt8GADjxGj3jfwuwKBgQCxSHrEErL2+IcYdIGhonKyzehbdcRN19QWy8moAocnH7dyNDuyxrD/ufvpZfASfrUyoFv8vR4zIVJDkyUBd0s3O5r3DBP32R0oAbUPbUfWYcGI6+h50L93l3F+zZ1Fe58lNxWQjiX8A5oIbOPo06Wiqjq7lB3+QIavEVgbcprlRwKBgB5D5A3uEkysN3NkjQaAAlbZyX6f+sLxUbHCJ5Cl2Igs6YZ2SzliY1tnjhs7+EqeBmGaeC9ToxbbXzDhH+ZontTXfcEu50c5nJ+rxremTswR4n0JkYwOovGTOrp07Zmj1LSmSFOEtsDA6vysOyXNDYG848Acu0X0vuECpWr7A8nvAoGBAJwEnEs86l0koOSo/DVVaVudJkMXhvl9GT7wG/MiEvknjTWosdVRgquEzGjmE25eK/4epOCONVU7bweloLSLbZ/OGGPGS4jHhJ3Kw/e4KBn4+nCi2g2/7CLfGvaydXmhgRy4qjIATfLceXtXaBNj7HIC1kCwzSphFwPzSM6DDEU8";
        String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgmJ5v66LB3uJiOa+/J+m1B3sgn1TNqO1Lh3gcfEoY3VIZCzqrGMCRrq0zk+EpOuchCZZJolN72AAWor97aLxCZzsVebhWZNvywCYW0fkbUF25uMKaIEQoNasPnLMToflhyzzmS4bLnamt+kv8xQf6XSM5FerT4J6w2JFFi1Vmmz0gTfGfqU6xpWDCjP6QZ7eOj/ujWvTgtavpG8cnsaqnakl1VeYkMW5jXGsOq16ms5W1/a9IsCw/Oe1Ez2vPKwEjxv+xxNpTdxcQm+Oq7Dc8/gAxkJBUy7SbI88II54MbY7qWDM+xKuY+WigOjNWiX/3m/M7RCrduLOV3Y8aGiBdQIDAQAB";
        AlipayConfig alipayConfig = new AlipayConfig();
        alipayConfig.setServerUrl("https://openapi-sandbox.dl.alipaydev.com/gateway.do");
        alipayConfig.setAppId("9021000130615233");
        alipayConfig.setPrivateKey(privateKey);
        alipayConfig.setFormat("json");
        alipayConfig.setAlipayPublicKey(alipayPublicKey);
        alipayConfig.setCharset("UTF-8");
        alipayConfig.setSignType("RSA2");
        AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

        request.setReturnUrl("http://127.0.0.1:9999/car-api/pay/callback");

        AlipayTradePagePayModel model = new AlipayTradePagePayModel();

        model.setOutTradeNo(orderDB.getNum());
        model.setTotalAmount(orderDB.getCarPrice() + "");
        model.setSubject(orderDB.getCarNo());
        model.setProductCode("FAST_INSTANT_TRADE_PAY");
        request.setBizModel(model);

        AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
        String body = response.getBody();

        if (response.isSuccess()) {
            System.err.println("调用成功");
            servletResponse.setContentType("text/html;charset=utf8");
            PrintWriter writer = servletResponse.getWriter();
            writer.write(body);
            writer.flush();
            writer.close();
        } else {
            System.err.println("调用失败");
        }

    }

    // 回调方法
    @RequestMapping("/callback")
    public void callback(String out_trade_no) throws IOException, AlipayApiException {

        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("num", out_trade_no);
        Order orderDB = orderService.getOne(wrapper);
        orderDB.setStat(1);
        orderDB.setRemark("已支付");
        orderService.updateById(orderDB);

        // 使用hutool把参数转换为map集合
        Map<String, String> params = ServletUtil.getParamMap(request);
        // 验证签名
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8", "RSA2"); //调用SDK验证签名
        if (signVerified) {
            System.err.println("回滚的订单编号:" + out_trade_no);
            servletResponse.sendRedirect("http://127.0.0.1:8080/order");
        }
    }

    // 退款
    @RequestMapping("/refund")
    public Map<String,Object> refund(String no) throws AlipayApiException {
        Map<String,Object> map  = new HashMap<>();
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("num", no);
        Order orderDB = orderService.getOne(wrapper);

        String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCLHDjvEn6fjWNOV/SGgkNv7B0AsgTA1WuKypedU2WIJ4hE5ZvJI5NiYgyh/O/HeFPYPhv+P7wv1+ATKGc5yyvb7I2b8/3MZhxFYMgn/4HjgJ9EGGfSSIkM5JqoZFHRuUNu5I1a9PqTW7SaReRdmXs3axVuHd+MYLh9KVGJyAiueAtQwq040EZHHz5lQ9oSj/R7zPEl4jXtXKHq4MC/tRl/wqAYeKwMeati9iUhK9BHYzxeTiV08l6UrMZteQ7duNxJTIg/uLQdgb7DwaWcBgy514Ra50PKmpXlHasIwhXfnW58aSpVjzdnbYu290JsGXW9h5CRp1VxZlqDfeg7zC95AgMBAAECggEAemldWFNIBZLfXiNb07lxKl31r5T3RdEID1vzSsgGQme3LBl4muipWxu4zhrLzRV/gnw1Gehv6xHl5jXZkCvO7nocqq1sGp+IJSzjNP9MByw5+iwXwB2ALE5GIgQVU53ZTw3jrbSBg4ZhSJhUOmFC4iNi+kFjZB4AenMPg1T4/rzqPPI17FO8MsW1CYiRmQcajkSDm37WXEayOaeSUXbDbxwUU96sNYU96kOuS92rX05hiZ+mX4XtKLqPuUVp0OKHe7WVEnf4sQp0nnZV4FjIS1MFk/dG24RfXBNtAkkA9m0NRxC0MTPEc2bwbjJ6bRyaETLMdzEN5d2qgCBYQzwJWQKBgQDQnHtx9Sg43XeSgqssRNaRkqC+naqyccorSfa5MTzxYfU1ByWlI4/SFfqHAbGTv2i6M191tOqffGHRHQUnyv8pSNwEbTUaQ7qZO03KiRc5cKw21U9UPrhUM8Lb5l7W2hrkpcLf5HOHOwqyMy215M84G8AsMb2vFhTD0+zh4fWHWwKBgQCqtf+kvhrO8bQQ69lgow/RB1g06VCM7I8a8WCWeLjbNwJujpcBmZA+ozVydmTsa3RNqaBfcUe6V5qQ8HCfLU4M9mKAd1cZSpP0/gwxjXBanT0pW2IaqfCJnwSZE602hNJhaDcIo0UEa94EK0TD67bpjqbPmrkt8GADjxGj3jfwuwKBgQCxSHrEErL2+IcYdIGhonKyzehbdcRN19QWy8moAocnH7dyNDuyxrD/ufvpZfASfrUyoFv8vR4zIVJDkyUBd0s3O5r3DBP32R0oAbUPbUfWYcGI6+h50L93l3F+zZ1Fe58lNxWQjiX8A5oIbOPo06Wiqjq7lB3+QIavEVgbcprlRwKBgB5D5A3uEkysN3NkjQaAAlbZyX6f+sLxUbHCJ5Cl2Igs6YZ2SzliY1tnjhs7+EqeBmGaeC9ToxbbXzDhH+ZontTXfcEu50c5nJ+rxremTswR4n0JkYwOovGTOrp07Zmj1LSmSFOEtsDA6vysOyXNDYG848Acu0X0vuECpWr7A8nvAoGBAJwEnEs86l0koOSo/DVVaVudJkMXhvl9GT7wG/MiEvknjTWosdVRgquEzGjmE25eK/4epOCONVU7bweloLSLbZ/OGGPGS4jHhJ3Kw/e4KBn4+nCi2g2/7CLfGvaydXmhgRy4qjIATfLceXtXaBNj7HIC1kCwzSphFwPzSM6DDEU8";
        String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgmJ5v66LB3uJiOa+/J+m1B3sgn1TNqO1Lh3gcfEoY3VIZCzqrGMCRrq0zk+EpOuchCZZJolN72AAWor97aLxCZzsVebhWZNvywCYW0fkbUF25uMKaIEQoNasPnLMToflhyzzmS4bLnamt+kv8xQf6XSM5FerT4J6w2JFFi1Vmmz0gTfGfqU6xpWDCjP6QZ7eOj/ujWvTgtavpG8cnsaqnakl1VeYkMW5jXGsOq16ms5W1/a9IsCw/Oe1Ez2vPKwEjxv+xxNpTdxcQm+Oq7Dc8/gAxkJBUy7SbI88II54MbY7qWDM+xKuY+WigOjNWiX/3m/M7RCrduLOV3Y8aGiBdQIDAQAB";
        AlipayConfig alipayConfig = new AlipayConfig();
        alipayConfig.setServerUrl("https://openapi-sandbox.dl.alipaydev.com/gateway.do");
        alipayConfig.setAppId("9021000130615233");
        alipayConfig.setPrivateKey(privateKey);
        alipayConfig.setFormat("json");
        alipayConfig.setAlipayPublicKey(alipayPublicKey);
        alipayConfig.setCharset("UTF-8");
        alipayConfig.setSignType("RSA2");
        AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();

        model.setRefundAmount(orderDB.getCarPrice()+"");
        model.setOutTradeNo(orderDB.getNum());
        request.setBizModel(model);

        AlipayTradeRefundResponse response = alipayClient.execute(request);
        System.out.println(response.getBody());
        if (response.isSuccess()) {
            System.out.println("调用成功");
            orderDB.setStat(2);
            orderDB.setRemark("已退款");
            boolean b = orderService.updateById(orderDB);
            map.put("flag",b);
            map.put("msg","退款成功");
            return map;
        } else {
            map.put("flag",false);
            map.put("msg","调用失败");
            return map;
        }
    }

    // 核对订单
    @RequestMapping("/check")
    public Map<String,Object> check() throws AlipayApiException {
        Map<String,Object> map  = new HashMap<>();
        List<Order> list = orderService.list();
        for (Order order : list) {
            String no = order.getNum();
            if(order.getStat()!=2){
                String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCLHDjvEn6fjWNOV/SGgkNv7B0AsgTA1WuKypedU2WIJ4hE5ZvJI5NiYgyh/O/HeFPYPhv+P7wv1+ATKGc5yyvb7I2b8/3MZhxFYMgn/4HjgJ9EGGfSSIkM5JqoZFHRuUNu5I1a9PqTW7SaReRdmXs3axVuHd+MYLh9KVGJyAiueAtQwq040EZHHz5lQ9oSj/R7zPEl4jXtXKHq4MC/tRl/wqAYeKwMeati9iUhK9BHYzxeTiV08l6UrMZteQ7duNxJTIg/uLQdgb7DwaWcBgy514Ra50PKmpXlHasIwhXfnW58aSpVjzdnbYu290JsGXW9h5CRp1VxZlqDfeg7zC95AgMBAAECggEAemldWFNIBZLfXiNb07lxKl31r5T3RdEID1vzSsgGQme3LBl4muipWxu4zhrLzRV/gnw1Gehv6xHl5jXZkCvO7nocqq1sGp+IJSzjNP9MByw5+iwXwB2ALE5GIgQVU53ZTw3jrbSBg4ZhSJhUOmFC4iNi+kFjZB4AenMPg1T4/rzqPPI17FO8MsW1CYiRmQcajkSDm37WXEayOaeSUXbDbxwUU96sNYU96kOuS92rX05hiZ+mX4XtKLqPuUVp0OKHe7WVEnf4sQp0nnZV4FjIS1MFk/dG24RfXBNtAkkA9m0NRxC0MTPEc2bwbjJ6bRyaETLMdzEN5d2qgCBYQzwJWQKBgQDQnHtx9Sg43XeSgqssRNaRkqC+naqyccorSfa5MTzxYfU1ByWlI4/SFfqHAbGTv2i6M191tOqffGHRHQUnyv8pSNwEbTUaQ7qZO03KiRc5cKw21U9UPrhUM8Lb5l7W2hrkpcLf5HOHOwqyMy215M84G8AsMb2vFhTD0+zh4fWHWwKBgQCqtf+kvhrO8bQQ69lgow/RB1g06VCM7I8a8WCWeLjbNwJujpcBmZA+ozVydmTsa3RNqaBfcUe6V5qQ8HCfLU4M9mKAd1cZSpP0/gwxjXBanT0pW2IaqfCJnwSZE602hNJhaDcIo0UEa94EK0TD67bpjqbPmrkt8GADjxGj3jfwuwKBgQCxSHrEErL2+IcYdIGhonKyzehbdcRN19QWy8moAocnH7dyNDuyxrD/ufvpZfASfrUyoFv8vR4zIVJDkyUBd0s3O5r3DBP32R0oAbUPbUfWYcGI6+h50L93l3F+zZ1Fe58lNxWQjiX8A5oIbOPo06Wiqjq7lB3+QIavEVgbcprlRwKBgB5D5A3uEkysN3NkjQaAAlbZyX6f+sLxUbHCJ5Cl2Igs6YZ2SzliY1tnjhs7+EqeBmGaeC9ToxbbXzDhH+ZontTXfcEu50c5nJ+rxremTswR4n0JkYwOovGTOrp07Zmj1LSmSFOEtsDA6vysOyXNDYG848Acu0X0vuECpWr7A8nvAoGBAJwEnEs86l0koOSo/DVVaVudJkMXhvl9GT7wG/MiEvknjTWosdVRgquEzGjmE25eK/4epOCONVU7bweloLSLbZ/OGGPGS4jHhJ3Kw/e4KBn4+nCi2g2/7CLfGvaydXmhgRy4qjIATfLceXtXaBNj7HIC1kCwzSphFwPzSM6DDEU8";
                String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgmJ5v66LB3uJiOa+/J+m1B3sgn1TNqO1Lh3gcfEoY3VIZCzqrGMCRrq0zk+EpOuchCZZJolN72AAWor97aLxCZzsVebhWZNvywCYW0fkbUF25uMKaIEQoNasPnLMToflhyzzmS4bLnamt+kv8xQf6XSM5FerT4J6w2JFFi1Vmmz0gTfGfqU6xpWDCjP6QZ7eOj/ujWvTgtavpG8cnsaqnakl1VeYkMW5jXGsOq16ms5W1/a9IsCw/Oe1Ez2vPKwEjxv+xxNpTdxcQm+Oq7Dc8/gAxkJBUy7SbI88II54MbY7qWDM+xKuY+WigOjNWiX/3m/M7RCrduLOV3Y8aGiBdQIDAQAB";
                AlipayConfig alipayConfig = new AlipayConfig();
                alipayConfig.setServerUrl("https://openapi-sandbox.dl.alipaydev.com/gateway.do");
                alipayConfig.setAppId("9021000130615233");
                alipayConfig.setPrivateKey(privateKey);
                alipayConfig.setFormat("json");
                alipayConfig.setAlipayPublicKey(alipayPublicKey);
                alipayConfig.setCharset("UTF-8");
                alipayConfig.setSignType("RSA2");
                AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
                AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
                AlipayTradeQueryModel model = new AlipayTradeQueryModel();

                model.setOutTradeNo(no);
                request.setBizModel(model);
                AlipayTradeQueryResponse response = alipayClient.execute(request);
                System.out.println(response.getBody());
                if (response.isSuccess()) {
                    // 已支付,状态码未改
                    System.out.println("调用成功");
                    if(order.getStat()==1){
                        order.setRemark("已支付");
                    }else {
                        order.setRemark("已支付,状态码未改");
                    }
                } else {
                    // 未支付,状态码已改
                    System.out.println("调用失败");
                    if(order.getStat()==0){

                    }else {
                        order.setRemark("未支付,状态码已改");
                    }
                }
            }
        }
        boolean b = orderService.updateBatchById(list);
        if(b){
            map.put("flag",b);
            map.put("msg","校验成功");
            return map;
        }else {
            map.put("flag",false);
            map.put("msg","校验失败");
            return map;
        }

    }
}
  • mq消费者,MyListener.java
package com.llh.listener;

import cn.hutool.core.collection.CollUtil;
import com.llh.service.OrderService;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * User: lilinhan
 * DateTime: 2023/10/31 19:16
 */
@Component
public class MyListener implements MessageListenerOrderly {
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    OrderService orderService;

    // 初始化mq
    @PostConstruct
    public void init() throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.subscribe("car2", "*");
        consumer.setConsumerGroup("dsf");
        consumer.setInstanceName("name");
        consumer.registerMessageListener(this);
        consumer.start();
    }

    // 接收的有序消息队列
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
        if(CollUtil.isNotEmpty(msgs)){
            for (MessageExt msg : msgs) {
                String body = new String(msg.getBody());
                System.err.println("接受的消息--------"+body);

                return ConsumeOrderlyStatus.SUCCESS;
            }
        }else {
            System.err.println("消息为空");
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
}

3.6、模拟抢单

  • 配置类,MybatisPlusConfig.java
package com.llh.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * User: lilinhan
 * DateTime: 2023/10/13 11:53
 */
@Configuration
@MapperScan(value = {"com.llh.mapper"})
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        // 分页拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);// 核心线程数量
        taskExecutor.setMaxPoolSize(10);// 最大线程数量
        taskExecutor.setKeepAliveSeconds(2);// 设置时长(秒)
        taskExecutor.setQueueCapacity(10);// 设置队列容量
        taskExecutor.setThreadNamePrefix("llh-thread:");// 线程名前缀
        return taskExecutor;
    }

}
  • ThreadController.java
package com.llh.controller;

import cn.hutool.core.lang.Snowflake;
import com.llh.domain.Takeaway;
import com.llh.service.TakeawayService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.*;

/**
 * User: lilinhan
 * DateTime: 2023/11/2 14:04
 */
@RestController
@RequestMapping("/thread")
public class ThreadController {
    @Autowired
    TakeawayService takeawayService;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    RedissonClient redissonClient;

    private int count = 1;

    // 生成订单
    @RequestMapping("/test")
    public boolean test(){
        for (int i = 0; i < 3; i++) {
            threadPoolTaskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    List<Takeaway> list = new ArrayList<>();
                    for (int i1 = 0; i1 < 5; i1++) {
                        System.out.println("当前线程-----"+Thread.currentThread().getId()+":"+Thread.currentThread().getName());
                        // 生成实体
                        Takeaway takeaway = new Takeaway();
                        takeaway.setPrice(new BigDecimal(20.22));
                        takeaway.setFroms(new Snowflake().nextIdStr());
                        takeaway.setTos(new Snowflake().nextIdStr());
                        takeaway.setCreated(new Date());

                        list.add(takeaway);
                    }
                    takeawayService.saveBatch(list);
                }

            });
        }
        return true;

    }

    // 测试抢单
    @RequestMapping("/size")
    public Map<String,Object> size(String no){
        Map<String,Object> map = new HashMap<>();
        System.err.println("当前用户:"+Thread.currentThread().getId()+",正在抢购商品:"+no);
//        Boolean b1 = redisTemplate.opsForValue().setIfAbsent(no, no, 2, TimeUnit.SECONDS);
        RLock lock = redissonClient.getLock(no);

        if(lock.tryLock()){
            try {
                // 通过id查询订单
                Takeaway takeaway = takeawayService.getById(no);

                takeaway.setStat(1);// 已抢
                Thread.sleep(3000);
                boolean b = takeawayService.updateById(takeaway);
                if(takeaway.getStat()==1){
                    System.err.println("此商品已被抢购");
                }
                if(b){
                    System.err.println("恭喜用户:"+Thread.currentThread().getId()+",@@@@@@成功抢购到商品:"+no);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }finally {
                if(lock!=null && lock.isHeldByCurrentThread()){
                    lock.unlock();;
                }
            }

        }else {
            System.err.println("抢购结束");
        }

        return map;
    }
}

4、前端代码

  • 用户登录,LoginView.vue
<script>
import qs from "qs";

export default {
    data(){
        return{
            formInline:{},
            formInline2:{},
            formInline3:{},
            activeName: 'easyLogin',
            codeUrl:'http://127.0.0.1:9999/user-api/user/getCode',
            rules:{
                name:[
                    { required: true, message: '请输入账号', trigger: 'blur' },
                ],
                phone:[
                    { required: true, message: '请输入手机号', trigger: 'blur' },
                ],
                pwd:[
                    { required: true, message: '请输入密码', trigger: 'blur' },
                ],
                code:[
                    { required: true, message: '请输入验证码', trigger: 'blur' },
                ],
                email:[
                    { required: true, message: '请输入邮箱', trigger: 'blur' },
                ]
            }
        }
    },
    methods:{
        handleClick(tab, event) {
            console.log(tab, event);
        },
        newCode(){
            this.codeUrl = 'http://127.0.0.1:9999/user-api/user/getCode?time='+new Date().getTime();
        },
        onSubmit(loginForm){
            this.$refs[loginForm].validate((val)=>{
                if(val){
                    this.axios.post("/user-api/user/login",qs.stringify(this.formInline)).then(res=>{
                        if(res.data.code==1001){
                            this.$message.success(res.data.msg);
                            localStorage.setItem("token",res.data.token);
                            localStorage.setItem("loginName", res.data.loginName)
                            localStorage.setItem("userId", res.data.userId)
                            this.$router.push("/good");
                        }else{
                            this.$message.error(res.data.msg);
                            this.newCode();
                            this.formInline.code = '';
                        }
                    })
                }else {
                    return false;
                }
            })
        },
        sendSms(tel){
            this.axios.post("/user-api/user/sendSms?phone="+tel).then(res=>{
                if(res.data.code==1001){
                    this.$message.success(res.data.msg);
                }else {
                    this.$message.error(res.data.msg);
                }
            })
        },
        onSubmit2(loginForm){
            this.$refs[loginForm].validate((val)=>{
                if(val){
                    this.axios.post("/user-api/user/phoneLogin",qs.stringify(this.formInline2)).then(res=>{
                        if(res.data.code==1001){
                            this.$message.success(res.data.msg);
                            localStorage.setItem("token",res.data.token);
                            this.$router.push("/list");
                        }else{
                            this.$message.error(res.data.msg);
                            this.formInline2.code = '';
                        }
                    })
                }else {
                    return false;
                }
            })
        },
        Register(loginForm){
            this.$refs[loginForm].validate((val)=>{
                if(val){
                    this.axios.post("/user-api/user/register",qs.stringify(this.formInline3)).then(res=>{
                        if(res.data.code==1001){
                            this.$message.success(res.data.msg);
                            this.activeName = "easyLogin";
                        }else {
                            this.$message.error(res.data.msg);
                        }
                    })
                }else {
                    return false;
                }
            })
        },
        resetForm(formName) {
            this.$refs[formName].resetFields();
        }
    },
    created() {
    
    }
}
</script>

<template>
    <div>
        <el-tabs v-model="activeName" @tab-click="handleClick">
            <el-tab-pane label="普通登录" name="easyLogin">
                <el-form :inline="true" :rules="rules" ref="formInline" :model="formInline" class="demo-form-inline">
                    <el-form-item label="账号" prop="name">
                        <el-input v-model="formInline.name" placeholder="账号"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item label="密码" prop="pwd">
                        <el-input v-model="formInline.pwd" placeholder="密码"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item label="验证码" prop="code">
                        <el-input v-model="formInline.code" placeholder="验证码"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item>
                        <el-image @click="newCode" :src="codeUrl"></el-image>
                    </el-form-item><br>
                    
                    <el-form-item>
                        <el-button type="primary" @click="onSubmit('formInline')">登录</el-button>
                        <el-button @click="resetForm('formInline')">重置</el-button>
                    </el-form-item>
                </el-form>
            </el-tab-pane>
            
            <el-tab-pane label="手机号加验证码登录" name="telAndYzm">
                <el-form :inline="true" :rules="rules" ref="formInline2" :model="formInline2" class="demo-form-inline">
                    <el-form-item label="手机号" prop="phone">
                        <el-input v-model="formInline2.phone" placeholder="手机号"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item label="验证码" prop="code">
                        <el-input v-model="formInline2.code" placeholder="验证码"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item>
                        <el-button type="primary" @click="sendSms(formInline2.phone)">发送短信</el-button>
                        <el-button type="primary" @click="onSubmit2('formInline2')">登录</el-button>
                    </el-form-item>
                </el-form>
            </el-tab-pane>
            
            <el-tab-pane label="邮箱注册" name="register">
                <el-form :inline="true" :rules="rules" ref="formInline3" :model="formInline3" class="demo-form-inline">
                    <el-form-item label="邮箱" prop="email">
                        <el-input v-model="formInline3.email" placeholder="QQ号">
                            <template slot="append">@qq.com</template>
                        </el-input>
                    </el-form-item><br>
                    
                    <el-form-item label="手机号" prop="phone">
                        <el-input v-model="formInline3.phone" placeholder="手机号"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item label="验证码" prop="code">
                        <el-input v-model="formInline3.code" placeholder="验证码"></el-input>
                    </el-form-item><br>
                    
                    <el-form-item>
                        <el-image @click="newCode" :src="codeUrl"></el-image>
                    </el-form-item><br>
                    
                    <el-form-item>
                        <el-button type="primary" @click="Register('formInline3')">注册</el-button>
                        <el-button @click="resetForm('formInline3')">重置</el-button>
                    </el-form-item>
                </el-form>
            </el-tab-pane>
        </el-tabs>
    
    </div>
</template>

<style scoped>

</style>
  • 车辆列表,ListView.vue
<script>
import qs from "qs";

export default {
    data(){
        return{
            formInline:{},
            current:1,
            size:3,
            models:[],
            brands:[],
            dists:[],
            colors:[],
            tableData:[],
            total:0,
            addDialogFormVisible:false,
            updateDialogFormVisible:false,
            orderDialogFormVisible:false,
            formLabelWidth:'150px',
            addForm:{},
            updateForm:{
                imgUrl:''
            },
            orderForm:{
                userPhone:'',
                carStart:'',
                carEnd:'',
                carRent:0,
            },
            imageUrl:'',
            value:'',
        }
    },
    methods:{
        updateCar(){
            this.axios.post("/car-api/car/update",qs.stringify(this.updateForm)).then(res=>{
                let flag = res.data.flag;
                if(flag){
                    this.$message.success(res.data.msg);
                    this.updateDialogFormVisible = false;
                    this.onSubmit();
                }else {
                    this.$message.success(res.data.msg);
                }
            })
        },
        openUpdate(row){
            this.updateForm = row;
            this.imageUrl = row.imgUrl;
            this.updateDialogFormVisible = true;
        },
        delCar(id){
            this.axios.post("/car-api/car/del?id="+id).then(res=>{
                if(res.data.flag){
                    this.$message.success(res.data.msg);
                    this.onSubmit();
                }else {
                    this.$message.success(res.data.msg);
                }
            })
        },
        saveOrder(){
            this.orderForm.carStart = this.value[0];
            this.orderForm.carEnd = this.value[1];
            this.axios.post("/car-api/order/save",qs.stringify(this.orderForm)).then(res=>{
                let flag = res.data.flag;
                if(flag){
                    this.$message.success(res.data.msg);
                    this.orderDialogFormVisible = false;
                    this.$router.push("/order")
                }else {
                    this.$message.success(res.data.msg);
                }
            })
        },
        goCar(row){
            this.orderForm.carNo = row.num;
            this.orderForm.userPhone = row.userPhone;
            this.orderForm.carRent = row.rent;
            this.orderDialogFormVisible = true;
        },
        openAdd(){
            this.addForm = {};
            this.addDialogFormVisible = true;
        },
        saveCar(){
            this.axios.post("/car-api/car/save",qs.stringify(this.addForm)).then(res=>{
                let flag = res.data.flag;
                if(flag){
                    this.$message.success(res.data.msg);
                    this.addDialogFormVisible = false;
                    this.onSubmit();
                }else {
                    this.$message.success(res.data.msg);
                }
            })
        },
        handleAvatarSuccess(res, file) {
            this.imageUrl = URL.createObjectURL(file.raw);
            this.addForm.imgUrl = res.url;
            this.updateForm.imgUrl = res.url;
        },
        handleSizeChange(val) {
            console.log(`每页 ${val} 条`);
            this.size = val;
            this.onSubmit();
        },
        handleCurrentChange(val) {
            console.log(`当前页: ${val}`);
            this.current = val;
            this.onSubmit();
        },
        initList(){
            this.axios.get("/car-api/dist/list").then(res=>{
                this.models = res.data.models;
                this.brands = res.data.brands;
                this.dists = res.data.dists;
                this.colors = res.data.colors;
            })
        },
        onSubmit(){
            this.axios.post("/car-api/car/list?current="+this.current+"&size="+this.size,qs.stringify(this.formInline)).then(res=>{
                this.tableData = res.data.records;
                this.total = res.data.total;
            })
        }
    },
    created() {
        this.initList();
        this.onSubmit();
    }
}
</script>

<template>
<div>
    <el-form :inline="true" :model="formInline" class="demo-form-inline">
        <el-form-item label="车辆品牌">
            <el-select v-model="formInline.brandId" placeholder="车辆品牌" clearable="clearable">
                <el-option v-for="c in brands" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
            </el-select>
        </el-form-item>
        <el-form-item label="车辆型号">
            <el-select v-model="formInline.modelId" placeholder="车辆型号" clearable="clearable">
                <el-option v-for="c in models" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
            </el-select>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="onSubmit">查询</el-button>
            <el-button type="primary" @click="openAdd">添加</el-button>
        </el-form-item>
    </el-form>
    
    <el-table
        :data="tableData"
        border
        style="width: 100%">
        <el-table-column prop="num" label="车辆编号" width="180"></el-table-column>
        <el-table-column prop="modelName" label="型号" width="100"></el-table-column>
        <el-table-column prop="brandName" label="品牌" width="100"></el-table-column>
        <el-table-column prop="outs" label="排量(T)" width="100">
            <template v-slot="scope">
                {{scope.row.outs}}.0
            </template>
        </el-table-column>
        <el-table-column prop="speed" label="变速箱" width="100"></el-table-column>
        <el-table-column prop="years" label="年代" width="100"></el-table-column>
        <el-table-column prop="rent" label="日租金" width="100"></el-table-column>
        <el-table-column prop="imgUrl" label="图片" width="150">
            <template v-slot="scope">
                <el-image :src="scope.row.imgUrl" style="width: 80px;height: 80px"></el-image>
            </template>
        </el-table-column>
        <el-table-column prop="detail" label="介绍" width="180"></el-table-column>
        <el-table-column prop="stat" label="状态" width="100">
            <template v-slot="scope">
                <span v-if="scope.row.stat==0" style="color: green">空闲</span>
                <span v-if="scope.row.stat==1" style="color: orange">出租</span>
            </template>
        </el-table-column>
        <el-table-column label="操作" width="150">
            <template v-slot="scope">
                <el-button type="warning" size="mini" @click="openUpdate(scope.row)">编辑</el-button>
                <el-button type="danger" size="mini" @click="delCar(scope.row.id)">删除</el-button>
                <span v-if="scope.row.stat==0">
                    <el-button type="primary" size="mini" @click="goCar(scope.row)">租车</el-button>
                </span>
            </template>
        </el-table-column>
    </el-table>
    
    <el-pagination
        background
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="current"
        :page-sizes="[3, 6, 9, 12]"
        :page-size="size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total">
    </el-pagination>
    
    <el-dialog title="车辆录入" :visible.sync="addDialogFormVisible">
        <el-form :model="addForm">
            <el-form-item label="车辆信息:" :label-width="formLabelWidth">
                <el-select v-model="addForm.brandId" placeholder="请选择车辆品牌" clearable="clearable">
                    <el-option v-for="c in brands" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
                </el-select>
                <el-select v-model="addForm.distId" placeholder="请选择车系" clearable="clearable">
                    <el-option v-for="c in dists" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
                </el-select>
                <el-select v-model="addForm.modelId" placeholder="请选择车辆型号" clearable="clearable">
                    <el-option v-for="c in models" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
                </el-select>
            </el-form-item>
            
            <el-form-item label="预售价格:" :label-width="formLabelWidth">
                <el-input v-model="addForm.price" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="行驶里程:" :label-width="formLabelWidth">
                <el-input v-model="addForm.mileage" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="车辆颜色:" :label-width="formLabelWidth">
                <el-radio-group v-model="addForm.colorId">
                    <el-radio v-for="c in colors" :label="c.id" >{{c.distValue}}</el-radio>
                </el-radio-group>
            </el-form-item>
            
            <el-form-item label="联系人:" :label-width="formLabelWidth">
                <el-input v-model="addForm.userName" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="手机号:" :label-width="formLabelWidth">
                <el-input v-model="addForm.userPhone" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="车辆介绍:" :label-width="formLabelWidth">
                <el-input type="textarea" v-model="addForm.detail" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="车辆图片:" :label-width="formLabelWidth">
                <el-upload
                    class="avatar-uploader"
                    action="http://127.0.0.1:9999/car-api/upload/minio"
                    :show-file-list="false"
                    :on-success="handleAvatarSuccess">
                    <img v-if="imageUrl" :src="imageUrl" class="avatar">
                    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                </el-upload>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="addDialogFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveCar('addForm')">确 定</el-button>
        </div>
    </el-dialog>
    
    <!--车辆修改-->
    <el-dialog title="车辆修改" :visible.sync="updateDialogFormVisible">
        <el-form :model="updateForm">
            <el-form-item label="车辆信息:" :label-width="formLabelWidth">
                <el-select v-model="updateForm.brandId" placeholder="请选择车辆品牌" clearable="clearable">
                    <el-option v-for="c in brands" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
                </el-select>
                <el-select v-model="updateForm.distId" placeholder="请选择车系" clearable="clearable">
                    <el-option v-for="c in dists" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
                </el-select>
                <el-select v-model="updateForm.modelId" placeholder="请选择车辆型号" clearable="clearable">
                    <el-option v-for="c in models" :key="c.id" :label="c.distValue" :value="c.id"></el-option>
                </el-select>
            </el-form-item>
            
            <el-form-item label="预售价格:" :label-width="formLabelWidth">
                <el-input v-model="updateForm.price" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="行驶里程:" :label-width="formLabelWidth">
                <el-input v-model="updateForm.mileage" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="车辆颜色:" :label-width="formLabelWidth">
                <el-radio-group v-model="updateForm.colorId">
                    <el-radio v-for="c in colors" :label="c.id" >{{c.distValue}}</el-radio>
                </el-radio-group>
            </el-form-item>
            
            <el-form-item label="联系人:" :label-width="formLabelWidth">
                <el-input v-model="updateForm.userName" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="手机号:" :label-width="formLabelWidth">
                <el-input v-model="updateForm.userPhone" autocomplete="off" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="车辆介绍:" :label-width="formLabelWidth">
                <el-input type="textarea" v-model="updateForm.detail" clearable="clearable"></el-input>
            </el-form-item>
            
            <el-form-item label="车辆图片:" :label-width="formLabelWidth">
                <el-upload
                    class="avatar-uploader"
                    action="http://127.0.0.1:9999/car-api/upload/minio"
                    :show-file-list="false"
                    :on-success="handleAvatarSuccess">
                    <img v-if="imageUrl" :src="imageUrl" class="avatar">
                    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                </el-upload>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="updateDialogFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="updateCar('updateForm')">确 定</el-button>
        </div>
    </el-dialog>
    
    <el-dialog title="租车详情" :visible.sync="orderDialogFormVisible">
        <el-form :model="orderForm">
            <el-form-item label="车辆编号" :label-width="formLabelWidth">
                <el-input v-model="orderForm.carNo" autocomplete="off"></el-input>
            </el-form-item>
            
            <el-form-item label="租金/天" :label-width="formLabelWidth">
                <el-input v-model="orderForm.carRent" autocomplete="off"></el-input>
            </el-form-item>
            
            <el-form-item label="租用天数" :label-width="formLabelWidth">
                <el-date-picker
                    v-model="value"
                    type="daterange"
                    value-format="yyyy-MM-dd"
                    range-separator="至"
                    start-placeholder="开始日期"
                    end-placeholder="结束日期">
                </el-date-picker>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="orderDialogFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveOrder">确 定</el-button>
        </div>
    </el-dialog>
</div>
</template>

<style>
.avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
}
.avatar-uploader .el-upload:hover {
    border-color: #409EFF;
}
.avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
}
.avatar {
    width: 178px;
    height: 178px;
    display: block;
}
</style>
  • 订单列表,OrderView.vue
<script>
export default {
    data(){
        return{
            tableData:[],
            
        }
    },
    methods:{
        check(){
            this.axios.get("/car-api/pay/check").then(res=>{
                if(res.data.flag){
                    this.$message.success(res.data.msg);
                    this.initList();
                }else {
                    this.$message.error(res.data.msg);
                }
            })
        },
        refund(no){
            this.axios.get("/car-api/pay/refund?no="+no).then(res=>{
                if(res.data.flag){
                    this.$message.success(res.data.msg);
                    this.initList();
                }else {
                    this.$message.error(res.data.msg);
                }
            })
        },
        goPay(no){
            window.location.href="http://localhost:9999/car-api/pay/goPay?no="+no;
        },
        initList(){
            this.axios.get("/car-api/order/list").then(res=>{
                this.tableData = res.data;
            })
        },
    },
    created() {
        this.initList();
    }
}
</script>

<template>
<div>
    <el-button type="primary" @click="check">校验订单</el-button>
    <el-image src="https://llh-images.oss-cn-beijing.aliyuncs.com/test/6454bb7c-55e7-4ae7-808c-5c6c5204f0e2.jpg" style="width: 100px;height: 100px"></el-image>
    <el-image src="https://llh-images.oss-cn-beijing.aliyuncs.com/bdaffc4d-cdac-46dd-85a4-ea36cecb122b.jpg" style="width: 100px;height: 100px"></el-image>
    
    <el-table
        :data="tableData"
        border
        style="width: 100%">
        <el-table-column prop="num" label="订单编号" width="180"></el-table-column>
        <el-table-column prop="carNo" label="车辆编号" width="180"></el-table-column>
        <el-table-column prop="carRent" label="租金/天" width="150"></el-table-column>
        <el-table-column prop="carPrice" label="费用" width="150"></el-table-column>
        <el-table-column prop="userPhone" label="联系人电话" width="150"></el-table-column>
        <el-table-column prop="remark" label="说明" width="180">
            <template v-slot="scope">
                <span v-if="scope.row.remark=='已支付,状态码未改'" style="color: orange">
                    {{scope.row.remark}}
                    <el-button type="warning" size="mini" @click="goUpdate(scope.row.num)">去修改</el-button>
                </span>
                <span v-if="scope.row.remark=='未支付,状态码已改'" style="color: orange">
                    {{scope.row.remark}}
                    <el-button type="warning" size="mini" @click="goPay(scope.row.num)">去支付</el-button>
                </span>
                <span v-if="scope.row.remark=='已支付'" style="color: green">
                    {{scope.row.remark}}
                </span>
                <span v-if="scope.row.remark=='已退款'" style="color: red">
                    {{scope.row.remark}}
                </span>
            </template>
        </el-table-column>
        <el-table-column prop="createTime" label="提交时间" width="180"></el-table-column>
        <el-table-column label="操作" width="150">
            <template v-slot="scope">
                <span v-if="scope.row.stat==0">
                    <el-button type="primary" size="mini" @click="goPay(scope.row.num)">待支付</el-button>
                </span>
                <span v-if="scope.row.stat==1">
                    <el-button type="danger" size="mini" @click="refund(scope.row.num)">去退款</el-button>
                </span>
                <span v-if="scope.row.stat==2" style="color: red">
                    已退款
                </span>
            </template>
        </el-table-column>
    </el-table>
</div>
</template>

<style scoped>

</style>
标签:JavaSpringCloud
本文到此就结束啦
Last modification:March 25, 2024
如果觉得我的文章对你有用,请随意赞赏