校园闪送项目笔记

搭建前端环境

注册微信开发者账号

打开微信公总平台,按照流程一步步注册:https://mp.weixin.qq.com/

然后去申请开通三个我们项目会用到的接口 ![[Pasted image 20250417092751.png]]

以及我们需要用到的插件 ![[Pasted image 20250417093027.png]] https://fuwu.weixin.qq.com/search?tab=3&type=&serviceType=3&page=1&kw=腾讯位置服务地图选点 fuwu.weixin.qq.com/search?tab=3&type=&serviceType=3&page=1&kw=微信同声传译

安装 node.js 和微信开发者工具

下载node.js:https://nodejs.org/en/download 版本选择16.20.0 ![[Pasted image 20250417093149.png]]

微信开发者工具也是点击下载一步步走就行

  • 下载两遍,安装两个相同的,方便后期项目两个端口一起调试 ![[Pasted image 20250417091648.png]] ![[Pasted image 20250417091741.png]]

在开发者工具中运行前端代码

  • 点击左上角项目栏下的导入项目 ![[Pasted image 20250417093655.png]]

  • 点击上方设置栏中的安全设置,打开里面的服务端口 ![[Pasted image 20250417093936.png]]

另一个也是一样

搭建后端环境

安装软件环境

安装rabbitmq

  • **第一步 拉取镜像

`docker pull rabbitmq:3.9.0-management

  • **第二步 使用容器启动服务

`docker run -d –name=rabbitmq –restart=always -p 5672:5672 -p 15672:15672 rabbitmq:3.9.0-management  

  • **第三步 安装延迟队列插件

1、首先下载rabbitmq_delayed_message_exchange-3.9.0.ez文件上传到RabbitMQ所在服务器,下载地址:https://www.rabbitmq.com/community-plugins.html

2、上传下载延迟队列插件到Linux操作系统中,切换到插件所在目录,

执行 docker cp rabbitmq_delayed_message_exchange-3.9.0.ez rabbitmq:/plugins 命令,将刚插件拷贝到容器内plugins目录下

3、执行 docker exec -it rabbitmq /bin/bash 命令进入到容器内部,并 cd plugins 进入plugins目录

执行 ls -l|grep delay  命令查看插件是否copy成功 在容器内plugins目录下,执行 rabbitmq-plugins enable rabbitmq_delayed_message_exchange  命令启用插件

4、exit命令退出RabbitMQ容器内部,然后执行 docker restart rabbitmq 命令重启RabbitMQ容器

  • **第四步 远程访问设置凭证
# 进入容器
docker exec -it rabbitmq bash

# 创建新用户(用户名:admin,密码:Abc123)
rabbitmqctl add_user admin Abc123

# 赋予管理员权限
rabbitmqctl set_user_tags admin administrator

# 赋予所有权限(虚拟主机为 /)
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

安装redis

  • **第一步 拉取镜像

docker pull redis

  • **第二步 创建Redis配置文件
## 创建目录
mkdir -p /home/redis/conf
## 创建文件
touch /home/redis/conf/redis.conf
  • **第三步 创建redis并启动
docker run -d \
  --name redis \
  -p 6379:6379 \
  --restart unless-stopped \
  -v /home/redis/data:/data \
  -v /home/redis/conf/redis.conf:/etc/redis/redis.conf \
  redis:latest \
  redis-server /etc/redis/redis.conf
  • **第四步 进入redis容器
### 直接通过Docker Redis 命令进入Redis控制台
docker exec -it redis redis-cli
### 进入 Redis 控制台
redis-cli
### 添加一个变量为 key 为 name , value 为 bella 的内容
> set name bella
### 查看 key 为 name 的 value 值
> get name

安装minio

 docker run \
--name minio_one \
-p 9000:9000  \
-p 9001:9001  \
-d \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-v /root/minio-data:/data \
-v /root/minio-config:/root/.minio \
minio/minio server  /data --console-address ":9001"
  • docker run:这是Docker命令行工具用来运行一个新容器的命令。

  • –name minio_one:这个参数为容器指定了一个名称,这里名称被设置为minio_one。使用名称可以更方便地管理容器。

  • p 9000:9000:这个参数将容器内的9000端口映射到宿主机的9000端口。MinIO服务默认使用9000端口提供API服务。

  • -p 9001:9001:这个参数将容器内的9001端口映射到宿主机的9001端口。这是MinIO的控制台(Console)端口,用于访问MinIO的图形用户界面。

  • -d:这个参数告诉Docker以“detached”模式运行容器,即在后台运行。

  • -e “MINIO_ROOT_USER=admin”:设置环境变量MINIO_ROOT_USER,这是访问MinIO服务的用户名称,这里设置为admin。

  • -e “MINIO_ROOT_PASSWORD=admin123456”:设置环境变量MINIO_ROOT_PASSWORD,这是访问MinIO服务的用户密码,这里设置为admin123456。

  • -v /home/data:/data:这个参数将宿主机的目录/home/data:/data挂载到容器的/data目录。MinIO会将所有数据存储在这个目录。

  • -v /root/config:/root/.minio:这个参数将宿主机的目录/root/minio-config挂载到容器的/root/.minio目录。这个目录用于存储MinIO的配置文件和数据。

  • minio/minio:这是要运行的Docker镜像的名称,这里使用的是官方发布的MinIO镜像。

  • server /data:这是传递给MinIO程序的命令行参数,告诉MinIO以服务器模式运行,并且使用/data目录作为其数据存储位置。

  • –console-address “:9001”:这个参数指定MinIO控制台服务的监听地址和端口。在这个例子中,它设置为监听所有接口上的9001端口。

注意:文件上传时,需要调整-下Linux 服务器的时间与Windows 时间一致!

第一步:安装ntp服务
yum -y install ntp
第二步:开启开机启动服务
systemctl enable ntpd
第三步:启动服务
systemctl start ntpd
第四步:更改时区
timedatectl set-timezone Asia/Shanghai
第五步:启用ntp同步
timedatectl set-ntp yes
第六步:同步时间
ntpq -p

如果启动不了,那么就重构主义,推了重建

# 强制删除旧容器(保留数据卷)
docker rm -f minio_one

# 重新运行 MinIO 容器(使用正确的镜像名称 minio/minio)
docker run \
--name minio_one \
-p 9000:9000 \
-p 9001:9001 \
-d \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-v /root/minio-data:/data \
-v /root/minio-config:/root/.minio \
minio/minio server /data --console-address ":9001"

nacos

  • 开启nacos

bash /root/nacos/bin/startup.sh -m standalone

  • 关闭nacos

bash /root/nacos/bin/shutdown.sh

导入数据库内容

  • 只需要在 DataGrip 连接上服务器或者本地的数据库

  • 然后再把所需要的数据库和表创建好,数据导入进去

逻辑删除

  • 物理删除是与之前一样,直接把数据删除,但这样不方便我们恢复数据,所以 mybatis-plus 有一种删除叫逻辑删除

  • 我们可以在数据表加一列属性表示为逻辑删除,值为0表示没有删除,值为1表示删除,方便我们恢复数据

官方文档:https://baomidou.com/guides/logic-delete/

第一种方法:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: isDelete #默认deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
      id-type: auto

第二种方法: 添加@TableLogic注解

@Data
public class User{
。。。
    /**
     * 1删除0正常
     */
    @TableLogic(value = "1",delval = "0")
    private Integer isDelete;
。。。
}

添加完后再使用mp已经封装好的语句都会加上 where deleted = 0

分页查询

  • 实现分页查询前需要配置分页插件

  • 分页插件:

@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
        return interceptor;
    }
}

导入配置文件

  • 两种导入模式

第一种:像之前一样一条一条创建

第二种:把文件打成压缩包,但是压缩包名称要与组的名字相同(默认是 DEFAULT_GROUP) ![[Pasted image 20250418214153.png]]

客户端登录

微信小程序登录流程

  • 官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

![[Pasted image 20250419174655.png]]

微信小程序登录接口

  • 这是接下来需要实现的步骤

项目工程结构: ![[Pasted image 20250419180421.png]]

准备工作

  • **导入微信工具包相关依赖

在 service-customer 中引入依赖

<dependency>  
    <groupId>com.github.binarywang</groupId>  
    <artifactId>weixin-java-miniapp</artifactId>  
</dependency>
  • **修改 nacos 配置中心文件

    • 因为查看官方文档可以看到我们后端请求腾讯接口需要三个值:小程序id、密钥和临时票据code,所以我们需要去 nacos 修改配置文件,把密钥和小程序id先作配置

    • 注意查看项目配置文件 bootstrap.properties 里 nacos 的 IP 与端口是否正确

  • 创建配置类来读取配置文件信息

在 service-customer 创建包config,创建类来读取配置文件中的内容

@Component
@Data
//从配置文件中读取 wx.miniapp 前缀下的字段
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxConfigProperties {
    private String appId;
    private String secret;
}

创建微信工具包对象

@Component
public class WxConfigOperator {

    @Autowired
    private WxConfigProperties wxConfigProperties;

    @Bean
    public WxMaService wxMaService() {
        WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
        config.setAppid(wxConfigProperties.getAppId());
        config.setSecret(wxConfigProperties.getSecret());

        WxMaService service = new WxMaServiceImpl();
        service.setWxMaConfig(config);
        return service;
    }
}

功能实现-基础功能

  • 在 service-customer 的 CustomerInfoController
@Slf4j
@RestController
@RequestMapping("/customer/info")
@SuppressWarnings({"unchecked", "rawtypes"})
public class CustomerInfoController {

	@Autowired
	private CustomerInfoService customerInfoService;

	// 微信小程序登录接口
	@Operation(summary = "小程序授权登录")
	@GetMapping("login/{code}")
	public Result<Long> longin(@PathVariable String code) {
	    return Result.ok(customerInfoService.login(code));
	}
}
  • service实现接口
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, CustomerInfo> implements CustomerInfoService {

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private CustomerInfoMapper customerInfoMapper;

    @Autowired
    private CustomerLoginLogMapper customerLoginLogMapper;

    //微信小程序登录
    @Override
    public Long login(String code) {
        String openid = null;
        // 获取code值,通过微信工具包对象,获取唯一标识openId
        try {
            WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
             openid = sessionInfo.getOpenid();
        } catch (WxErrorException e) {
            throw new RuntimeException(e);
        }

        // 用openId查询数据库是否存在
        // 如果openId不存在返回null,如果存在返回记录
        LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper();
        wrapper.eq(CustomerInfo::getWxOpenId, openid);
        CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);

        // 如果不存在,也就是第一次登陆,添加信息到用户表
        if (customerInfo == null) {
            customerInfo = new CustomerInfo();
            //用当前时间戳作为昵称
            customerInfo.setNickname(String.valueOf(System.currentTimeMillis()));
            //设置默认头像
            customerInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
            //把openId存入用户表
            customerInfo.setWxOpenId(openid);
            customerInfoMapper.insert(customerInfo);
        }

        // 记录登录日志
        CustomerLoginLog loginLog = new CustomerLoginLog();
        loginLog.setCustomerId(customerInfo.getId());
        loginLog.setMsg("小程序登录");
        customerLoginLogMapper.insert(loginLog);

        // 最后返回用户id
        return customerInfo.getId();
    }
}

功能实现-远程调用

  • service-customer-client 定义接口
@FeignClient(value = "service-customer")
public interface CustomerInfoFeignClient {

    @GetMapping("/customer/info/login/{code}")
    public Result<Long> longin(@PathVariable String code);
    
}
  • 在 web-customer 进行远程调用 controller:
@Slf4j
@Tag(name = "客户API接口管理")
@RestController
@RequestMapping("/customer")
@SuppressWarnings({"unchecked", "rawtypes"})
public class CustomerController {

    @Autowired
    private CustomerService customerInfoService;

    @Operation(summary = "小程序授权登录")
    @GetMapping("/login/{code}")
    public Result<String> wxLogin(@PathVariable String code) {
        return Result.ok(customerInfoService.login(code));
    }
}

service:

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class CustomerServiceImpl implements CustomerService {

    // 注入远程调用
    @Autowired
    private CustomerInfoFeignClient client;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public String login(String code) {

        // 拿着code进行远程调用,返回用户id
        Result<Long> longin = client.longin(code);

        // 如果返回失败了。返回错误信息
        Integer code1 = longin.getCode();
        if(code1 != 200){
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }

        // 获取远程调用的用户id
        Long customerId = longin.getData();

        // 判断返回用户id是否为空,为空则返回错误提示
        if(customerId == null){
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }

        // 把用户id放到Redis中,设置过期时间
        String token = UUID.randomUUID().toString();

        // 返回token
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX+token,
                customerId.toString(),
                RedisConstant.USER_LOGIN_KEY_TIMEOUT,
                TimeUnit.SECONDS);
        return token;
    }
}

功能实现-测试

  • 运行乘客端微信小程序(微信开发者工具)

  • 启动后端服务

    • 启动网关

    • 启动 service-customer 服务

    • 启动 web-customer 服务

把用到的配置文件都检查一遍,重点看看IP地址和开放端口,以及MySQL的远程用户(就是因为这个问题,后面用映射成localhost解决了)

获取客户端登录信息

service-customer 接口

  • Controller
@Operation(summary = "获取客户端登录信息")
	@GetMapping("/getCustomerLoginInfo/{customerId}")
	public Result<CustomerLoginVo> getCustomerLoginInfo(@PathVariable Long customerId) {
		CustomerLoginVo customerLoginVo = customerInfoService.getCustomerInfoService(customerId);
		return Result.ok(customerLoginVo);
	}
  • service
@Override
    public CustomerLoginVo getCustomerInfoService(Long customerId) {

        // 根据用户id查询用户信息
//        LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper();
//        wrapper.eq(CustomerInfo::getId, customerId);
//        CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);
        CustomerInfo info = customerInfoMapper.selectById(customerId);
        if (info == null) {
            throw new RuntimeException("用户不存在");
        }

        // 封装到customerLoginVo对象
        CustomerLoginVo customerLoginVo = new CustomerLoginVo();

        // springboot的工具类,可以把第一个对象的属性复制到第二个对象的同名属性中
        BeanUtils.copyProperties(info, customerLoginVo);

        /*
        * 还有一个属性需要手动拷贝,判断是否存在手机号
        * StringUtils.hasText(info.getPhone())判断字符串是否有值
        */
        customerLoginVo.setIsBindPhone(StringUtils.hasText(info.getPhone()));

        // 返回对象
        return customerLoginVo;
    }

web-customer 接口

  • controller
@Operation(summary = "获取客户登录信息")
@GetMapping("/getCustomerLoginInfo")
public Result<CustomerLoginVo> getCustomerLoginInfo(@RequestHeader(value = "token") String token) {

	CustomerLoginVo customerLoginVo = customerInfoService.getCustomerLoginInfo(token);
	// 返回用户对象
	return Result.ok(customerLoginVo);
}
  • service
    • 这里的token要和前面设置的一样
    • redisTemplate.opsForValue().get() 就是把redis当成一个map容器,通过键值对的方式来存储或查询
@Override
public CustomerLoginVo getCustomerLoginInfo(String token) {
	/*
	 * 从请求头中获取token字符串
	 * 根据token查询redis
	 */
	String customerId = (String) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX+token);

	// 根据用户id进行远程调用,获取用户信息
	if(!StringUtils.hasText(customerId)){
		throw new GuiguException(ResultCodeEnum.DATA_ERROR);
	}

	Result<CustomerLoginVo> customerLoginInfo = client.getCustomerLoginInfo(Long.parseLong(customerId));
	Integer code = customerLoginInfo.getCode();
	if(code != 200){
		throw new GuiguException(ResultCodeEnum.DATA_ERROR);
	}
	CustomerLoginVo data = customerLoginInfo.getData();
	if(data == null){
		throw new GuiguException(ResultCodeEnum.DATA_ERROR);
	}
	return data;
}

登录校验

这里选用 AOP面向切面加自定义注解完成,有想过用拦截器来做,但是路径太多,懒得配置了(高情商,更希望学习底层知识)

创建自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginDetection {
    
}

创建切面类

@Component
@Aspect
public class LoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    // 环绕通知,运行前后都触发
    // 切入点表达式,用来指定那些规则的方法进行增强
    @Around("execution(* com.atguigu.daijia.*.controller.*.*(..)) && @annotation(loginDetection)")
    public Object login(ProceedingJoinPoint proceedingJoinPoint,
                        LoginDetection loginDetection) throws Throwable {

        // 获取request对象,并且从请求头获取token
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;

        String token = sra.getRequest().getHeader("token");


        // 判断token是否有效,如果为空或过期,返回登录提示
        if(!StringUtils.hasText(token)){
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        // token不为空,查询redis、对应用户id,把用户id放到ThreadLocal里面
        String customerId = (String) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
        
        if(StringUtils.hasText(customerId)){
            AuthContextHolder.setUserId(Long.parseLong(customerId));
        }
        
        // 执行目标方法
        return proceedingJoinPoint.proceed();
    }
}

把之前的getCustomerLoginInfo用上注解

在web-customer中的service里 getCustomerLoginInfo 做修改,用上我们刚刚写的注解

@Operation(summary = "获取客户登录信息")
    @LoginDetection
    @GetMapping("/getCustomerLoginInfo")
    public Result<CustomerLoginVo> getCustomerLoginInfo() {

        // 从ThreadLocal获取用户id
        Long userId = AuthContextHolder.getUserId();

        // 调用service
        CustomerLoginVo customerLoginVo = customerInfoService.getCustomerInfo(userId);

        // 返回用户对象
        return Result.ok(customerLoginVo);
    }

service

@Override
public CustomerLoginVo getCustomerInfo(Long customerId) {

	Result<CustomerLoginVo> customerLoginInfo = client.getCustomerLoginInfo(customerId);
	Integer code = customerLoginInfo.getCode();
	if(code != 200){
		throw new GuiguException(ResultCodeEnum.DATA_ERROR);
	}
	CustomerLoginVo data = customerLoginInfo.getData();
	if(data == null){
		throw new GuiguException(ResultCodeEnum.DATA_ERROR);
	}
	return data;
}

获取乘客手机号

可恶,写完的时候发现个人版的微信开发无法使用这个,白写,最后不做判断直接改成true了

controller

@Operation(summary = "更新客户微信手机号码")
@PostMapping("/updateWxPhoneNumber")
public Result<Boolean> updateWxPhoneNumber(@RequestBody UpdateWxPhoneForm updateWxPhoneForm) {
	return Result.ok(customerInfoService.updateWxPhoneNumber(updateWxPhoneForm));
}

service

// 更新客户微信手机号码
@Override
public Boolean updateWxPhoneNumber(UpdateWxPhoneForm updateWxPhoneForm) {

	// 根据code查询用户信息
	try {
		WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(updateWxPhoneForm.getCode());

		// 更新用户信息
		Long customerId = updateWxPhoneForm.getCustomerId();
		CustomerInfo customerInfo = customerInfoMapper.selectById(customerId);
		customerInfo.setPhone(phoneNoInfo.getPhoneNumber());
		customerInfoMapper.updateById(customerInfo);

		return true;
	} catch (WxErrorException e) {
		throw new RuntimeException(e);
	}
}

web-customer

  • controller 企业版
@Operation(summary = "更新用户微信手机号")
@LoginDetection
@PostMapping("/updateWxPhone")
public Result updateWxPhone(@RequestBody UpdateWxPhoneForm updateWxPhoneForm) {
	updateWxPhoneForm.setCustomerId(AuthContextHolder.getUserId());
	return Result.ok(customerInfoService.updateWxPhoneNumber(updateWxPhoneForm));
}

个人版:

@Operation(summary = "更新用户微信手机号")
@LoginDetection
@PostMapping("/updateWxPhone")
public Result updateWxPhone(@RequestBody UpdateWxPhoneForm updateWxPhoneForm) {
	updateWxPhoneForm.setCustomerId(AuthContextHolder.getUserId());
	return Result.ok(true);
}
  • service
@Override
public boolean updateWxPhoneNumber(UpdateWxPhoneForm updateWxPhoneForm) {
	Result<Boolean> booleanResult = client.updateWxPhoneNumber(updateWxPhoneForm);
	return true;
}

司机端登录

司机开启接单的条件:

1、登录

2、认证通过

3、建立了腾讯云人员库人员

4、当日验证了人脸识别

准备工作与流程

  • 准备共奏

因为司机端要完成认证模块,比如身份证,驾驶证之类的,所以我们需要用到腾讯云的对象存储COS 还有腾讯云的OCR来识别身份证和驾驶证

  • 流程如下
    • 司机端登录功能
    • 获取用户信息
    • 腾讯云对象存储COS
    • 上传信息接口
    • 腾讯云OCR,识别身份证与驾驶证
    • 人脸识别
  • 修改项目配置文件和Nacos里面配置文件内容

  • 创建类,读取配置文件内容,微信小程序id和秘钥,这个跟客户端的一样,直接复制过来

@Component
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxConfigProperties {
    private String appId;
    private String secret;
}
  • 创建类,初始化微信工具包相关对象
@Component
public class WxConfigOperator {

    @Autowired
    private WxConfigProperties wxConfigProperties;

    @Bean
    public WxMaService wxMaService() {
        //微信小程序id和秘钥
        WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl();
        wxMaConfig.setAppid(wxConfigProperties.getAppId());
        wxMaConfig.setSecret(wxConfigProperties.getSecret());
        WxMaService service = new WxMaServiceImpl();
        service.setWxMaConfig(wxMaConfig);
        return service;
    }
}

司机端登录功能

这个步骤跟之前客户端差不多

  • service:
    • 就是获取code,然后通过code加密钥和小程序id去获取用户的唯一表示openid
    • 判断这个用户是不是第一次登录
    • 是的话初始化用户
    • 返回id
  • web-service:
    • 获取code,调用service
    • 远程调用获取数据
    • 生成token
    • 放入redis
    • 返回token

service-drive

DriverInfoController

@Slf4j
@Tag(name = "司机API接口管理")
@RestController
@RequestMapping(value="/driver/info")
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverInfoController {

    @Autowired
    private DriverInfoService driverInfoService;

    @Operation(summary = "小程序授权登录")
    @GetMapping("/login/{code}")
    public Result<Long> login(@PathVariable("code") String code) {
        return Result.ok(driverInfoService.login(code));
    }
}

DriverInfoServiceImpl

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverInfoServiceImpl extends ServiceImpl<DriverInfoMapper, DriverInfo> implements DriverInfoService {

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private DriverInfoMapper driverInfoMapper;

    @Autowired
    private DriverSetMapper driverSetMapper;

    @Autowired
    private DriverAccountMapper driverAccountMapper;

    @Autowired
    private DriverLoginLogMapper driverLoginLogMapper;

    // 小程序授权登录
    @Override
    public Long login(String code) {
        try {
            // 通过code+密钥+小程序id获取用户唯一标识openid
            WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
            String openid = sessionInfo.getOpenid();

            // 根据openid查询用户信息
            LambdaQueryWrapper<DriverInfo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(DriverInfo::getWxOpenId, openid);
            DriverInfo driverInfo = driverInfoMapper.selectOne(wrapper);

            if(driverInfo == null){
                // 添加司机基本信息
                driverInfo = new DriverInfo();
                driverInfo.setNickname(String.valueOf(System.currentTimeMillis()));
                driverInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
                driverInfo.setWxOpenId(openid);
                driverInfoMapper.insert(driverInfo);

                // 初始化司机设置
                DriverSet driverSet = new DriverSet();
                driverSet.setDriverId(driverInfo.getId());
                driverSet.setOrderDistance(new BigDecimal(0));//0:无限制
                driverSet.setAcceptDistance(new BigDecimal(SystemConstant.ACCEPT_DISTANCE));//默认接单范围:5公里
                driverSet.setIsAutoAccept(0);//0:否 1:是
                driverSetMapper.insert(driverSet);

                // 初始化司机账户信息
                DriverAccount driverAccount = new DriverAccount();
                driverAccount.setDriverId(driverInfo.getId());
                driverAccountMapper.insert(driverAccount);
            }

            // 更新登录日志
            DriverLoginLog loginLog = new DriverLoginLog();
            loginLog.setDriverId(driverInfo.getId());
            loginLog.setMsg("小程序登录");
            driverLoginLogMapper.insert(loginLog);

            // 返回用户id
            return driverInfo.getId();
        } catch (WxErrorException e) {
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }
    }
}

service-drive-client

@FeignClient(value = "service-driver")
public interface DriverInfoFeignClient {

    // 小程序授权登录
    @GetMapping("/driver/info/login/{code}")
    public Result<Long> login(@PathVariable("code") String code);
}

web-drive

controller

@Slf4j
@Tag(name = "司机API接口管理")
@RestController
@RequestMapping(value="/driver")
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverController {

    @Autowired
    private DriverService driverService;

    @Operation(summary = "小程序授权登录")
    @GetMapping("/login/{code}")
    public Result<String> login(@PathVariable("code") String code) {
        return Result.ok(driverService.login(code));
    }
}

service

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverServiceImpl implements DriverService {

    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    // 小程序授权登录
    @Override
    public String login(String code) {
        Result<Long> result = driverInfoFeignClient.login(code);
        if(result.getCode() != 200){
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }
        Long data = result.getData();
        if(data == null){
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }
        String token = UUID.randomUUID().toString().replace("-","");
        redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX + token,
                data.toString(),
                RedisConstant.USER_LOGIN_KEY_TIMEOUT,
                TimeUnit.SECONDS);

        return token;
    }
}

获取登录司机信息

service-driver

controller

@Operation(summary = "获取司机登录信息")  
@GetMapping("/getDriverLoginInfo/{driverId}")  
public Result<DriverLoginVo> getDriverInfo(@PathVariable("driverId") long driverId) {  
    DriverLoginVo driverLoginVo = driverInfoService.getDriverInfo(driverId);  
    return Result.ok(driverLoginVo);  
}

service

// 获取司机登录信息
@Override
public DriverLoginVo getDriverInfo(long driverId) {
	// 通过id查询司机信息
	DriverInfo driverInfo = driverInfoMapper.selectById(driverId);
	
	// 赋予到vo对象中
	DriverLoginVo driverLoginVo = new DriverLoginVo();
	BeanUtils.copyProperties(driverInfo, driverLoginVo);

	// 查询是否需要建档人脸识别
	String faceModelId = driverInfo.getFaceModelId();
	boolean isArchiveFace = StringUtils.hasText(faceModelId);
	driverLoginVo.setIsArchiveFace(isArchiveFace);
	return driverLoginVo;
}

service-driver-client

@FeignClient(value = "service-driver")
public interface DriverInfoFeignClient {

    // 小程序授权登录
    @GetMapping("/driver/info/login/{code}")
    public Result<Long> login(@PathVariable("code") String code);

    // 获取登录司机信息
    @GetMapping("/driver/info/getDriverLoginInfo/{driverId}")
    public Result<DriverLoginVo> getDriverInfo(@PathVariable("driverId") long driverId);
}

web-driver

controller

@Operation(summary = "获取登录司机信息")
@LoginDetection
@GetMapping("/getDriverLoginInfo")
public Result<DriverLoginVo> getDriverLoginInfo() {
	return Result.ok(driverService.getDriverLoginVo());
}

service

@Override
public DriverLoginVo getDriverLoginVo() {
	Long driverId = AuthContextHolder.getUserId();
	Result<DriverLoginVo> driverInfo = driverInfoFeignClient.getDriverInfo(driverId);
	return driverInfo.getData();
}

测试一下前面的代码

  • 打开微信开发者工具
  • 改好相关配置文件
  • nacos
  • 启动后端服务
    • 网关服务service-gateway
    • 司机端基础服务service-driver
    • 对外访问服务web-driver

可恶,在这里前面web-driver里面controller的路径写错了,一直访问不上,还有nacos里面的配置忘记修改成映射后的端口

开通腾讯云对象存储COS

准备工作

  • 注册并实名腾讯云
  • 找到对象存储
  • 创建储存桶
  • 创建并保存密钥和id

思路

在 web-driver:

  • 获取文件以及存放路径
  • 远程调用实现文件上传
  • 返回 vo 对象

在 service-driver:

  • 导入依赖
  • 获取上传文件及路径
  • 修改配置文件
  • 基于腾讯云官方文档调用实现上传
  • 返回 vo 对象

注意点:因为保密隐私,我们这里的存储桶肯定是私密的,但是我们又要让用户能看到上传后的结果,因此我们需要创建一个临时地址,让客户看到存储内容

web-service

  • controller
@Tag(name = "上传管理接口")
@RestController
@RequestMapping("file")
public class FileController {

    @Autowired
    private CosService cosService;

    // 文件上传接口
    @Operation(summary = "上传")
    @LoginDetection
    @PostMapping("/upload")
    public Result<CosUploadVo> upload(@RequestPart("file") MultipartFile file,
                                      @RequestParam(value = "path", defaultValue = "auth") String path) {
        CosUploadVo cosUploadVo = cosService.uploadFile(file, path);
        return Result.ok(cosUploadVo);
    }
}
  • service
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class CosServiceImpl implements CosService {

    @Autowired
    private CosFeignClient cosFeignClient;

    // 文件上传接口
    @Override
    public CosUploadVo uploadFile(MultipartFile file, String path) {
        // 远程调用获取vo
        Result<CosUploadVo> result =  cosFeignClient.upload(file, path);
        CosUploadVo cosUploadVo = result.getData();
        return cosUploadVo;
    }
}

service-driver-client

@FeignClient(value = "service-driver")
public interface CosFeignClient {

    /*
    * 文件上传
    * MediaType 表示上传类型
    */
    @PostMapping(value = "/cos/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Result<CosUploadVo> upload(@RequestPart("file") MultipartFile file,
                                      @RequestParam(value = "path") String path);
}

service-driver

  • 导入依赖
<dependency>
     <groupId>com.qcloud</groupId>
     <artifactId>cos_api</artifactId>
     <version>5.6.227</version>
</dependency>
  • 修改配置文件

  • 创建类读取配置文件内容

@Data
@Component
@ConfigurationProperties(prefix = "tencent.cloud")
public class TencentCloudProperties {
    private String secretId;
    private String secretKey;
    private String region;
    private String bucketPrivate;
}
  • controller
@Slf4j
@Tag(name = "腾讯云cos上传接口管理")
@RestController
@RequestMapping(value="/cos")
@SuppressWarnings({"unchecked", "rawtypes"})
public class CosController {

    @Autowired
    private CosService cosService;

    @Operation(summary = "上传")
    @PostMapping("/upload")
    public Result<CosUploadVo> upload(@RequestPart("file") MultipartFile file,
                                      @RequestParam("path") String path) {
        CosUploadVo cosUploadVo = cosService.upload(file, path);
        return Result.ok(cosUploadVo);
    }
}
  • service
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class CosServiceImpl implements CosService {

    @Autowired
    private TencentCloudProperties tencentCloudProperties;

    // 文件上传
   @Override
    public CosUploadVo upload(MultipartFile file, String path) {

        // 获取cos客户端
        COSClient cosClient = getCosClient();

        // 文件上传
        // 设置文件元数据信息
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(file.getSize());
        metadata.setContentEncoding("UTF-8");
        metadata.setContentType(file.getContentType());

        // 向储存桶保存文件
        String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); //文件后缀名
        String uploadPath = "/driver/" + path + "/" + UUID.randomUUID().toString().replaceAll("-", "") + fileType;
        // 01.jpg
        // /driver/auth/0o98754.jpg
        PutObjectRequest putObjectRequest = null;
        try {
            //1 bucket名称
            //2
            putObjectRequest = new PutObjectRequest(tencentCloudProperties.getBucketPrivate(),
                    uploadPath,
                    file.getInputStream(),
                    metadata);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        putObjectRequest.setStorageClass(StorageClass.Standard);
        PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); //上传文件
        cosClient.shutdown();

        //返回vo对象
        CosUploadVo cosUploadVo = new CosUploadVo();
        cosUploadVo.setUrl(uploadPath);
        // 图片临时访问回显url
        cosUploadVo.setShowUrl(this.getImagerUrl(uploadPath));
        return cosUploadVo;
    }

提取CosClient

private COSClient getCosClient() {
	// 1 初始化用户身份信息(secretId, secretKey)。
	String secretId = tencentCloudProperties.getSecretId();
	String secretKey = tencentCloudProperties.getSecretKey();
	COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
	// 2 设置 bucket 的地域
	Region region = new Region(tencentCloudProperties.getRegion());
	ClientConfig clientConfig = new ClientConfig(region);
	// 这里建议设置使用 https 协议
	clientConfig.setHttpProtocol(HttpProtocol.https);
	// 3 生成 cos 客户端。
	COSClient cosClient = new COSClient(cred, clientConfig);
	return cosClient;
}

获取临时签名url

@Override
public String getImagerUrl(String path) {
	if(!StringUtils.hasText(path)){
		return "";
	}
	COSClient cosClient = getCosClient();
	GeneratePresignedUrlRequest request =
			new GeneratePresignedUrlRequest(tencentCloudProperties.getBucketPrivate(),
					path,
					HttpMethodName.GET);
	// 设置临时URL有效期
	DateTime dateTime = new DateTime().plusMinutes(15);
	request.setExpiration(dateTime.toDate());
	// 调用方法获取
	URL url = cosClient.generatePresignedUrl(request);
	cosClient.shutdown();
	return url.toString();
}

这一大段看着很复杂,其实大部分都是腾讯云官方文档里面的内容,我们只需要修改一下参数即可

腾讯云身份证与驾驶证识别接口

思路

司机注册成功后,应该需要去实名认证,这里可以用到腾讯云的身份识别和云存储功能,身份证和驾驶证上传的步骤都差不多,这里就写在一起了

那我们现在计划很明确了

web-driver:

  • 获取上传文件
  • 远程调用获取认证信息
  • 返回认证信息

service-driver:

  • 上传认证图片
  • 调用腾讯云相关接口完成认证
  • 返回认证信息

service-driver

  • controller:
@Slf4j
@Tag(name = "腾讯云识别接口管理")
@RestController
@RequestMapping(value="/ocr")
@SuppressWarnings({"unchecked", "rawtypes"})
public class OcrController {

    @Autowired
    private OcrService ocrService;

    @Operation(summary = "身份证识别")
    @PostMapping("/idCardOcr")
    public Result<IdCardOcrVo> idCardOcr(@RequestPart("file") MultipartFile file) {
        IdCardOcrVo idCardOcrVo = ocrService.idCardOcr(file);
        return Result.ok(idCardOcrVo);
    }

	@Operation(summary = "驾驶证识别")  
	@PostMapping("/driverLicenseOcr")  
	public Result<DriverLicenseOcrVo> driverLicenseOcr(@RequestPart("file") MultipartFile file) {  
	    DriverLicenseOcrVo driverLicenseOcrVo = ocrService.driverLicenseOcr(file);  
	    return Result.ok(driverLicenseOcrVo);  
	}
}
  • service: 导入依赖:
<dependency>  
    <groupId>com.tencentcloudapi</groupId>  
    <artifactId>tencentcloud-sdk-java</artifactId>  
    <version>${tencentcloud.version}</version>  
</dependency>

看着代码很多,其实全都是从腾讯云官网拉过来修改配置的

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OcrServiceImpl implements OcrService {

    @Autowired
    private TencentCloudProperties tencentCloudProperties;

    @Autowired
    private CosService cosService;

    // 身份证识别
    @Override
    public IdCardOcrVo idCardOcr(MultipartFile file) {
        try{
            // file转换成base64
            byte[] base64 = Base64.encodeBase64(file.getBytes());
            String fileBase64 = new String(base64);
            // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
            Credential cred = new Credential(tencentCloudProperties.getSecretId(),
                    tencentCloudProperties.getSecretKey());
            // 实例化一个http选项,可选的,没有特殊需求可以跳过
            HttpProfile httpProfile = new HttpProfile();
            httpProfile.setEndpoint("ocr.tencentcloudapi.com");
            // 实例化一个client选项,可选的,没有特殊需求可以跳过
            ClientProfile clientProfile = new ClientProfile();
            clientProfile.setHttpProfile(httpProfile);
            // 实例化要请求产品的client对象,clientProfile是可选的
            OcrClient client = new OcrClient(cred,tencentCloudProperties.getRegion(), clientProfile);
            // 实例化一个请求对象,每个接口都会对应一个request对象
            IDCardOCRRequest req = new IDCardOCRRequest();
            //设置文件
            req.setImageBase64(fileBase64);

            // 返回的resp是一个IDCardOCRResponse的实例,与请求对象对应
            IDCardOCRResponse resp = client.IDCardOCR(req);

            //转换为IdCardOcrVo对象
            IdCardOcrVo idCardOcrVo = new IdCardOcrVo();
            if (StringUtils.hasText(resp.getName())) {
                //身份证正面
                idCardOcrVo.setName(resp.getName());
                idCardOcrVo.setGender("男".equals(resp.getSex()) ? "1" : "2");
                idCardOcrVo.setBirthday(DateTimeFormat.forPattern("yyyy/MM/dd").parseDateTime(resp.getBirth()).toDate());
                idCardOcrVo.setIdcardNo(resp.getIdNum());
                idCardOcrVo.setIdcardAddress(resp.getAddress());

                //上传身份证正面图片到腾讯云cos
                CosUploadVo cosUploadVo = cosService.upload(file, "idCard");
                idCardOcrVo.setIdcardFrontUrl(cosUploadVo.getUrl());
                idCardOcrVo.setIdcardFrontShowUrl(cosUploadVo.getShowUrl());
            } else {
                //身份证反面
                //证件有效期:"2010.07.21-2020.07.21"
                String idcardExpireString = resp.getValidDate().split("-")[1];
                idCardOcrVo.setIdcardExpire(DateTimeFormat.forPattern("yyyy.MM.dd").parseDateTime(idcardExpireString).toDate());
                //上传身份证反面图片到腾讯云cos
                CosUploadVo cosUploadVo = cosService.upload(file, "idCard");
                idCardOcrVo.setIdcardBackUrl(cosUploadVo.getUrl());
                idCardOcrVo.setIdcardBackShowUrl(cosUploadVo.getShowUrl());
            }
            return idCardOcrVo;
        } catch (Exception e) {
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }
    }

	//驾驶证识别  
	@Override  
	public DriverLicenseOcrVo driverLicenseOcr(MultipartFile file) {  
	    try{  
	        //图片转换base64格式字符串  
	        byte[] base64 = Base64.encodeBase64(file.getBytes());  
	        String fileBase64 = new String(base64);  
	  
	        // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密  
	        Credential cred = new Credential(tencentCloudProperties.getSecretId(),  
	                tencentCloudProperties.getSecretKey());  
	        // 实例化一个http选项,可选的,没有特殊需求可以跳过  
	        HttpProfile httpProfile = new HttpProfile();  
	        httpProfile.setEndpoint("ocr.tencentcloudapi.com");  
	        // 实例化一个client选项,可选的,没有特殊需求可以跳过  
	        ClientProfile clientProfile = new ClientProfile();  
	        clientProfile.setHttpProfile(httpProfile);  
	        // 实例化要请求产品的client对象,clientProfile是可选的  
	        OcrClient client = new OcrClient(cred, tencentCloudProperties.getRegion(),  
	                clientProfile);  
	        // 实例化一个请求对象,每个接口都会对应一个request对象  
	        DriverLicenseOCRRequest req = new DriverLicenseOCRRequest();  
	        req.setImageBase64(fileBase64);  
	  
	        // 返回的resp是一个DriverLicenseOCRResponse的实例,与请求对象对应  
	        DriverLicenseOCRResponse resp = client.DriverLicenseOCR(req);  
	  
	        //封装到vo对象里面  
	        DriverLicenseOcrVo driverLicenseOcrVo = new DriverLicenseOcrVo();  
	        if (StringUtils.hasText(resp.getName())) {  
	            //驾驶证正面  
	            //驾驶证名称要与身份证名称一致  
	            driverLicenseOcrVo.setName(resp.getName());  
	            driverLicenseOcrVo.setDriverLicenseClazz(resp.getClass_());  
	            driverLicenseOcrVo.setDriverLicenseNo(resp.getCardCode());  
	            driverLicenseOcrVo.setDriverLicenseIssueDate(DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime(resp.getDateOfFirstIssue()).toDate());  
	            driverLicenseOcrVo.setDriverLicenseExpire(DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime(resp.getEndDate()).toDate());  
	  
	            //上传驾驶证反面图片到腾讯云cos  
	            CosUploadVo cosUploadVo = cosService.upload(file, "driverLicense");  
	            driverLicenseOcrVo.setDriverLicenseFrontUrl(cosUploadVo.getUrl());  
	            driverLicenseOcrVo.setDriverLicenseFrontShowUrl(cosUploadVo.getShowUrl());  
	        } else {  
	            //驾驶证反面  
	            //上传驾驶证反面图片到腾讯云cos  
	            CosUploadVo cosUploadVo =  cosService.upload(file, "driverLicense");  
	            driverLicenseOcrVo.setDriverLicenseBackUrl(cosUploadVo.getUrl());  
	            driverLicenseOcrVo.setDriverLicenseBackShowUrl(cosUploadVo.getShowUrl());  
	        }  
	  
	        return driverLicenseOcrVo;  
	    } catch (Exception e) {  
	        e.printStackTrace();  
	        throw new GuiguException(ResultCodeEnum.DATA_ERROR);  
	    }  
	}
}

service-client

@FeignClient(value = "service-driver")  
public interface OcrFeignClient {  
  
    @PostMapping(value = "/ocr/idCardOcr", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)  
    Result<IdCardOcrVo> idCardOcr(@RequestPart("file") MultipartFile file);  
  
    @PostMapping(value = "/ocr/driverLicenseOcr", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)  
    Result<DriverLicenseOcrVo> driverLicenseOcr(@RequestPart("file") MultipartFile file);  
}

web-service

  • controller
@Slf4j  
@Tag(name = "腾讯云识别接口管理")  
@RestController  
@RequestMapping(value="/ocr")  
@SuppressWarnings({"unchecked", "rawtypes"})  
public class OcrController {  
  
    @Autowired  
    private OcrService ocrService;  
  
    @Operation(summary = "身份证识别")  
    @LoginDetection  
    @PostMapping("/idCardOcr")  
    public Result<IdCardOcrVo> uploadDriverLicenseOcr(@RequestPart("file")MultipartFile file){  
        return Result.ok(ocrService.idCardOcr(file));  
    }  
  
    @Operation(summary = "驾驶证识别")  
    @LoginDetection  
    @PostMapping("/driverLicenseOcr")  
    public Result<DriverLicenseOcrVo> driverLicenseOcr(@RequestPart("file")MultipartFile file){  
        return Result.ok(ocrService.driverLicenseOcr(file));  
    }  
}
  • serivce
@Slf4j  
@Service  
@SuppressWarnings({"unchecked", "rawtypes"})  
public class OcrServiceImpl implements OcrService {  
  
    @Autowired  
    private OcrFeignClient ocrFeignClient;  
  
    // 身份证  
    @Override  
    public IdCardOcrVo idCardOcr(MultipartFile file) {  
        Result<IdCardOcrVo> ocrVoResult = ocrFeignClient.idCardOcr(file);  
        return ocrVoResult.getData();  
    }  
  
    // 驾驶证  
    @Override  
    public DriverLicenseOcrVo driverLicenseOcr(MultipartFile file) {  
        Result<DriverLicenseOcrVo> result = ocrFeignClient.driverLicenseOcr(file);  
        return result.getData();  
    }  
}

获取司机认证信息

思路

司机登录后会进入认证界面,判单是否认证,进入认证界面是会显示回显的图片

其实这个很简单,因为腾讯云的那个文字识别会返回参数出来,我们只需要把回显地址返回回去就行

driver-service

  • 通过id来传入回显地址

driver-web

  • 调用该

service-driver

  • controller
@Operation(summary = "获取司机认证信息")
@GetMapping("/getDriverAuthInfo/{driverId}")
public Result<DriverAuthInfoVo> getDriverAuthInfo(@PathVariable Long driverId) {
    DriverAuthInfoVo driverAuthInfoVo = driverInfoService.getDriverAuthInfo(driverId);
    return Result.ok(driverAuthInfoVo);
}
  • service
```java
//获取司机认证信息
@Override
public DriverAuthInfoVo getDriverAuthInfo(Long driverId) {
    DriverInfo driverInfo = driverInfoMapper.selectById(driverId);
    DriverAuthInfoVo driverAuthInfoVo = new DriverAuthInfoVo();
    BeanUtils.copyProperties(driverInfo,driverAuthInfoVo);
    driverAuthInfoVo.setIdcardBackShowUrl(cosService.getImageUrl(driverAuthInfoVo.getIdcardBackUrl()));
        driverAuthInfoVo.setIdcardFrontShowUrl(cosService.getImageUrl(driverAuthInfoVo.getIdcardFrontUrl()));

    driverAuthInfoVo.setIdcardHandShowUrl(cosService.getImageUrl(driverAuthInfoVo.getIdcardHandUrl()));

    driverAuthInfoVo.setDriverLicenseFrontShowUrl(cosService.getImageUrl(driverAuthInfoVo.getDriverLicenseFrontUrl()));

    driverAuthInfoVo.setDriverLicenseBackShowUrl(cosService.getImageUrl(driverAuthInfoVo.getDriverLicenseBackUrl()));

    driverAuthInfoVo.setDriverLicenseHandShowUrl(cosService.getImageUrl(driverAuthInfoVo.getDriverLicenseHandUrl()));
    return driverAuthInfoVo;

}

service-client

@GetMapping("/driver/info/getDriverAuthInfo/{driverId}")
Result<DriverAuthInfoVo> getDriverAuthInfo(@PathVariable("driverId") Long driverId);

web-serive

  • controller
@Operation(summary = "获取司机认证信息")
@GuiguLogin
@GetMapping("/getDriverAuthInfo")
public Result<DriverAuthInfoVo> getDriverAuthInfo() {
    //获取登录用户id,当前是司机id
    Long driverId = AuthContextHolder.getUserId();
    return Result.ok(driverService.getDriverAuthInfo(driverId));
}
  • serivce
//司机认证信息
@Override
public DriverAuthInfoVo getDriverAuthInfo(Long driverId) {
    Result<DriverAuthInfoVo> authInfoVoResult = driverInfoFeignClient.getDriverAuthInfo(driverId);
    DriverAuthInfoVo driverAuthInfoVo = authInfoVoResult.getData();
    return driverAuthInfoVo;
}

修改司机认证信息

思路

前端点击提交后,后端需要更新客户认证信息 ​    0: 未认证 【刚注册完为未认证状态】 ​   1:审核中 【提交了认证信息后变为审核中】 ​   2:认证通过 【后台审核通过】 ​   -1:认证未通过【后台审核不通过】

这个和上面差不多,其他就不写了,就写一个service

// 更新司机认证信息
@Override
public boolean updateDriverAuthInfo(UpdateDriverAuthInfoForm update) {
	Long driverId = update.getDriverId();
	DriverInfo driverInfo = new DriverInfo();
	driverInfo.setId(driverId);
	BeanUtils.copyProperties(update, driverInfo);
	return this.updateById(driverInfo);
}

开通人脸识别

腾讯云文档: 人脸识别_人脸搜索_人脸检测_人脸比对-腾讯云

1、开通腾讯云人脸识别 2、创建人员库

创建司机人脸模型

创建人脸识别模型之后,腾讯云返回模型id,获取模型id,更新到数据库表,但是因为这是调用腾讯云接口,所以如果之前已经有人拿这张图片创建过则不会创建新的模型id,而是返回之前创建的id

  • 修改配置文件
  • 修改配置类
@Data
@Component
@ConfigurationProperties(prefix = "tencent.cloud")
public class TencentCloudProperties {

    private String secretId;
    private String secretKey;
    private String region;
    private String bucketPrivate;

    private String persionGroupId;
}

service-driver

后面都是只写重要的代码实现,一般的调用就不写了

// 创建司机人脸模型
@Override
public Boolean creatDriverFaceModel(DriverFaceModelForm driverFaceModelForm) {
	//根据司机id获取司机信息
	DriverInfo driverInfo =
			driverInfoMapper.selectById(driverFaceModelForm.getDriverId());
	try{
		Credential cred = new Credential(tencentCloudProperties.getSecretId(),
				tencentCloudProperties.getSecretKey());
		// 实例化一个http选项,可选的,没有特殊需求可以跳过
		HttpProfile httpProfile = new HttpProfile();
		httpProfile.setEndpoint("iai.tencentcloudapi.com");
		// 实例化一个client选项,可选的,没有特殊需求可以跳过
		ClientProfile clientProfile = new ClientProfile();
		clientProfile.setHttpProfile(httpProfile);
		// 实例化要请求产品的client对象,clientProfile是可选的
		IaiClient client = new IaiClient(cred, tencentCloudProperties.getRegion(),
				clientProfile);
		// 实例化一个请求对象,每个接口都会对应一个request对象
		CreatePersonRequest req = new CreatePersonRequest();
		//设置相关值
		req.setGroupId(tencentCloudProperties.getPersionGroupId());
		//基本信息
		req.setPersonId(String.valueOf(driverInfo.getId()));
		req.setGender(Long.parseLong(driverInfo.getGender()));
		req.setQualityControl(4L);
		req.setUniquePersonControl(4L);
		req.setPersonName(driverInfo.getName());
		req.setImage(driverFaceModelForm.getImageBase64());

		// 返回的resp是一个CreatePersonResponse的实例,与请求对象对应
		CreatePersonResponse resp = client.CreatePerson(req);
		// 输出json格式的字符串回包
		System.out.println(AbstractModel.toJsonString(resp));
		String faceId = resp.getFaceId();
		if(StringUtils.hasText(faceId)) {
			driverInfo.setFaceModelId(faceId);
			driverInfoMapper.updateById(driverInfo);
		}
	} catch (TencentCloudSDKException e) {
		e.printStackTrace();
		return false;
	}
	return true;
}

预估订单数据

查找客户端当前订单

当一个客户发起订单前先要查询是否有发起并且没有完成的订单,如果有订单未完成,则会弹出进行中的订单

预估驾驶路线

  • 访问腾讯官网 https://lbs.qq.com/

  • 进行注册,手机号或者微信或者其他方式

  • 使用注册账号进行登录,找到控制台

  • 在应用管理 – 我的应用 ,创建应用

  • 在创建应用中,添加key

  • 修改nacos配置文件

  • 编写接口

下面实现接口,具体内容就是请求腾讯云提供的接口,按照接口传参数,然后返回需要的结果

腾讯官方文档: WebService API | 腾讯位置服务

  • 编写配置类
@Configuration
public class MyConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  • service
@Slf4j  
@Service  
@SuppressWarnings({"unchecked", "rawtypes"})  
public class MapServiceImpl implements MapService {  
  
    @Autowired  
    private RestTemplate restTemplate;  
  
    @Value("${tencent.map.key}")  
    private String key;  
  
    //计算驾驶线路  
    @Override  
    public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {  
        //请求腾讯提供接口,按照接口要求传递相关参数,返回需要结果  
        //使用HttpClient,目前Spring封装调用工具使用RestTemplate  
        //定义调用腾讯地址  
        String url = "https://apis.map.qq.com/ws/direction/v1/driving/?from={from}&to={to}&key={key}";  
  
        //封装传递参数  
        Map<String,String> map = new HashMap();  
        //开始位置  
        // 经纬度:比如 北纬40 东京116  
        map.put("from",calculateDrivingLineForm.getStartPointLatitude()+","+calculateDrivingLineForm.getStartPointLongitude());  
        //结束位置  
        map.put("to",calculateDrivingLineForm.getEndPointLatitude()+","+calculateDrivingLineForm.getEndPointLongitude());  
        //key  
        map.put("key",key);  
  
        //使用RestTemplate调用 GET        JSONObject result = restTemplate.getForObject(url, JSONObject.class, map);  
        //处理返回结果  
        //判断调用是否成功  
        int status = Objects.requireNonNull(result).getIntValue("status");  
        if(status != 0) {//失败  
            throw new GuiguException(ResultCodeEnum.MAP_FAIL);  
        }  
  
        //获取返回路线信息  
        JSONObject route =  
                result.getJSONObject("result").getJSONArray("routes").getJSONObject(0);  
  
        //创建vo对象  
        DrivingLineVo drivingLineVo = new DrivingLineVo();  
        //预估时间  
        drivingLineVo.setDuration(route.getBigDecimal("duration"));  
        //距离  6.583 == 6.58 / 6.59        drivingLineVo.setDistance(route.getBigDecimal("distance")  
                .divide(new BigDecimal(1000))  
                .setScale(2, RoundingMode.HALF_UP));  
        //路线  
        drivingLineVo.setPolyline(route.getJSONArray("polyline"));  
  
        return drivingLineVo;  
    }  
}

预估订单金额

整合规则引擎Drools

  • 引入依赖
<dependencies>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-decisiontables</artifactId>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-mvel</artifactId>
    </dependency>
</dependencies>
  • 创建Drools配置类
@Configuration
public class DroolsConfig {
    // 制定规则文件的路径
    private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";

    @Bean
    public KieContainer kieContainer() {
        KieServices kieServices = KieServices.Factory.get();

        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();

        KieModule kieModule = kb.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        return kieContainer;
    }
}
  • 创建规则文件

封装费用规则接口

实体类

  • 两个实体类,输入对象和输出对象

  • 输入对象

@Data
public class FeeRuleRequest {

    @Schema(description = "代驾里程")// swagger里面的描述
    private BigDecimal distance;

    @Schema(description = "代驾时间")
    private String startTime;

    @Schema(description = "等候分钟")
    private Integer waitMinute;
}
  • 输出对象
@Data
public class FeeRuleResponse {


    @Schema(description = "总金额")
    private BigDecimal totalAmount;

    @Schema(description = "里程费")
    private BigDecimal distanceFee;

    @Schema(description = "等时费用")
    private BigDecimal waitFee;

    @Schema(description = "远程费")
    private BigDecimal longDistanceFee;

    @Schema(description = "基础里程(公里)")
    private BigDecimal baseDistance;

    @Schema(description = "基础里程费(元)")
    private BigDecimal baseDistanceFee;

    @Schema(description = "超出基础里程的里程(公里)")
    private BigDecimal exceedDistance;

    @Schema(description = "超出基础里程的价格(元/公里)")
    private BigDecimal exceedDistancePrice;

    @Schema(description = "基础等时分钟(分钟)")
    private Integer baseWaitMinute;

    @Schema(description = "超出基础等时的分钟(分钟)")
    private Integer exceedWaitMinute;

    @Schema(description = "超出基础分钟的价格(元/分钟)")
    private BigDecimal exceedWaitMinutePrice;

    @Schema(description = "基础远途里程(公里)")
    private BigDecimal baseLongDistance;

    @Schema(description = "超出基础远程里程的里程(公里)")
    private BigDecimal exceedLongDistance;

    @Schema(description = "超出基础远程里程的价格(元/公里)")
    private BigDecimal exceedLongDistancePrice;
}

费用规则文件

1.起步价
00:00:00-06:59:59 19元(含3公里)
07:00:00-23:59:59 19元(含5公里) 2.里程费
超出起步里程后开始计算
00:00:00-06:59:59 4元/1公里
07:00:00-23:59:59 3元/1公里 3.等候费
等候10分钟后 1元/1分钟 4.远途费
订单行程超出12公里后每公里1元 5.计算总金额
订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费

//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
package  com.atguigu.daijia

import com.atguigu.daijia.model.form.rules.FeeRuleRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;

global com.atguigu.daijia.model.vo.rules.FeeRuleResponse feeRuleResponse;

/**
1.起步价
    00:00:00-06:59:59  19元(含3公里)
    07:00:00-23:59:59  19元(含5公里)
*/
rule "起步价 00:00:00-06:59:59  19元(含3公里)"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "00:00:00" && startTime <= "06:59:59")
    then
        feeRuleResponse.setBaseDistance(new BigDecimal("3.0"));
        feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));
        //3公里内里程费为0
        feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
        System.out.println("00:00:00-06:59:59 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end
rule "起步价 07:00:00-23:59:59  19元(含5公里)"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "07:00:00" && startTime <= "23:59:59")
    then
        feeRuleResponse.setBaseDistance(new BigDecimal("5.0"));
        feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));

        //5公里内里程费为0
        feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("3.0"));
        System.out.println("07:00:00-23:59:59 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end

/**
2.里程费
    超出起步里程后开始计算
    00:00:00-06:59:59   4元/1公里
    07:00:00-23:59:59   3元/1公里
*/
rule "里程费 00:00:00-06:59:59 4元/1公里"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "00:00:00"
            && startTime <= "06:59:59"
            && distance.doubleValue() > 3.0)
    then
        BigDecimal exceedDistance = $rule.getDistance().subtract(new BigDecimal("3.0"));
        feeRuleResponse.setExceedDistance(exceedDistance);
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
        System.out.println("里程费,超出里程:" + feeRuleResponse.getExceedDistance() + "公里,单价:" + feeRuleResponse.getExceedDistancePrice());
end
rule "里程费 07:00:00-23:59:59 3元/1公里"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "07:00:00"
            && startTime <= "23:59:59"
            && distance.doubleValue() > 5.0)
    then
        BigDecimal exceedDistance = $rule.getDistance().subtract(new BigDecimal("5.0"));
        feeRuleResponse.setExceedDistance(exceedDistance);
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("3.0"));
        System.out.println("里程费,超出里程:" + feeRuleResponse.getExceedDistance() + "公里,单价:" + feeRuleResponse.getExceedDistancePrice());
end

/**
3.等候费
    等候10分钟后  1元/1分钟
*/
rule "等候费 等候10分钟后 1元/1分钟"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(waitMinute > 10)
    then
        Integer exceedWaitMinute = $rule.getWaitMinute() - 10;
        feeRuleResponse.setBaseWaitMinute(10);
        feeRuleResponse.setExceedWaitMinute(exceedWaitMinute);
        feeRuleResponse.setExceedWaitMinutePrice(new BigDecimal("1.0"));
        System.out.println("等候费,超出分钟:" + feeRuleResponse.getExceedWaitMinute() + "分钟,单价:" + feeRuleResponse.getExceedWaitMinutePrice());
end
rule "无等候费"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(waitMinute <= 10)
    then
        feeRuleResponse.setBaseWaitMinute(10);
        feeRuleResponse.setExceedWaitMinute(0);
        feeRuleResponse.setExceedWaitMinutePrice(new BigDecimal("1.0"));
        System.out.println("等候费:无");
end

/**
4.远途费
    订单行程超出12公里后每公里1元
*/
rule "远途费 订单行程超出12公里后每公里1元"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(distance.doubleValue() > 12.0)
    then
        BigDecimal exceedLongDistance = $rule.getDistance().subtract(new BigDecimal("12.0"));
        feeRuleResponse.setBaseLongDistance(new BigDecimal("12.0"));
        feeRuleResponse.setExceedLongDistance(exceedLongDistance);
        feeRuleResponse.setExceedLongDistancePrice(new BigDecimal("1.0"));
        System.out.println("远途费,超出公里:" + feeRuleResponse.getExceedLongDistance() + "公里,单价:" + feeRuleResponse.getExceedLongDistancePrice());
end
rule "无远途费"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(distance.doubleValue() <= 12.0)
    then
        feeRuleResponse.setBaseLongDistance(new BigDecimal("12.0"));
        feeRuleResponse.setExceedLongDistance(new BigDecimal("0"));
        feeRuleResponse.setExceedLongDistancePrice(new BigDecimal("0"));
        System.out.println("远途费:无");
end

/**
5.计算总金额
    订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费
*/
rule "计算总金额"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(distance.doubleValue() > 0.0)
    then
        //订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费
        BigDecimal distanceFee = feeRuleResponse.getBaseDistanceFee().add(feeRuleResponse.getExceedDistance().multiply(feeRuleResponse.getExceedDistancePrice()));
        BigDecimal waitFee = new BigDecimal(feeRuleResponse.getExceedWaitMinute()).multiply(feeRuleResponse.getExceedWaitMinutePrice());
        BigDecimal longDistanceFee = feeRuleResponse.getExceedLongDistance().multiply(feeRuleResponse.getExceedLongDistancePrice());
        BigDecimal totalAmount = distanceFee.add(waitFee).add(longDistanceFee);
        feeRuleResponse.setDistanceFee(distanceFee);
        feeRuleResponse.setWaitFee(waitFee);
        feeRuleResponse.setLongDistanceFee(longDistanceFee);
        feeRuleResponse.setTotalAmount(totalAmount);
        System.out.println("计算总金额:" + feeRuleResponse.getTotalAmount() + "元");
end

预估订单金额接口

  • FeeRuleController
@Slf4j
@RestController
@RequestMapping("/rules/fee")
@SuppressWarnings({"unchecked", "rawtypes"})
public class FeeRuleController {

    @Autowired
    private FeeRuleService feeRuleService;
    @Operation(summary = "计算订单费用")
    @PostMapping("/calculateOrderFee")
    public Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm) {
        FeeRuleResponseVo feeRuleResponseVo = feeRuleService.calculateOrderFee(calculateOrderFeeForm);
        return Result.ok(feeRuleResponseVo);
    }
}
  • FeeRuleService
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class FeeRuleServiceImpl implements FeeRuleService {

    @Autowired
    private KieContainer kieContainer;

    // 计算订单费用
    @Override
    public FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm form) {
        // 封装输入对象
        FeeRuleRequest request = new FeeRuleRequest();
        request.setDistance(form.getDistance());
        Date startTime = form.getStartTime();
        request.setStartTime(new DateTime(startTime).toString("HH:mm:ss"));
        request.setWaitMinute(form.getWaitMinute());

        // Drools执行规则
        KieSession session = kieContainer.newKieSession();
        // 封装返回对象
        FeeRuleResponseVo response = new FeeRuleResponseVo();
        session.setGlobal("feeRuleResponse",response);

        session.insert(request);
        session.fireAllRules();
        session.dispose();

        // 封装数据到输出对象
        FeeRuleResponseVo vo = new FeeRuleResponseVo();
        BeanUtils.copyProperties(response,vo);
        return vo;
    }
}

远程调用接口

@FeignClient(value = "service-rules")
public interface FeeRuleFeignClient {

    /**
     * 计算订单费用
     * @param calculateOrderFeeForm
     * @return
     */
    @PostMapping("/rules/fee/calculateOrderFee")
    Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm);
}

预估订单数据接口

  • OrderController
@Autowired  
private OrderService orderService;  
  
@Operation(summary = "预估订单数据")  
@LoginDetection  
@PostMapping("/expectOrder")  
public Result<ExpectOrderVo> expectOrder(@RequestBody ExpectOrderForm expectOrderForm) {  
    return Result.ok(orderService.expectOrder(expectOrderForm));  
}
  • OrderService
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MapFeignClient mapFeignClient;

    @Autowired
    private FeeRuleFeignClient feeRuleFeignClient;

    // 预估订单数据
    @Override
    public ExpectOrderVo expectOrder(ExpectOrderForm expectOrderForm) {
        //获取驾驶线路
        CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
        BeanUtils.copyProperties(expectOrderForm,calculateDrivingLineForm);
        Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
        DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

        //获取订单费用
        FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
        calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
        calculateOrderFeeForm.setStartTime(new Date());
        calculateOrderFeeForm.setWaitMinute(0);
        Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
        FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

        //封装ExpectOrderVo
        ExpectOrderVo expectOrderVo = new ExpectOrderVo();
        expectOrderVo.setDrivingLineVo(drivingLineVo);
        expectOrderVo.setFeeRuleResponseVo(feeRuleResponseVo);
        return expectOrderVo;
    }
}

乘客下单(一)

点击 呼叫代驾 会生成代驾订单,在 order_info 里添加订单数据

乘客下单接口

  • controller 不写了,就是调用获取数据

  • service

@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderStatusLogMapper orderStatusLogMapper;

    // 乘客下单
    @Override
    public Long saveOrderInfo(OrderInfoForm orderInfoForm) {
        // 向order_info表中插入数据
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(orderInfoForm, orderInfo);

        orderInfo.setOrderNo(UUID.randomUUID().toString().replaceAll("-", ""));
        orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());

        orderInfoMapper.insert(orderInfo);

        // 记录日志
        this.log(orderInfo.getId(), orderInfo.getStatus());
        
        return orderInfo.getId();
    }

    public void log(Long orderId, Integer status) {
        OrderStatusLog orderStatusLog = new OrderStatusLog();
        orderStatusLog.setOrderId(orderId);
        orderStatusLog.setOrderStatus(status);
        orderStatusLog.setOperateTime(new Date());
        orderStatusLogMapper.insert(orderStatusLog);
    }
}
  • 远程调用
@FeignClient(value = "service-order")
public interface OrderInfoFeignClient {

    /**
     * 保存订单信息
     * @param orderInfoForm
     * @return
     */
    @PostMapping("/order/info/saveOrderInfo")
    Result<Long> saveOrderInfo(@RequestBody OrderInfoForm orderInfoForm);
}
  • web-service
@Override
public Long submitOrder(SubmitOrderForm submitOrderForm) {
	//1 重新计算驾驶线路
	CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
	BeanUtils.copyProperties(submitOrderForm,submitOrderForm);
	Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
	DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

	//2 重新订单费用
	FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
	calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
	calculateOrderFeeForm.setStartTime(new Date());
	calculateOrderFeeForm.setWaitMinute(0);
	Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
	FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

	//封装数据
	OrderInfoForm orderInfoForm = new OrderInfoForm();
	BeanUtils.copyProperties(submitOrderForm,orderInfoForm);
	orderInfoForm.setExpectDistance(drivingLineVo.getDistance());
	orderInfoForm.setExpectAmount(feeRuleResponseVo.getTotalAmount());
	Result<Long> orderInfoResult = orderInfoFeignClient.saveOrderInfo(orderInfoForm);
	Long orderId = orderInfoResult.getData();

	//TODO 查询附近可以接单司机

	return orderId;
}

查询订单状态

订单微服务模块接口

  • 根据订单id得到订单状态

  • OrderInfoController

@Operation(summary = "根据订单id获取订单状态")
@GetMapping("/getOrderStatus/{orderId}")
public Result<Integer> getOrderStatus(@PathVariable Long orderId) {
    return Result.ok(orderInfoService.getOrderStatus(orderId));
}
  • service
@Override  
public Integer getOrderStatus(Long orderId) {  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getId, orderId);  
    wrapper.select(OrderInfo::getStatus);  
  
    OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);  
  
    if(orderInfo == null){  
        return OrderStatus.NULL_ORDER.getStatus();  
    }  
    return orderInfo.getStatus();  
}
  • 远程调用
/**
 * 根据订单id获取订单状态
 * @param orderId
 * @return
 */
@GetMapping("/order/info/getOrderStatus/{orderId}")
Result<Integer> getOrderStatus(@PathVariable("orderId") Long orderId);

远程调用

乘客端 web-customer

controller

@Operation(summary = "查询订单状态")  
@LoginDetection  
@GetMapping("/getOrderStatus/{orderId}")  
public Result<Integer> getOrderStatus(@PathVariable Long orderId) {  
    return Result.ok(orderService.getOrderStatus(orderId));  
}

service

@Override
public Integer getOrderStatus(Long orderId) {
    Result<Integer> integerResult = orderInfoFeignClient.getOrderStatus(orderId);
    return integerResult.getData();
}

司机端 web-driver

controller

@Tag(name = "订单API接口管理")
@RestController
@RequestMapping("/order")
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderController {

    @Autowired
    private OrderService orderService;
    
    @Operation(summary = "查询订单状态")
    @GuiguLogin
    @GetMapping("/getOrderStatus/{orderId}")
    public Result<Integer> getOrderStatus(@PathVariable Long orderId) {
        return Result.ok(orderService.getOrderStatus(orderId));
    }
}

service

@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    @Override
    public Integer getOrderStatus(Long orderId) {
        return orderInfoFeignClient.getOrderStatus(orderId).getData();
    }
}

乘客下单(二)

搜索附件可以下单的司机

Redis的GEO功能

  • **GEOADD 添加位置信息
GEOADD zhangsan 116.403963 39.915119 tiananmen 116.417876 39.915411 wangfujing 116.404354 39.904748 qianmen
  • **GEORADIUS 搜索范围以内消息
GEORADIUS zhangsan 116.4000 39.9000 1 km WITHDIST

实时更新司机的位置信息

封装位置相关接口

service.service-map

controller

@Tag(name = "位置API接口管理")
@RestController
@RequestMapping("/map/location")
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationController {

    @Autowired
    private LocationService locationService;

    //司机开启接单,更新司机位置信息
    @Operation(summary = "开启接单服务:更新司机经纬度位置")
    @PostMapping("/updateDriverLocation")
    public Result<Boolean> updateDriverLocation(@RequestBody
                                                UpdateDriverLocationForm updateDriverLocationForm) {
        Boolean flag = locationService.updateDriverLocation(updateDriverLocationForm);
        return Result.ok(flag);
    }

    //司机关闭接单,删除司机位置信息
    @Operation(summary = "关闭接单服务:删除司机经纬度位置")
    @DeleteMapping("/removeDriverLocation/{driverId}")
    public Result<Boolean> removeDriverLocation(@PathVariable Long driverId) {
        return Result.ok(locationService.removeDriverLocation(driverId));
    }
}

service

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 更新司机位置信息
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(),
                updateDriverLocationForm.getLatitude().doubleValue());
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION,
                                point,
                                updateDriverLocationForm.getDriverId().toString());
        return true;
    }

    // 删除司机位置信息
    @Override
    public Boolean removeDriverLocation(Long driverId) {
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION,driverId.toString());
        return true;
    }
}

远程调用定义

service-client.service-map-client

@FeignClient(value = "service-map")
public interface LocationFeignClient {

    /**
     * 开启接单服务:更新司机经纬度位置
     * @param updateDriverLocationForm
     * @return
     */
    @PostMapping("/map/location/updateDriverLocation")
    Result<Boolean> updateDriverLocation(@RequestBody UpdateDriverLocationForm updateDriverLocationForm);

    /**
     * 关闭接单服务:删除司机经纬度位置
     * @param driverId
     * @return
     */
    @DeleteMapping("/map/location/removeDriverLocation/{driverId}")
    Result<Boolean> removeDriverLocation(@PathVariable("driverId") Long driverId);
}

司机web端

web.web-driver.controller

controller

@Slf4j  
@Tag(name = "位置API接口管理")  
@RestController  
@RequestMapping(value="/location")  
@SuppressWarnings({"unchecked", "rawtypes"})  
public class LocationController {  
  
    @Autowired  
    private LocationService locationService;  
  
    @Operation(summary = "开启接单服务:更新司机经纬度位置")  
    @LoginDetection  
    @PostMapping("/updateDriverLocation")  
    public Result<Boolean> updateDriverLocation(@RequestBody UpdateDriverLocationForm updateDriverLocationForm) {  
        Long driverId = AuthContextHolder.getUserId();  
        updateDriverLocationForm.setDriverId(driverId);  
        return Result.ok(locationService.updateDriverLocation(updateDriverLocationForm));  
    }  
}

service

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Autowired
    private LocationFeignClient locationFeignClient;

    // 更新司机消息
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        Result<Boolean> booleanResult = locationFeignClient.updateDriverLocation(updateDriverLocationForm);
        return booleanResult.getData();
    }
}

获取司机个性化设置信息

封装查询司机个性化信息接口

service.service-driver.driver

controller

@Operation(summary = "获取司机设置信息")
@GetMapping("/getDriverSet/{driverId}")
public Result<DriverSet> getDriverSet(@PathVariable Long driverId) {
	return Result.ok(driverInfoService.getDriverSet(driverId));
}

service

@Override
public DriverSet getDriverSet(Long driverId) {
	LambdaQueryWrapper<DriverSet> wrapper = new LambdaQueryWrapper<>();
	wrapper.eq(DriverSet::getDriverId, driverId);
	DriverSet driverSet = driverSetMapper.selectOne(wrapper);
	return driverSet;
}

远程调用

service-client.serivce-driver-client.DriverInfoFeignClient

@GetMapping("/driver/info/getDriverSet/{driverId}")  
Result<DriverSet> getDriverSet(@PathVariable("driverId") Long driverId);

修改司机web端

web.web-driver.controller

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

    @Autowired
    private LocationFeignClient locationFeignClient;

    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;

    // 更新司机消息
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        Long driverId = updateDriverLocationForm.getDriverId();
        Result<DriverSet> driverSet = driverInfoFeignClient.getDriverSet(driverId);
        DriverSet data = driverSet.getData();

        if(data.getServiceStatus() == 1){
            Result<Boolean> booleanResult = locationFeignClient.updateDriverLocation(updateDriverLocationForm);
            return booleanResult.getData();
        }else {
            throw new GuiguException(ResultCodeEnum.NO_START_SERVICE);
        }
    }
}

搜索附件适合接单的司机

封装搜索接单司机接口

service-map.map

controller

@Operation(summary = "搜索附近满足条件的司机")
@PostMapping("/searchNearByDriver")
public Result<List<NearByDriverVo>> searchNearByDriver(@RequestBody
													   SearchNearByDriverForm searchNearByDriverForm) {
	return Result.ok(locationService.searchNearByDriver(searchNearByDriverForm));
}

service

//搜索附近满足条件的司机
@Override
public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
    //搜索经纬度位置5公里以内的司机
    //1 操作redis里面geo
    //创建point,经纬度位置
    Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
            searchNearByDriverForm.getLatitude().doubleValue());

    //定义距离,5公里
    Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS,
                           RedisGeoCommands.DistanceUnit.KILOMETERS);

    //创建circle对象,point  distance
    Circle circle = new Circle(point,distance);

    //定义GEO参数,设置返回结果包含内容
    RedisGeoCommands.GeoRadiusCommandArgs args =
            RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                    .includeDistance()  //包含距离
                    .includeCoordinates() //包含坐标
                    .sortAscending(); //升序

    GeoResults<RedisGeoCommands.GeoLocation<String>> result =
            redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

    //2 查询redis最终返回list集合
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();

    //3 对查询list集合进行处理
    // 遍历list集合,得到每个司机信息
    // 根据每个司机个性化设置信息判断
    List<NearByDriverVo> list = new ArrayList<>();
    if(!CollectionUtils.isEmpty(content)) {
        Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();
        while(iterator.hasNext()) {
            GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();

            //获取司机id
            Long driverId = Long.parseLong(item.getContent().getName());

            //远程调用,根据司机id个性化设置信息
            Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
            DriverSet driverSet = driverSetResult.getData();

            //判断订单里程order_distance
            BigDecimal orderDistance = driverSet.getOrderDistance();
            //orderDistance==0,司机没有限制的
            //如果不等于0 ,比如30,接单30公里代驾订单。
            //接单距离 - 当前单子距离  < 0,不复合条件
            // 30          35
            if(orderDistance.doubleValue() != 0
                    && orderDistance.subtract(searchNearByDriverForm.getMileageDistance()).doubleValue()<0) {
                continue;
            }

            //判断接单里程 accept_distance
            //当前接单距离
            BigDecimal currentDistance =
                    new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);

            BigDecimal acceptDistance = driverSet.getAcceptDistance();
            if(acceptDistance.doubleValue() !=0
            && acceptDistance.subtract(currentDistance).doubleValue()<0) {
                continue;
            }

            //封装复合条件数据
            NearByDriverVo nearByDriverVo = new NearByDriverVo();
            nearByDriverVo.setDriverId(driverId);
            nearByDriverVo.setDistance(currentDistance);
            list.add(nearByDriverVo);

        }

    }
    return list;
}

接口测试

  • 启动相关服务

service-map 和 service-driver

  • 使用Swagger进行接口测试

http://localhost:8503/doc.html#/home

第一个接口测试用例及结果 ![[Pasted image 20250528233022.png]]

发现错误来源于redis,开始排查

排查成功,犯了两个很低级的错误

  • 测试错误接口
  • nacos没有改配置文件,导致redis连接不上

再次测试为成功 ![[Pasted image 20250528233937.png]]

第二个接口测试用例及结果 ![[Pasted image 20250528234733.png]]

还是失败,挠头了,去看看报错是什么 ![[Pasted image 20250528235311.png]]

空对象?ok啊,结合自己聪明的大脑以及一点外界帮助,得出原因是因为司机只有一个,但是却有三满足条件司机的数据,程序蒙了,不知道返回哪个

解决方法是:因为是测试嘛,也不用太严谨,自己手动在数据库里面凭空产生点司机出来就行

再次测试,成功 ![[Pasted image 20250528235542.png]]

集成XXL-JOB

service.serivce-dispatch模块

导入依赖

<dependencies>
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
    </dependency>
</dependencies>

执行器配置

  • 修改nacos配置文件 service-dispatch-dev.yaml

  • 创建XXL-JOB配置类

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

  

@Configuration

public class XxlJobConfig {

    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address}")
    private String address;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
}

编写任务job方法测试是否成功

@Component
public class DispatchJobHandler {
    
    @XxlJob("firstJobHandler")
    public void testJobHandler() {
        System.out.println("xxl-job项目集成测试");
    }
}

测试

第一步,启动相关服务

  • 调度中心
  • 执行器项目服务

第二步,在调度中心创建任务

第三步,启动任务

测试的时候遇到点小问题:

  • 先运行调度中心再运行项目
  • 失败要留意xxl的错误日志,哪里有问题就删哪里

封装XXL-JOB客户端

调度中心创建任务方法

在xxl-job-admin中的controller中创建

JobInfoController把原来的添加删除修改任务替换

	//自定义任务操作的方法
	//添加任务
	@RequestMapping("/addJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> addJobInfo(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.add(jobInfo);
	}

	//删除任务
	@RequestMapping("/removeJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> removeJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.remove(jobInfo.getId());
	}

	//修改任务
	@RequestMapping("/updateJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> updateJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.update(jobInfo);
	}

	//停止任务
	@RequestMapping("/stopJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> pauseJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.stop(jobInfo.getId());
	}

	//启动任务
	@RequestMapping("/startJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> startJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.start(jobInfo.getId());
	}

	//添加并启动任务
	@RequestMapping("/addAndStartJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> addAndStartJob(@RequestBody XxlJobInfo jobInfo) {
		ReturnT<String> result = xxlJobService.add(jobInfo);

		String content = result.getContent();
		int id = Integer.parseInt(content);
		xxlJobService.start(id);

		//立即执行一次
		JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, jobInfo.getExecutorParam(), "");
		return result;
	}

执行器项目配置文件添加任务方法

  • 修改nacos配置文件 service-dispatch-dev.yaml
  • 在job.admin下
client:
  jobGroupId: 1
  addUrl: ${xxl.job.admin.addresses}/jobinfo/addJob
  removeUrl: ${xxl.job.admin.addresses}/jobinfo/removeJob
  startJobUrl: ${xxl.job.admin.addresses}/jobinfo/startJob
  stopJobUrl: ${xxl.job.admin.addresses}/jobinfo/stopJob
  addAndStartUrl: ${xxl.job.admin.addresses}/jobinfo/addAndStartJob

添加相关配置类service-dispatch

  • 创建配置类,读取配置文件里面的调用的调度中心的操作方法
@Data
@Component
@ConfigurationProperties(prefix = "xxl.job.client")
public class XxlJobClientConfig {

    private Integer jobGroupId;
    private String addUrl;
    private String removeUrl;
    private String startJobUrl;
    private String stopJobUrl;
    private String addAndStartUrl;
}
  • 创建客户端类,编写调用调度中心里面的方法
import com.alibaba.fastjson.JSONObject;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.dispatch.xxl.config.XxlJobClientConfig;
import com.atguigu.daijia.model.entity.dispatch.XxlJobInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Component
public class XxlJobClient {

    @Autowired
    private XxlJobClientConfig xxlJobClientConfig;

    //客户端调用服务端里面的方法
    @Autowired
    private RestTemplate restTemplate;

    @SneakyThrows
    public Long addJob(String executorHandler, String param, String corn, String desc){
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
        xxlJobInfo.setJobDesc(desc);
        xxlJobInfo.setAuthor("qy");
        xxlJobInfo.setScheduleType("CRON");
        xxlJobInfo.setScheduleConf(corn);
        xxlJobInfo.setGlueType("BEAN");
        xxlJobInfo.setExecutorHandler(executorHandler);
        xxlJobInfo.setExecutorParam(param);
        xxlJobInfo.setExecutorRouteStrategy("FIRST");
        xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
        xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
        xxlJobInfo.setExecutorTimeout(0);
        xxlJobInfo.setExecutorFailRetryCount(0);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getAddUrl();
        ResponseEntity<JSONObject> response =
                restTemplate.postForEntity(url, request, JSONObject.class);

        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("增加xxl执行任务成功,返回信息:{}", response.getBody().toJSONString());
            //content为任务id
            return response.getBody().getLong("content");
        }
        log.info("调用xxl增加执行任务失败:{}", response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean startJob(Long jobId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId(jobId.intValue());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getStartJobUrl();
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("启动xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
            return true;
        }
        log.info("启动xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean stopJob(Long jobId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId(jobId.intValue());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getStopJobUrl();
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("停止xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
            return true;
        }
        log.info("停止xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean removeJob(Long jobId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId(jobId.intValue());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getRemoveUrl();
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("删除xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
            return true;
        }
        log.info("删除xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    //添加并启动任务
    public Long addAndStart(String executorHandler, String param, String corn, String desc) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
        xxlJobInfo.setJobDesc(desc);
        xxlJobInfo.setAuthor("qy");
        xxlJobInfo.setScheduleType("CRON");
        xxlJobInfo.setScheduleConf(corn);
        xxlJobInfo.setGlueType("BEAN");
        xxlJobInfo.setExecutorHandler(executorHandler);
        xxlJobInfo.setExecutorParam(param);
        xxlJobInfo.setExecutorRouteStrategy("FIRST");
        xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
        xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
        xxlJobInfo.setExecutorTimeout(0);
        xxlJobInfo.setExecutorFailRetryCount(0);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        //获取调度中心请求路径
        String url = xxlJobClientConfig.getAddAndStartUrl();

        //restTemplate
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("增加并开始执行xxl任务成功,返回信息:{}", response.getBody().toJSONString());
            //content为任务id
            return response.getBody().getLong("content");
        }
        log.info("增加并开始执行xxl任务失败:{}", response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }
}

启动类配置RestTemplate

直接把RestTemplate写在启动类里

@SpringBootApplication  
@EnableDiscoveryClient  
@EnableFeignClients  
public class ServiceDispatchApplication {  
  
    public static void main(String[] args) {  
        SpringApplication.run(ServiceDispatchApplication.class, args);  
    }  
  
    @Bean  
    public RestTemplate restTemplate() {  
        return new RestTemplate();  
    }  
}

创建并启动任务接口

service-dispatch

controller

// 创建并启动任务调度方法
@Operation(summary = "添加并开始新订单任务调度")
@PostMapping("/addAndStartTask")
public Result<Long> addAndStartTask(@RequestBody NewOrderTaskVo newOrderTaskVo) {
	Long id = newOrderService.addAndStartTask(newOrderTaskVo);
	return Result.ok(id);
}

service

// 添加并开始新订单任务调度
@Override
public Long addAndStartTask(NewOrderTaskVo newOrderTaskVo) {
	// 判断当前订单是否开启任务调度
	LambdaQueryWrapper<OrderJob> wrapper = new LambdaQueryWrapper<>();
	wrapper.eq(OrderJob::getOrderId, newOrderTaskVo.getOrderId());
	OrderJob orderJob = orderJobMapper.selectOne(wrapper);

	// 没有启动,进行操作
	if(orderJob == null){
		//创建并启动任务调度
		//String executorHandler 执行任务job方法
		// String param
		// String corn 执行cron表达式
		// String desc 描述信息
		Long id = xxlJobClient.addAndStart("newOrderTaskHandler",
				"", "0 0/1 * * * ?",
				"新创建订单任务调度:" + newOrderTaskVo.getOrderId());

		orderJob = new OrderJob();
		orderJob.setOrderId(newOrderTaskVo.getOrderId());
		orderJob.setJobId(id);
		orderJob.setParameter(JSONObject.toJSONString(newOrderTaskVo));

		orderJobMapper.insert(orderJob);
	}

	return orderJob.getJobId();
}

远程调用

service-client service-dispatch-client

@FeignClient(value = "service-dispatch")
public interface NewOrderFeignClient {

    /**
     * 添加新订单任务
     * @param newOrderDispatchVo
     * @return
     */
    @PostMapping("/dispatch/newOrder/addAndStartTask")
    Result<Long> addAndStartTask(@RequestBody NewOrderTaskVo newOrderDispatchVo);
}

开发具体任务job方法

JobHandler

public class JobHandler {

    @Autowired
    private XxlJobLogMapper xxlJobLogMapper;
    
    @Autowired
    private NewOrderService newOrderService;
    
    @XxlJob("newOrderTaskHandler")
    public void newOrderTaskHandler() {

        //记录任务调度日志
        XxlJobLog xxlJobLog = new XxlJobLog();
        xxlJobLog.setJobId(XxlJobHelper.getJobId());
        long startTime = System.currentTimeMillis();

        try {
            //执行任务:搜索附近代驾司机
            newOrderService.executeTask(XxlJobHelper.getJobId());

            //成功状态
            xxlJobLog.setStatus(1);
        } catch (Exception e) {
            //失败状态
            xxlJobLog.setStatus(0);
            xxlJobLog.setError(e.getMessage());
            e.printStackTrace();
        } finally {
            long times = System.currentTimeMillis()- startTime;
            //TODO 完善long
            xxlJobLog.setTimes((int)times);
            xxlJobLogMapper.insert(xxlJobLog);
        }
    }
}
  • 远程调用 service-driver-client LocationFeignClient
    // 搜索附近满足条件的司机
    @PostMapping("/map/location/searchNearByDriver")
    public Result<List<NearByDriverVo>> searchNearByDriver(@RequestBody SearchNearByDriverForm searchNearByDriverForm);

service-dispatch NewOrderServiceImpl

@Autowired
private LocationFeignClient locationFeignClient;

@Autowired
private OrderInfoFeignClient orderInfoFeignClient;

@Autowired
private RedisTemplate redisTemplate;

// 搜索附件司机
@Override
public void executeTask(long jobId) {
	// 根据jobid查询当前任务是否已经创建
	LambdaQueryWrapper<OrderJob> wrapper = new LambdaQueryWrapper<>();
	wrapper.eq(OrderJob::getJobId, jobId);
	OrderJob orderJob = orderJobMapper.selectOne(wrapper);
	if(orderJob == null){
		return;
	}
	// 查询订单状态,如果是接单状态,继续执行,否则停止
	String parameter = orderJob.getParameter();
	NewOrderTaskVo newOrderTaskVo = JSONObject.parseObject(parameter, NewOrderTaskVo.class);
	Integer status = orderInfoFeignClient.getOrderStatus(newOrderTaskVo.getOrderId()).getData();

	if(status.intValue() != OrderStatus.WAITING_ACCEPT.getStatus().intValue()){
		xxlJobClient.stopJob(jobId);
		return;
	}
	// 远程调用,查询正在接单的司机集合
	SearchNearByDriverForm from = new SearchNearByDriverForm();
	SearchNearByDriverForm searchNearByDriverForm = new SearchNearByDriverForm();
	searchNearByDriverForm.setLongitude(newOrderTaskVo.getStartPointLongitude());
	searchNearByDriverForm.setLatitude(newOrderTaskVo.getStartPointLatitude());
	List<NearByDriverVo> result = locationFeignClient.searchNearByDriver(from).getData();

	// 遍历满足条件的司机集合,为每个司机创建临时队列,存储新订单信息
	result.forEach(driver -> {
		String repeatKey =
				RedisConstant.DRIVER_ORDER_REPEAT_LIST+newOrderTaskVo.getOrderId();
		Boolean isMember = redisTemplate.opsForSet().isMember(repeatKey, driver.getDriverId());
		if(!isMember){
			// 把订单信息推送刚给满足条件的司机
			redisTemplate.opsForSet().add(parameter, driver.getDriverId());
			// 设置过期时间15分钟(一分钟等待)
			redisTemplate.expire(repeatKey,
					RedisConstant.DRIVER_ORDER_REPEAT_LIST_EXPIRES_TIME,
					TimeUnit.MINUTES);

			NewOrderDataVo newOrderDataVo = new NewOrderDataVo();
			newOrderDataVo.setOrderId(newOrderTaskVo.getOrderId());
			newOrderDataVo.setStartLocation(newOrderTaskVo.getStartLocation());
			newOrderDataVo.setEndLocation(newOrderTaskVo.getEndLocation());
			newOrderDataVo.setExpectAmount(newOrderTaskVo.getExpectAmount());
			newOrderDataVo.setExpectDistance(newOrderTaskVo.getExpectDistance());
			newOrderDataVo.setExpectTime(newOrderTaskVo.getExpectTime());
			newOrderDataVo.setFavourFee(newOrderTaskVo.getFavourFee());
			newOrderDataVo.setDistance(driver.getDistance());
			newOrderDataVo.setCreateTime(newOrderTaskVo.getCreateTime());
			// 新订单保存司机的临时队列
			String key = RedisConstant.DRIVER_ORDER_TEMP_LIST+driver.getDriverId();
			redisTemplate.opsForList().leftPush(key, JSONObject.toJSONString(newOrderDataVo));
			// 设置过期时间,一分钟
			redisTemplate.expire(key, RedisConstant.DRIVER_ORDER_TEMP_LIST_EXPIRES_TIME, TimeUnit.MINUTES);
		}
	});
}

乘客下单添加任务调度

web-customer 补充之前OrderServiceImpl里写的查询附近可以接单司机

@Autowired  
private NewOrderFeignClient newOrderFeignClient;

@Override
public Long submitOrder(SubmitOrderForm submitOrderForm) {
	//1 重新计算驾驶线路
	CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
	BeanUtils.copyProperties(submitOrderForm,submitOrderForm);
	Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
	DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

	//2 重新订单费用
	FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
	calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
	calculateOrderFeeForm.setStartTime(new Date());
	calculateOrderFeeForm.setWaitMinute(0);
	Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
	FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

	//封装数据
	OrderInfoForm orderInfoForm = new OrderInfoForm();
	BeanUtils.copyProperties(submitOrderForm,orderInfoForm);
	orderInfoForm.setExpectDistance(drivingLineVo.getDistance());
	orderInfoForm.setExpectAmount(feeRuleResponseVo.getTotalAmount());
	Result<Long> orderInfoResult = orderInfoFeignClient.saveOrderInfo(orderInfoForm);
	Long orderId = orderInfoResult.getData();

	// 任务调度:查询附近可以接单司机
	NewOrderTaskVo newOrderTaskVo = new NewOrderTaskVo();
	newOrderTaskVo.setOrderId(orderId);
	newOrderTaskVo.setStartLocation(orderInfoForm.getStartLocation());
	newOrderTaskVo.setStartPointLongitude(orderInfoForm.getStartPointLongitude());
	newOrderTaskVo.setStartPointLatitude(orderInfoForm.getStartPointLatitude());
	newOrderTaskVo.setEndLocation(orderInfoForm.getEndLocation());
	newOrderTaskVo.setEndPointLongitude(orderInfoForm.getEndPointLongitude());
	newOrderTaskVo.setEndPointLatitude(orderInfoForm.getEndPointLatitude());
	newOrderTaskVo.setExpectAmount(orderInfoForm.getExpectAmount());
	newOrderTaskVo.setExpectDistance(orderInfoForm.getExpectDistance());
	newOrderTaskVo.setExpectTime(drivingLineVo.getDuration());
	newOrderTaskVo.setFavourFee(orderInfoForm.getFavourFee());
	newOrderTaskVo.setCreateTime(new Date());
	Long jobId = newOrderFeignClient.addAndStartTask(newOrderTaskVo).getData();

	return orderId;
}

司机获取最新订单数据

service-dispatch

查询最新订单和清空司机队列数据

NewOrderController

@Operation(summary = "查询司机新订单数据")
@GetMapping("/findNewOrderQueueData/{driverId}")
public Result<List<NewOrderDataVo>> findNewOrderQueueData(@PathVariable Long driverId) {
	return Result.ok(newOrderService.findNewOrderQueueData(driverId));
}

@Operation(summary = "清空新订单队列数据")
@GetMapping("/clearNewOrderQueueData/{driverId}")
public Result<Boolean> clearNewOrderQueueData(@PathVariable Long driverId) {
	return Result.ok(newOrderService.clearNewOrderQueueData(driverId));
}

NewOrderServiceImpl

//获取最新订单
@Override
public List<NewOrderDataVo> findNewOrderQueueData(Long driverId) {
    List<NewOrderDataVo> list = new ArrayList<>();
    String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + driverId;
    Long size = redisTemplate.opsForList().size(key);
    if(size > 0) {
        for (int i = 0; i < size; i++) {
            String content = (String)redisTemplate.opsForList().leftPop(key);
            NewOrderDataVo newOrderDataVo = JSONObject.parseObject(content,NewOrderDataVo.class);
            list.add(newOrderDataVo);
        }
    }
    return list;
}

//清空队列数据
@Override
public Boolean clearNewOrderQueueData(Long driverId) {
    String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + driverId;
    redisTemplate.delete(key);
    return true;
}

远程调用

service-dispatch

NewOrderFeignClient

// 查询司机新订单数据
@GetMapping("/dispatch/newOrder/findNewOrderQueueData/{driverId}")
Result<List<NewOrderDataVo>> findNewOrderQueueData(@PathVariable("driverId") Long driverId);

// 清空新订单队列数据
@GetMapping("/dispatch/newOrder/clearNewOrderQueueData/{driverId}")
Result<Boolean> clearNewOrderQueueData(@PathVariable("driverId") Long driverId);

司机web端调用

OrderController

@Operation(summary = "查询司机新订单数据")  
@LoginDetection  
@GetMapping("/findNewOrderQueueData")  
public Result<List<NewOrderDataVo>> findNewOrderQueueData() {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.findNewOrderQueueData(driverId));  
}

service

@Autowired  
private NewOrderFeignClient newOrderFeignClient;

@Override  
public List<NewOrderDataVo> findNewOrderQueueData(Long driverId) {  
    return newOrderFeignClient.findNewOrderQueueData(driverId).getData();  
}

司机接单

需求

  • 乘客下单后,新订单信息已经放在司机临时队列,然后可以开始接单了

首先,司机登录、认证(身份证、驾驶证、创建人脸模型) 第二,司机每天接单前都需要人脸识别 第三,司机开始接单,更新接单状态 第四,司机接单后,删除司机之前存储在redis里面的位置信息 第五,司机接单后,清空临时队列新订单信息

查找司机端当前订单

设定是接单去需要验证司机是否有未完成的订单,暂时不管

  • 后续完成,暂时跳过
@Operation(summary = "查找司机端当前订单")  
@LoginDetection  
@GetMapping("/searchDriverCurrentOrder")  
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder() {  
    CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();  
    // TODO 后续完善  
    currentOrderInfoVo.setIsHasCurrentOrder(false);  
    return Result.ok(currentOrderInfoVo);  
}

判断司机在当日是否人脸识别

service-driver

DriverInfoController

@Operation(summary = "判断司机当日是否进行过人脸识别")  
@GetMapping("/isFaceRecognition/{driverId}")  
Result<Boolean> isFaceRecognition(@PathVariable("driverId") Long driverId) {  
    return Result.ok(driverInfoService.isFaceRecognition(driverId));  
}

service

// 判断司机当日是否进行过人脸识别  
@Override  
public Boolean isFaceRecognition(Long driverId) {  
    //根据司机id + 当日日期进行查询  
    LambdaQueryWrapper<DriverFaceRecognition> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(DriverFaceRecognition::getDriverId,driverId);  
    // 年-月-日 格式  
    wrapper.eq(DriverFaceRecognition::getFaceDate,new DateTime().toString("yyyy-MM-dd"));  
    //调用mapper方法  
    Long count = driverFaceRecognitionMapper.selectCount(wrapper);  
  
    return count != 0;  
}

远程调用

DriverInfoFeignClient

// 判断司机当日是否进行过人脸识别  
@GetMapping("/driver/info/isFaceRecognition/{driverId}")  
Result<Boolean> isFaceRecognition(@PathVariable("driverId") Long driverId);

web端调用

DriverController

@Operation(summary = "判断司机当日是否进行过人脸识别")  
@LoginDetection  
@GetMapping("/isFaceRecognition")  
Result<Boolean> isFaceRecognition() {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(driverService.isFaceRecognition(driverId));  
}

service

// 判断司机当日是否进行过人脸识别  
@Override  
public Boolean isFaceRecognition(Long driverId) {  
    Result<Boolean> faceRecognition = driverInfoFeignClient.isFaceRecognition(driverId);  
    return faceRecognition.getData();  
}

人脸识别接口

service-driver

DriverInfoController

@Operation(summary = "验证司机人脸")
@PostMapping("/verifyDriverFace")
public Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm) {
    return Result.ok(driverInfoService.verifyDriverFace(driverFaceModelForm));
}

service

// 验证司机人脸  
@Override  
public Boolean verifyDriverFace(DriverFaceModelForm driverFaceModelForm) {  
    // 照片比对  
    try{  
        // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密  
        // 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性  
        // 以下代码示例仅供参考,建议采用更安全的方式来使用密钥  
        // 请参见:https://cloud.tencent.com/document/product/1278/85305  
        // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取  
        Credential cred = new Credential(tencentCloudProperties.getSecretId(),  
                tencentCloudProperties.getSecretKey());  
        // 使用临时密钥示例  
        // Credential cred = new Credential("SecretId", "SecretKey", "Token");  
        // 实例化一个http选项,可选的,没有特殊需求可以跳过  
        HttpProfile httpProfile = new HttpProfile();  
        httpProfile.setEndpoint("iai.tencentcloudapi.com");  
        // 实例化一个client选项,可选的,没有特殊需求可以跳过  
        ClientProfile clientProfile = new ClientProfile();  
        clientProfile.setHttpProfile(httpProfile);  
  
        // 实例化要请求产品的client对象,clientProfile是可选的  
        IaiClient client = new IaiClient(cred,  
                tencentCloudProperties.getRegion(), clientProfile);  
        // 实例化一个请求对象,每个接口都会对应一个request对象  
        VerifyFaceRequest req = new VerifyFaceRequest();  
        // 设置相关参数  
        req.setImage(driverFaceModelForm.getImageBase64());  
        req.setPersonId(String.valueOf(driverFaceModelForm.getDriverId()));  
  
        // 返回的resp是一个VerifyFaceResponse的实例,与请求对象对应  
        VerifyFaceResponse resp = client.VerifyFace(req);  
        // 输出json格式的字符串回包  
        System.out.println(AbstractModel.toJsonString(resp));  
        if(resp.getIsMatch()){  
            // 静态活体检测  
            Boolean isSuccess = this.detectLiveFace(driverFaceModelForm.getImageBase64());  
            if(isSuccess){  
                // 都没问题,添加塑胶到认证表中  
                DriverFaceRecognition driverFaceRecognition = new DriverFaceRecognition();  
                driverFaceRecognition.setDriverId(driverFaceModelForm.getDriverId());  
                driverFaceRecognition.setFaceDate(new Date());  
                driverFaceRecognitionMapper.insert(driverFaceRecognition);  
                return true;  
            }  
        }  
    } catch (TencentCloudSDKException e) {  
        System.out.println(e.toString());  
    }  
  
    throw new GuiguException(ResultCodeEnum.DATA_ERROR);  
}  
  
//人脸静态活体检测  
private Boolean detectLiveFace(String imageBase64) {  
    try{  
        // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密  
        // 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305  
        // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取  
        Credential cred = new Credential(tencentCloudProperties.getSecretId(),  
                tencentCloudProperties.getSecretKey());  
        // 实例化一个http选项,可选的,没有特殊需求可以跳过  
        HttpProfile httpProfile = new HttpProfile();  
        httpProfile.setEndpoint("iai.tencentcloudapi.com");  
        // 实例化一个client选项,可选的,没有特殊需求可以跳过  
        ClientProfile clientProfile = new ClientProfile();  
        clientProfile.setHttpProfile(httpProfile);  
        // 实例化要请求产品的client对象,clientProfile是可选的  
        IaiClient client = new IaiClient(cred, tencentCloudProperties.getRegion(),  
                clientProfile);  
        // 实例化一个请求对象,每个接口都会对应一个request对象  
        DetectLiveFaceRequest req = new DetectLiveFaceRequest();  
        req.setImage(imageBase64);  
        // 返回的resp是一个DetectLiveFaceResponse的实例,与请求对象对应  
        DetectLiveFaceResponse resp = client.DetectLiveFace(req);  
        // 输出json格式的字符串回包  
        System.out.println(DetectLiveFaceResponse.toJsonString(resp));  
        if(resp.getIsLiveness()) {  
            return true;  
        }  
    } catch (TencentCloudSDKException e) {  
        System.out.println(e.toString());  
    }  
    return false;  
}

远程调用 service-driver-client

DriverInfoFeignClient

// 验证司机人脸  
@PostMapping("/driver/info/verifyDriverFace")  
Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm);

web-driver

DriverController

@Operation(summary = "验证司机人脸")  
@LoginDetection  
@PostMapping("/verifyDriverFace")  
public Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm) {  
    driverFaceModelForm.setDriverId(AuthContextHolder.getUserId());  
    return Result.ok(driverService.verifyDriverFace(driverFaceModelForm));  
}

service

// 验证司机人脸  
@Override  
public Boolean verifyDriverFace(DriverFaceModelForm driverFaceModelForm) {  
    Result<Boolean> booleanResult = driverInfoFeignClient.verifyDriverFace(driverFaceModelForm);  
    return booleanResult.getData();  
}

更新司机接单状态

DriverInfoController

@Operation(summary = "更新接单状态")  
@GetMapping("/updateServiceStatus/{driverId}/{status}")  
public Result<Boolean> updateServiceStatus(@PathVariable Long driverId, @PathVariable Integer status) {  
    return Result.ok(driverInfoService.updateServiceStatus(driverId, status));  
}

service

@Override  
public Boolean updateServiceStatus(Long driverId, Integer status) {  
    LambdaQueryWrapper<DriverSet> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(DriverSet::getDriverId,driverId);  
    DriverSet driverSet = new DriverSet();  
    driverSet.setServiceStatus(status);  
    driverSetMapper.update(driverSet,wrapper);  
    return true;  
}

远程调用DriverInfoFeignClient

// 更新接单状态  
@GetMapping("/driver/info/updateServiceStatus/{driverId}/{status}")  
Result<Boolean> updateServiceStatus(@PathVariable("driverId") Long driverId, @PathVariable("status") Integer status);

开启接单服务web接口

在web-driver中进行编写

司机开启接单后,上传位置信息到redis的GEO,才能被任务调度搜索到司机信息,开始抢单

DriverController

@Operation(summary = "开始接单服务")  
@LoginDetection  
@GetMapping("/startService")  
public Result<Boolean> startService() {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(driverService.startService(driverId));  
}

DriverServiceImpl

@Autowired  
private LocationFeignClient locationFeignClient;  
  
@Autowired  
private NewOrderFeignClient newOrderFeignClient;

// 开始接单服务  
@Override  
public Boolean startService(Long driverId) {  
    //1 判断完成认证  
    DriverLoginVo driverLoginVo = driverInfoFeignClient.getDriverLoginInfo(driverId).getData();  
    if(driverLoginVo.getAuthStatus()!=2) {  
        throw new GuiguException(ResultCodeEnum.AUTH_ERROR);  
    }  
  
    //2 判断当日是否人脸识别  
    Boolean isFace = driverInfoFeignClient.isFaceRecognition(driverId).getData();  
    if(!isFace) {  
        throw new GuiguException(ResultCodeEnum.FACE_ERROR);  
    }  
  
    //3 更新订单状态 1 开始接单  
    driverInfoFeignClient.updateServiceStatus(driverId,1);  
  
    //4 删除redis司机位置信息  
    locationFeignClient.removeDriverLocation(driverId);  
  
    //5 清空司机临时队列数据  
    newOrderFeignClient.clearNewOrderQueueData(driverId);  
    return true;  
}

停止接单服务web接口

DriverController

@Operation(summary = "停止接单服务")  
@LoginDetection  
@GetMapping("/stopService")  
public Result<Boolean> stopService() {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(driverService.stopService(driverId));  
}

DriverServiceImpl

//停止接单服务  
@Override  
public Boolean stopService(Long driverId) {  
    //更新司机的接单状态 0   
    driverInfoFeignClient.updateServiceStatus(driverId,0);  
  
    //删除司机位置信息  
    locationFeignClient.removeDriverLocation(driverId);  
  
    //清空司机临时队列  
    newOrderFeignClient.clearNewOrderQueueData(driverId);  
    return true;  
}

司机抢单

抢单接口

微服务抢单接口

service-order模块内

OrderInfoController

@Operation(summary = "司机抢单")  
@GetMapping("/robNewOrder/{driverId}/{orderId}")  
public Result<Boolean> robNewOrder(@PathVariable Long driverId, @PathVariable Long orderId) {  
    return Result.ok(orderInfoService.robNewOrder(driverId, orderId));  
}

在OrderInfoServiceImpl之前保存订单 方法修改

@Autowired  
private RedisTemplate redisTemplate;

//乘客下单  
@Override  
public Long saveOrderInfo(OrderInfoForm orderInfoForm) {  
    //order_info添加订单数据  
    OrderInfo orderInfo = new OrderInfo();  
    BeanUtils.copyProperties(orderInfoForm,orderInfo);  
    //订单号  
    String orderNo = UUID.randomUUID().toString().replaceAll("-","");  
    orderInfo.setOrderNo(orderNo);  
    //订单状态  
    orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());  
    orderInfoMapper.insert(orderInfo);  
  
    //记录日志  
    this.log(orderInfo.getId(),orderInfo.getStatus());  
  
    //向redis添加标识  
    //接单标识,标识不存在了说明不在等待接单状态了  
    redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,  
            "0", RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME, TimeUnit.MINUTES);  
  
    return orderInfo.getId();  
}

OrderInfoServiceImpl新增司机抢单方法

//司机抢单  
@Override  
public Boolean robNewOrder(Long driverId, Long orderId) {  
    //判断订单是否存在,通过Redis,减少数据库压力  
    if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {  
        //抢单失败  
        throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);  
    }  
  
    //司机抢单  
    //修改order_info表订单状态值2:已经接单 + 司机id + 司机接单时间  
    //修改条件:根据订单id  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getId,orderId);  
    OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);  
    //设置  
    orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());  
    orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());  
    orderInfo.setDriverId(driverId);  
    orderInfo.setAcceptTime(new Date());  
    //调用方法修改  
    int rows = orderInfoMapper.updateById(orderInfo);  
    if(rows != 1) {  
        //抢单失败  
        throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);  
    }  
  
    //删除抢单标识  
    redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);  
    return true;  
}

远程调用

/**  
 * 司机抢单  
 * @param driverId  
 * @param orderId  
 * @return  
 */  
@GetMapping("/order/info/robNewOrder/{driverId}/{orderId}")  
Result<Boolean> robNewOrder(@PathVariable("driverId") Long driverId, @PathVariable("orderId") Long orderId);

司机web端接口

OrderController

@Operation(summary = "司机抢单")  
@LoginDetection  
@GetMapping("/robNewOrder/{orderId}")  
public Result<Boolean> robNewOrder(@PathVariable Long orderId) {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.robNewOrder(driverId, orderId));  
}

service

// 司机抢单  
@Override  
public Boolean robNewOrder(Long driverId, Long orderId) {  
    Result<Boolean> booleanResult = orderInfoFeignClient.robNewOrder(driverId, orderId);  
    return booleanResult.getData();  
}

添加Redisson分布式锁到司机抢单

在service里service-order中OrderInfoServiceImpl添加分布式锁

修改之前写的robNewOrder

@Autowired  
private RedissonClient redissonClient;

//司机抢单  
@Override  
public Boolean robNewOrder(Long driverId, Long orderId) {  
    //判断订单是否存在,通过Redis,减少数据库压力  
    if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {  
        //抢单失败  
        throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);  
    }  
  
    //创建锁  
    RLock lock = redissonClient.getLock(RedisConstant.ROB_NEW_ORDER_LOCK + orderId);  
  
    try {  
        //获取锁  
        boolean flag = lock.tryLock(RedisConstant.ROB_NEW_ORDER_LOCK_WAIT_TIME,  
                RedisConstant.ROB_NEW_ORDER_LOCK_LEASE_TIME,   
                TimeUnit.SECONDS);  
        if(flag) {  
            if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {  
                //抢单失败  
                throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);  
            }  
            //司机抢单  
            //修改order_info表订单状态值2:已经接单 + 司机id + 司机接单时间  
            //修改条件:根据订单id  
            LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
            wrapper.eq(OrderInfo::getId,orderId);  
            OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);  
            //设置  
            orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());  
            orderInfo.setDriverId(driverId);  
            orderInfo.setAcceptTime(new Date());  
            //调用方法修改  
            int rows = orderInfoMapper.updateById(orderInfo);  
            if(rows != 1) {  
                //抢单失败  
                throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);  
            }  
  
            //删除抢单标识  
            redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);  
        }  
    }catch (Exception e) {  
        //抢单失败  
        throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);  
    }finally {  
        //释放  
        if(lock.isLocked()) {  
            lock.unlock();  
        }  
    }  
    return true;  
}

订单执行(一)

加载当前订单

需求

  • 无论是司机端,还是乘客端,遇到页面切换,重新登录小程序等,只要回到首页面,查看当前是否有正在执行的订单,如果有跳转到当前订单的执行页面

乘客端查找当前订单

订单微服务接口

  • OrderInfoController
@Operation(summary = "乘客端查找当前订单")
@GetMapping("/searchCustomerCurrentOrder/{customerId}")
public Result<CurrentOrderInfoVo> searchCustomerCurrentOrder(@PathVariable Long customerId) {
    return Result.ok(orderInfoService.searchCustomerCurrentOrder(customerId));
}

OrderInfoServiceImpl

@Autowired  
private RedissonClient redissonClient;

//乘客端查找当前订单  
@Override  
public CurrentOrderInfoVo searchCustomerCurrentOrder(Long customerId) {  
    //封装条件  
    //乘客id  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getCustomerId,customerId);  
  
    //各种状态  
    Integer[] statusArray = {  
            OrderStatus.ACCEPTED.getStatus(),  
            OrderStatus.DRIVER_ARRIVED.getStatus(),  
            OrderStatus.UPDATE_CART_INFO.getStatus(),  
            OrderStatus.START_SERVICE.getStatus(),  
            OrderStatus.END_SERVICE.getStatus(),  
            OrderStatus.UNPAID.getStatus()  
    };  
    wrapper.in(OrderInfo::getStatus,statusArray);  
  
    //获取最新一条记录  
    wrapper.orderByDesc(OrderInfo::getId);  
    wrapper.last(" limit 1");  
  
    //调用方法  
    OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);  
  
    //封装到CurrentOrderInfoVo  
    CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();  
    if(orderInfo != null) {  
        currentOrderInfoVo.setOrderId(orderInfo.getId());  
        currentOrderInfoVo.setStatus(orderInfo.getStatus());  
        currentOrderInfoVo.setIsHasCurrentOrder(true);  
    } else {  
        currentOrderInfoVo.setIsHasCurrentOrder(false);  
    }  
    return currentOrderInfoVo;  
}

远程调用

serivce-order-client OrderInfoFeignClient

/**  
 * 乘客端查找当前订单  
 * @param customerId  
 * @return  
 */  
@GetMapping("/order/info/searchCustomerCurrentOrder/{customerId}")  
Result<CurrentOrderInfoVo> searchCustomerCurrentOrder(@PathVariable("customerId") Long customerId);

乘客web端接口

web-customer OrderController

@Operation(summary = "乘客端查找当前订单")  
@LoginDetection  
@GetMapping("/searchCustomerCurrentOrder")  
public Result<CurrentOrderInfoVo> searchCustomerCurrentOrder() {  
    Long customerId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.searchCustomerCurrentOrder(customerId));  
}

OrderServiceImpl

// 乘客端查找当前订单  
@Override  
public CurrentOrderInfoVo searchCustomerCurrentOrder(Long customerId) {  
    Result<CurrentOrderInfoVo> currentOrderInfoVoResult = orderInfoFeignClient.searchCustomerCurrentOrder(customerId);  
    return currentOrderInfoVoResult.getData();  
}

司机端查找当前订单

订单微服务接口

service-order OrderInfoController

@Operation(summary = "司机端查找当前订单")
@GetMapping("/searchDriverCurrentOrder/{driverId}")
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder(@PathVariable Long driverId) {
    return Result.ok(orderInfoService.searchDriverCurrentOrder(driverId));
}

service

// 司机端查找当前订单
@Override
public CurrentOrderInfoVo searchDriverCurrentOrder(Long driverId) {
	//封装条件
	LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
	wrapper.eq(OrderInfo::getDriverId,driverId);
	Integer[] statusArray = {
			OrderStatus.ACCEPTED.getStatus(),
			OrderStatus.DRIVER_ARRIVED.getStatus(),
			OrderStatus.UPDATE_CART_INFO.getStatus(),
			OrderStatus.START_SERVICE.getStatus(),
			OrderStatus.END_SERVICE.getStatus()
	};
	wrapper.in(OrderInfo::getStatus,statusArray);
	wrapper.orderByDesc(OrderInfo::getId);
	wrapper.last(" limit 1");
	OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
	//封装到vo
	CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();
	if(null != orderInfo) {
		currentOrderInfoVo.setStatus(orderInfo.getStatus());
		currentOrderInfoVo.setOrderId(orderInfo.getId());
		currentOrderInfoVo.setIsHasCurrentOrder(true);
	} else {
		currentOrderInfoVo.setIsHasCurrentOrder(false);
	}
	return currentOrderInfoVo;
}

远程调用

service-order-client OrderInfoFeignClient

/**
 * 司机端查找当前订单
 * @param driverId
 * @return
 */
@GetMapping("/order/info/searchDriverCurrentOrder/{driverId}")
Result<CurrentOrderInfoVo> searchDriverCurrentOrder(@PathVariable("driverId") Long driverId);

司机端web接口

web-driver OrderController

@Operation(summary = "司机端查找当前订单")  
@LoginDetection  
@GetMapping("/searchDriverCurrentOrder")  
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder() {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.searchDriverCurrentOrder(driverId));  
}

service

// 司机端查找当前订单  
@Override  
public CurrentOrderInfoVo searchDriverCurrentOrder(Long driverId) {  
    return orderInfoFeignClient.searchDriverCurrentOrder(driverId).getData();  
}

获取订单信息

订单微服务接口

service-order OrderInfoController

@Operation(summary = "根据订单id获取订单信息")
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfo> getOrderInfo(@PathVariable Long orderId) {
    return Result.ok(orderInfoService.getById(orderId));
}

远程调用

OrderInfoFeignClient

/**
 * 根据订单id获取订单信息
 * @param orderId
 * @return
 */
@GetMapping("/order/info/getOrderInfo/{orderId}")
Result<OrderInfo> getOrderInfo(@PathVariable("orderId") Long orderId);

乘客端web接口

OrderController

@Operation(summary = "获取订单信息")  
@LoginDetection  
@GetMapping("/getOrderInfo/{orderId}")  
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {  
    Long customerId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.getOrderInfo(orderId, customerId));  
}

service

// 获取订单信息  
@Override  
public OrderInfoVo getOrderInfo(Long orderId, Long customerId) {  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();  
    //判断  
    if(orderInfo.getCustomerId() != customerId) {  
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
    }  
  
    OrderInfoVo orderInfoVo = new OrderInfoVo();  
    orderInfoVo.setOrderId(orderId);  
    BeanUtils.copyProperties(orderInfo,orderInfoVo);  
    return orderInfoVo;  
}

司机端web接口

OrderController

@Operation(summary = "获取订单账单详细信息")  
@LoginDetection  
@GetMapping("/getOrderInfo/{orderId}")  
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.getOrderInfo(orderId, driverId));  
}

service

@Override
public OrderInfoVo getOrderInfo(Long orderId, Long driverId) {
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
    if(orderInfo.getDriverId() != driverId) {
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
    }
    OrderInfoVo orderInfoVo = new OrderInfoVo();
    orderInfoVo.setOrderId(orderId);
    BeanUtils.copyProperties(orderInfo,orderInfoVo);
    return orderInfoVo;
}

司乘同显

司机端同显

  • 司机所在地址就是司乘同显开始位置,订单地址就是司乘同显的终点
  • 计算司机司乘同显最佳路线

司机端web

OrderController

@Operation(summary = "计算最佳驾驶线路")  
@LoginDetection  
@PostMapping("/calculateDrivingLine")  
public Result<DrivingLineVo> calculateDrivingLine(@RequestBody CalculateDrivingLineForm calculateDrivingLineForm) {  
    return Result.ok(orderService.calculateDrivingLine(calculateDrivingLineForm));  
}

service

@Autowired  
private MapFeignClient mapFeignClient;

// 计算最佳驾驶线路  
@Override  
public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {  
    Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);  
    return drivingLineVoResult.getData();  
}

更新位置到redis

  • 实时更新司机位置到redis

地图微服务接口service-map

LocationController

@Operation(summary = "司机赶往代驾起始点:更新订单地址到缓存")  
@PostMapping("/updateOrderLocationToCache")  
public Result<Boolean> updateOrderLocationToCache(@RequestBody UpdateOrderLocationForm updateOrderLocationForm) {  
    return Result.ok(locationService.updateOrderLocationToCache(updateOrderLocationForm));  
}

service

// 司机赶往代驾起始点:更新订单地址到缓存  
@Override  
public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {  
  
    OrderLocationVo orderLocationVo = new OrderLocationVo();  
    orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());  
    orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());  
  
    String key = RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId();  
    redisTemplate.opsForValue().set(key, orderLocationVo);  
    return true;  
}

远程调用service-map-client

LocationFeignClient

/**  
 * 司机赶往代驾起始点:更新订单地址到缓存  
 * @param updateOrderLocationForm  
 * @return  
 */  
@PostMapping("/map/location/updateOrderLocationToCache")  
Result<Boolean> updateOrderLocationToCache(@RequestBody UpdateOrderLocationForm updateOrderLocationForm);

司机web端

LocationController

@Operation(summary = "司机赶往代驾起始点:更新订单位置到Redis缓存")  
@LoginDetection  
@PostMapping("/updateOrderLocationToCache")  
public Result updateOrderLocationToCache(@RequestBody UpdateOrderLocationForm updateOrderLocationForm) {  
    return Result.ok(locationService.updateOrderLocationToCache(updateOrderLocationForm));  
}

service

// 司机赶往代驾起始点:更新订单位置到Redis缓存  
@Override  
public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {  
    return locationFeignClient.updateOrderLocationToCache(updateOrderLocationForm).getData();  
}

获取司机基本信息

  • 乘客端进入司乘同显界面可以看到司机的基本信息.

司机微服务接口service-driver

DriverInfoController

@Operation(summary = "获取司机基本信息")  
@GetMapping("/getDriverInfo/{driverId}")  
public Result<DriverInfoVo> getDriverInfoOrder(@PathVariable Long driverId) {  
    return Result.ok(driverInfoService.getDriverInfoOrder(driverId));  
}

service

//获取司机基本信息  
@Override  
public DriverInfoVo getDriverInfoOrder(Long driverId) {  
    //司机id获取基本信息  
    DriverInfo driverInfo = driverInfoMapper.selectById(driverId);  
  
    //封装DriverInfoVo  
    DriverInfoVo driverInfoVo = new DriverInfoVo();  
    BeanUtils.copyProperties(driverInfo,driverInfoVo);  
  
    //计算驾龄  
    //获取当前年  
    int currentYear = new DateTime().getYear();  
    //获取驾驶证初次领证日期  
    //driver_license_issue_date  
    int firstYear = new DateTime(driverInfo.getDriverLicenseIssueDate()).getYear();  
    int driverLicenseAge = currentYear - firstYear;  
    driverInfoVo.setDriverLicenseAge(driverLicenseAge);  
  
    return driverInfoVo;  
}

远程调用

DriverInfoFeignClient

/**
 * 获取司机基本信息
 * @param driverId
 * @return
 */
@GetMapping("/driver/info/getDriverInfo/{driverId}")
Result<DriverInfoVo> getDriverInfo(@PathVariable("driverId") Long driverId);

乘客端web接口

OrderController

@Operation(summary = "根据订单id获取司机基本信息")  
@LoginDetection  
@GetMapping("/getDriverInfo/{orderId}")  
public Result<DriverInfoVo> getDriverInfo(@PathVariable Long orderId) {  
    Long customerId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.getDriverInfo(orderId, customerId));  
}

service

// 根据订单id获取司机基本信息  
@Override  
public DriverInfoVo getDriverInfo(Long orderId, Long customerId) {  
    //根据订单id获取订单信息  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();  
    if(orderInfo.getCustomerId() != customerId) {  
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);  
    }  
    return driverInfoFeignClient.getDriverInfo(orderInfo.getDriverId()).getData();  
}

乘客端获取司机经纬度位置

  • 乘客查看司机位置

地图微服务接口service-map

LocationController

@Operation(summary = "司机赶往代驾起始点:获取订单经纬度位置")  
@GetMapping("/getCacheOrderLocation/{orderId}")  
public Result<OrderLocationVo> getCacheOrderLocation(@PathVariable Long orderId) { 
    return Result.ok(locationService.getCacheOrderLocation(orderId));  
}

service

// 司机赶往代驾起始点:获取订单经纬度位置  
@Override  
public OrderLocationVo getCacheOrderLocation(Long orderId) {  
    String key = RedisConstant.UPDATE_ORDER_LOCATION + orderId;  
    OrderLocationVo orderLocationVo = (OrderLocationVo)redisTemplate.opsForValue().get(key);  
    return orderLocationVo;  
}

远程调用

LocationFeignClient

/**  
 * 司机赶往代驾起始点:获取订单经纬度位置  
 * @param orderId  
 * @return  
 */  
@GetMapping("/map/location/getCacheOrderLocation/{orderId}")  
Result<OrderLocationVo> getCacheOrderLocation(@PathVariable("orderId") Long orderId);

乘客端web接口

OrderController

@Operation(summary = "司机赶往代驾起始点:获取订单经纬度位置")  
@GetMapping("/getCacheOrderLocation/{orderId}")  
public Result<OrderLocationVo> getCacheOrderLocation(@PathVariable Long orderId) {  
    return Result.ok(orderService.getCacheOrderLocation(orderId));  
}

service

// 司机赶往代驾起始点:获取订单经纬度位置  
@Override  
public OrderLocationVo getCacheOrderLocation(Long orderId) {  
    return locationFeignClient.getCacheOrderLocation(orderId).getData();  
}

乘客端同显

乘客端web接口

OrderController

@Operation(summary = "计算最佳驾驶线路")  
@LoginDetection  
@PostMapping("/calculateDrivingLine")  
public Result<DrivingLineVo> calculateDrivingLine(@RequestBody CalculateDrivingLineForm calculateDrivingLineForm) {  
    return Result.ok(orderService.calculateDrivingLine(calculateDrivingLineForm));  
}

service

// 计算最佳驾驶线路  
@Override  
public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {  
    return mapFeignClient.calculateDrivingLine(calculateDrivingLineForm).getData();  
}

司机到达起始点

  • 司机到达起始点后更新订单数据
  • 更新订单状态:司机已到达
  • 更新订单到达时间

订单微服务接口service-order

OrderInfoController

@Operation(summary = "司机到达起始点")  
@GetMapping("/driverArriveStartLocation/{orderId}/{driverId}")  
public Result<Boolean> driverArriveStartLocation(@PathVariable Long orderId, @PathVariable Long driverId) {  
    return Result.ok(orderInfoService.driverArriveStartLocation(orderId, driverId));  
}

service

//司机到达起始点  
@Override  
public Boolean driverArriveStartLocation(Long orderId, Long driverId) {  
    // 更新订单状态和到达时间,条件:orderId + driverId  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getId,orderId);  
    wrapper.eq(OrderInfo::getDriverId,driverId);  
  
    OrderInfo orderInfo = new OrderInfo();  
    orderInfo.setStatus(OrderStatus.DRIVER_ARRIVED.getStatus());  
    orderInfo.setArriveTime(new Date());  
  
    int rows = orderInfoMapper.update(orderInfo, wrapper);  
  
    if(rows == 1) {  
        return true;  
    } else {  
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);  
    }  
}

远程调用service-order-client

OrderInfoFeignClient

/**
 * 司机到达起始点
 * @param orderId
 * @param driverId
 * @return
 */
@GetMapping("/order/info/driverArriveStartLocation/{orderId}/{driverId}")
Result<Boolean> driverArriveStartLocation(@PathVariable("orderId") Long orderId, @PathVariable("driverId") Long driverId);

司机web调用

OrderController

@Operation(summary = "司机到达代驾起始地点")  
@LoginDetection  
@GetMapping("/driverArriveStartLocation/{orderId}")  
public Result<Boolean> driverArriveStartLocation(@PathVariable Long orderId) {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.driverArriveStartLocation(orderId, driverId));  
}
// 司机到达代驾起始地点  
@Override  
public Boolean driverArriveStartLocation(Long orderId, Long driverId) {  
    return orderInfoFeignClient.driverArriveStartLocation(orderId, driverId).getData();  
}

司机更新代驾车辆信息

订单微服务接口service-order

OrderInfoController

@Operation(summary = "更新代驾车辆信息")  
@PostMapping("/updateOrderCart")  
public Result<Boolean> updateOrderCart(@RequestBody UpdateOrderCartForm updateOrderCartForm) {  
    return Result.ok(orderInfoService.updateOrderCart(updateOrderCartForm));  
}

service

// 更新代驾车辆信息  
@Override  
public Boolean updateOrderCart(UpdateOrderCartForm updateOrderCartForm) {  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getId,updateOrderCartForm.getOrderId());  
    wrapper.eq(OrderInfo::getDriverId,updateOrderCartForm.getDriverId());  
  
    OrderInfo orderInfo = new OrderInfo();  
    BeanUtils.copyProperties(updateOrderCartForm,orderInfo);  
    orderInfo.setStatus(OrderStatus.UPDATE_CART_INFO.getStatus());  
  
    int rows = orderInfoMapper.update(orderInfo, wrapper);  
    if(rows == 1) {  
        return true;  
    } else {  
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);  
    }  
}

远程调用

OrderInfoFeignClient

/**  
 * 更新代驾车辆信息  
 * @param updateOrderCartForm  
 * @return  
 */  
@PostMapping("/order/info//updateOrderCart")  
Result<Boolean> updateOrderCart(@RequestBody UpdateOrderCartForm updateOrderCartForm);

司机web端接口

OrderController

@Operation(summary = "更新代驾车辆信息")  
@LoginDetection  
@PostMapping("/updateOrderCart")  
public Result<Boolean> updateOrderCart(@RequestBody UpdateOrderCartForm updateOrderCartForm) {  
    Long driverId = AuthContextHolder.getUserId();  
    updateOrderCartForm.setDriverId(driverId);  
    return Result.ok(orderService.updateOrderCart(updateOrderCartForm));  
}

service

// 更新代驾车辆信息  
@Override  
public Boolean updateOrderCart(UpdateOrderCartForm updateOrderCartForm) {  
    return orderInfoFeignClient.updateOrderCart(updateOrderCartForm).getData();  
}

测试

  • 启动项目:

    • XxlJobAdminApplication
    • ServerGatewayApplication :8600/
    • ServiceCustomerApplication :8501/
    • ServiceDispatchApplication :8509/
    • ServiceDriverApplication :8502/
    • ServiceMapApplication :8503/
    • ServiceOrderApplication:8505/
    • ServiceRulesApplication :8504/
    • WebCustomerApplication :8601/
    • WebDriverApplication :8602/
  • 清除之前的数据

  • 启动微信开发者工具

修改前面的代码web-driver

FileController

@Autowired  
private CosService cosService;  
  
//文件上传接口  
@Operation(summary = "上传")  
//@LoginDetection  
@PostMapping("/upload")  
public Result<String> upload(@RequestPart("file") MultipartFile file,  
                                  @RequestParam(name = "path",defaultValue = "auth") String path) {  
    CosUploadVo cosUploadVo = cosService.uploadFile(file,path);  
    String showUrl = cosUploadVo.getShowUrl();  
    return Result.ok(showUrl);  
}

订单执行(二)

开始服务

  • 更新订单状态

订单微服务接口

OrderInfoController

@Operation(summary = "开始服务")  
@PostMapping("/startDrive")  
public Result<Boolean> startDriver(@RequestBody StartDriveForm startDriveForm) {  
    Boolean flag = orderInfoService.startDriver(startDriveForm);  
    return Result.ok(flag);  
}

service

//开始代驾服务  
@Override  
public Boolean startDriver(StartDriveForm startDriveForm) {  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getId,startDriveForm.getOrderId());  
    wrapper.eq(OrderInfo::getDriverId,startDriveForm.getDriverId());  
  
    OrderInfo orderInfo = new OrderInfo();  
    orderInfo.setStatus(OrderStatus.START_SERVICE.getStatus());  
    orderInfo.setStartServiceTime(new Date());  
  
    int rows = orderInfoMapper.update(orderInfo, wrapper);  
    if(rows == 1) {  
        return true;  
    } else {  
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);  
    }  
}

远程调用

OrderInfoFeignClient

/**  
 * 开始代驾服务  
 * @param startDriveForm  
 * @return  
 */  
@PostMapping("/order/info/startDrive")  
Result<Boolean> startDrive(@RequestBody StartDriveForm startDriveForm);

司机web端调用

OrderController

@Operation(summary = "开始代驾服务")  
@LoginDetection  
@PostMapping("/startDrive")  
public Result<Boolean> startDrive(@RequestBody StartDriveForm startDriveForm) {  
    Long driverId = AuthContextHolder.getUserId();  
    startDriveForm.setDriverId(driverId);  
    return Result.ok(orderService.startDrive(startDriveForm));  
}

service

// 开始代驾服务  
@Override  
public Boolean startDrive(StartDriveForm startDriveForm) {  
    return orderInfoFeignClient.startDrive(startDriveForm).getData();  
}

批量保存订单位置信息

  • 开始服务后,司机端会实时收集司机位置
  • 定时批量上传位置到后台服务保存到MongoDB

地图微服务接口

LocationController

@Operation(summary = "批量保存代驾服务订单位置")  
@PostMapping("/saveOrderServiceLocation")  
public Result<Boolean> saveOrderServiceLocation(@RequestBody List<OrderServiceLocationForm> orderLocationServiceFormList) {  
    return Result.ok(locationService.saveOrderServiceLocation(orderLocationServiceFormList));  
}

编写接口

@Repository  
public interface OrderServiceLocationRepository extends MongoRepository<OrderServiceLocation, String> {  
}

导入依赖

<!--mongodb-->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-mongodb</artifactId>  
</dependency>

service

@Autowired  
private OrderServiceLocationRepository orderServiceLocationRepository;

@Override  
public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {  
    List<OrderServiceLocation> list = new ArrayList<>();  
    // 遍历,变成OrderServiceLocation  
    orderLocationServiceFormList.forEach(orderServiceLocationForm->{  
        //orderServiceLocationForm -- OrderServiceLocation  
        OrderServiceLocation orderServiceLocation = new OrderServiceLocation();  
        BeanUtils.copyProperties(orderServiceLocationForm,orderServiceLocation);  
        orderServiceLocation.setId(ObjectId.get().toString());  
        orderServiceLocation.setCreateTime(new Date());  
  
        list.add(orderServiceLocation);  
    });  
    //批量添加到MongoDB  
    orderServiceLocationRepository.saveAll(list);  
    return true;  
}

远程调用

LocationFeignClient

/**  
 * 开始代驾服务:保存代驾服务订单位置  
 * @param orderLocationServiceFormList  
 * @return  
 */  
@PostMapping("/map/location/saveOrderServiceLocation")  
Result<Boolean> saveOrderServiceLocation(@RequestBody List<OrderServiceLocationForm> orderLocationServiceFormList);

司机web端调用

LocationController

@Operation(summary = "开始代驾服务:保存代驾服务订单位置")  
@PostMapping("/saveOrderServiceLocation")  
public Result<Boolean> saveOrderServiceLocation(@RequestBody List<OrderServiceLocationForm> orderLocationServiceFormList) {  
    return Result.ok(locationService.saveOrderServiceLocation(orderLocationServiceFormList));  
}

service

// 始代驾服务:保存代驾服务订单位置  
@Override  
public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {  
    return locationFeignClient.saveOrderServiceLocation(orderLocationServiceFormList).getData();  
}

获取订单最后一个位置

  • 司机开始服务后,乘客端获取司机的最新动向

地图微服务接口

LocationController

@Operation(summary = "代驾服务:获取订单服务最后一个位置信息")  
@GetMapping("/getOrderServiceLastLocation/{orderId}")  
public Result<OrderServiceLastLocationVo> getOrderServiceLastLocation(@PathVariable Long orderId) {  
    return Result.ok(locationService.getOrderServiceLastLocation(orderId));  
}

service

@Autowired  
private MongoTemplate mongoTemplate;

// 代驾服务:获取订单服务最后一个位置信息  
@Override  
public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {  
    //查询MongoDB,查询条件 :orderId  
    Query query = new Query();  
    query.addCriteria(Criteria.where("orderId").is(orderId));  
    //根据创建时间降序排列  
    query.with(Sort.by(Sort.Order.desc("createTime")));  
    //只取一条数据  
    query.limit(1);  
  
    OrderServiceLocation orderServiceLocation =  
            mongoTemplate.findOne(query, OrderServiceLocation.class);  
    OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();  
    BeanUtils.copyProperties(orderServiceLocation,orderServiceLastLocationVo);  
    return orderServiceLastLocationVo;  
}

远程调用

LocationFeignClient

/**  
 * 代驾服务:获取订单服务最后一个位置信息  
 * @param orderId  
 * @return  
 */  
@GetMapping("/map/location/getOrderServiceLastLocation/{orderId}")  
Result<OrderServiceLastLocationVo> getOrderServiceLastLocation(@PathVariable Long orderId);

乘客web端调用

OrderController

@Operation(summary = "代驾服务:获取订单服务最后一个位置信息")  
@LoginDetection  
@GetMapping("/getOrderServiceLastLocation/{orderId}")  
public Result<OrderServiceLastLocationVo> getOrderServiceLastLocation(@PathVariable Long orderId) {  
    return Result.ok(orderService.getOrderServiceLastLocation(orderId));  
}

service

// 代驾服务:获取订单服务最后一个位置信息  
@Override  
public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {  
    return locationFeignClient.getOrderServiceLastLocation(orderId).getData();  
}

Minio上传接口

  • 司机服务过程中,司机端小程序实时采集录音,把录音和对话文本上传到后台服务,把完整监控保存到Minio

Minio安装

使用docker安装

// 创建数据存储目录
mkdir -p ~/minio/data

// 创建minio
docker run \
   -p 9000:9000 \
   -p 9090:9090 \
   --name minio \
   -v ~/minio/data:/data \
   -e "MINIO_ROOT_USER=admin" \
   -e "MINIO_ROOT_PASSWORD=admin123456" \
   -d \
   quay.io/minio/minio server /data --console-address ":9090"

2 安装到windows里面

  • 找到minio安装文件,放到没有中文没有空格目录

  • 创建空文件夹,作为数据存储目录

  • 在windows使用命令启动Minio服务

– minio.exe server D:\Desktop\hhsqdmz\sort\minio\data

Minio启动

  • 访问Minio控制台:ip:9000

  • 默认控制台用户名和密码都是:minioadmin

  • 在minio控制台里创建buckets

  • 创建buckets后记得把Access Policy设为public

司机端web接口

导入依赖

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-mongodb</artifactId>  
</dependency>

修改配置文件common-account.yaml

minio:
  endpointUrl: http://localhost:9000
  accessKey: minioadmin
  secreKey: minioadmin
  bucketName: daijia

创建配置类,读取minio的值

@Configuration  
@ConfigurationProperties(prefix="minio") //读取节点  
@Data  
public class MinioProperties {  
  
    private String endpointUrl;  
    private String accessKey;  
    private String secreKey;  
    private String bucketName;  
}

FileController

@Autowired  
private FileService fileService;

@Operation(summary = "上传")  
@PostMapping("/upload")  
public Result<String> upload(@RequestPart("file") MultipartFile file) {  
    String url = fileService.upload(file);  
    return Result.ok(url);  
}

service

@Autowired  
private MinioProperties minioProperties;  
  
// 上传  
@Override  
public String upload(MultipartFile file) {  
    try {  
        // 创建一个Minio的客户端对象  
        MinioClient minioClient = MinioClient.builder()  
                .endpoint(minioProperties.getEndpointUrl())  
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecreKey())  
                .build();  
  
        // 判断桶是否存在  
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());  
        if (!found) {       // 如果不存在,那么此时就创建一个新的桶  
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());  
        } else {  // 如果存在打印信息  
            System.out.println("Bucket 'daijia' already exists.");  
        }  
  
        // 设置存储对象名称  
        String extFileName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));  
        String fileName = new SimpleDateFormat("yyyyMMdd")  
                .format(new Date()) + "/" + UUID.randomUUID().toString().replace("-" , "") + "." + extFileName;  
  
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()  
                .bucket(minioProperties.getBucketName())  
                .stream(file.getInputStream(), file.getSize(), -1)  
                .object(fileName)  
                .build();  
        minioClient.putObject(putObjectArgs) ;  
  
        return minioProperties.getEndpointUrl() + "/" + minioProperties.getBucketName() + "/" + fileName ;  
  
    } catch (Exception e) {  
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);  
    }  
}

保存订单监控记录数据

  • 订单执行过程中,记录对话信息
  • 在前端小程序,同声传译,把录音转换文本,保存文本内存

订单微服务接口.

导入依赖

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-mongodb</artifactId>  
</dependency>

添加MongoRepository

@Repository  
public interface OrderMonitorRecordRepository extends MongoRepository<OrderMonitorRecord, String> {  
}

OrderMonitorController

@Autowired  
private OrderMonitorService orderMonitorService;  
  
@Operation(summary = "保存订单监控记录数据")  
@PostMapping("/saveOrderMonitorRecord")  
public Result<Boolean> saveMonitorRecord(@RequestBody OrderMonitorRecord orderMonitorRecord) {  
    return Result.ok(orderMonitorService.saveOrderMonitorRecord(orderMonitorRecord));  
}

service

@Autowired  
private OrderMonitorRecordRepository orderMonitorRecordRepository;  
  
// 保存订单监控记录数据  
@Override  
public Boolean saveOrderMonitorRecord(OrderMonitorRecord orderMonitorRecord) {  
    orderMonitorRecordRepository.save(orderMonitorRecord);  
    return true;  
}

远程调用

OrderMonitorFeignClient

/**  
 * 保存订单监控记录数据  
 * @param orderMonitorRecord  
 * @return  
 */  
@PostMapping("/order/monitor/saveOrderMonitorRecord")  
Result<Boolean> saveMonitorRecord(@RequestBody OrderMonitorRecord orderMonitorRecord);

司机端web调用

MonitorController

@Autowired  
private MonitorService monitorService;  
  
@Operation(summary = "上传录音")  
@PostMapping("/upload")  
public Result<Boolean> upload(@RequestParam("file") MultipartFile file,  
                              OrderMonitorForm orderMonitorForm) {  
  
    return Result.ok(monitorService.upload(file, orderMonitorForm));  
}

service

@Autowired  
private FileService fileService;  
  
@Autowired  
private OrderMonitorFeignClient orderMonitorFeignClient;  
  
// 上传录音  
@Override  
public Boolean upload(MultipartFile file, OrderMonitorForm orderMonitorForm) {  
    //上传文件  
    String url = fileService.upload(file);  
  
    OrderMonitorRecord orderMonitorRecord = new OrderMonitorRecord();  
    orderMonitorRecord.setOrderId(orderMonitorForm.getOrderId());  
    orderMonitorRecord.setFileUrl(url);  
    orderMonitorRecord.setContent(orderMonitorForm.getContent());  
    orderMonitorFeignClient.saveMonitorRecord(orderMonitorRecord);  
  
    return true;  
}

订单监控审核

使用腾讯云数据万象实现自动审核

  • 官方网址:https://cloud.tencent.com/product/ci

腾讯云COS图片审核

封装图片审核方法

在service-driver的CiService

Boolean imageAuditing(String path);

CiServiceImpl

@Service  
public class CiServiceImpl implements CiService {  
  
    @Autowired  
    private TencentCloudProperties tencentCloudProperties;  
  
    //图片审核  
    @Override  
    public Boolean imageAuditing(String path) {  
  
        //1.创建任务请求对象  
        ImageAuditingRequest request = new ImageAuditingRequest();  
        //2.添加请求参数 参数详情请见 API 接口文档  
        //2.1设置请求 bucket        request.setBucketName(tencentCloudProperties.getBucketPrivate());  
        //2.2设置审核策略 不传则为默认策略(预设)  
        //request.setBizType("");  
        //2.3设置 bucket 中的图片位置  
        request.setObjectKey(path);  
        //3.调用接口,获取任务响应对象  
        COSClient client = this.getCosClient();  
        ImageAuditingResponse response = client.imageAuditing(request);  
        client.shutdown();  
        //用于返回该审核场景的审核结果,返回值:0:正常。1:确认为当前场景的违规内容。2:疑似为当前场景的违规内容。  
        if (!response.getPornInfo().getHitFlag().equals("0")  
                || !response.getAdsInfo().getHitFlag().equals("0")  
                || !response.getTerroristInfo().getHitFlag().equals("0")  
                || !response.getPoliticsInfo().getHitFlag().equals("0")  
        ) {  
            return false;  
        }  
        return true;  
    }  
  
    public COSClient getCosClient() {  
        String secretId = tencentCloudProperties.getSecretId();  
        String secretKey = tencentCloudProperties.getSecretKey();  
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);  
        // 2 设置 bucket 的地域, COS 地域  
        Region region = new Region(tencentCloudProperties.getRegion());  
        ClientConfig clientConfig = new ClientConfig(region);  
        // 这里建议设置使用 https 协议  
        clientConfig.setHttpProtocol(HttpProtocol.https);  
        // 3 生成 cos 客户端。  
        COSClient cosClient = new COSClient(cred, clientConfig);  
        return cosClient;  
    }  
}

腾讯云COS图片添加审核

CosServiceImpl

@Autowired  
private CiService ciService;  
  
@Override  
public CosUploadVo upload(MultipartFile file, String path) {  
    //获取cosClient对象  
    COSClient cosClient = this.getCosClient();  
    //文件上传  
    //元数据信息  
    ObjectMetadata meta = new ObjectMetadata();  
    meta.setContentLength(file.getSize());  
    meta.setContentEncoding("UTF-8");  
    meta.setContentType(file.getContentType());  
  
    //向存储桶中保存文件  
    String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); //文件后缀名  
    String uploadPath = "/driver/" + path + "/" + UUID.randomUUID().toString().replaceAll("-", "") + fileType;  
    // 01.jpg  
    // /driver/auth/0o98754.jpg    PutObjectRequest putObjectRequest = null;  
    try {  
        //1 bucket名称  
        //2  
        putObjectRequest = new PutObjectRequest(tencentCloudProperties.getBucketPrivate(),  
                uploadPath,  
                file.getInputStream(),  
                meta);  
    } catch (IOException e) {  
        throw new RuntimeException(e);  
    }  
    putObjectRequest.setStorageClass(StorageClass.Standard);  
    PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); //上传文件  
    cosClient.shutdown();  
  
    // 图片审核  
    Boolean imageAuditing = ciService.imageAuditing(uploadPath);  
    if(!imageAuditing){  
        cosClient.deleteObject(tencentCloudProperties.getBucketPrivate(), uploadPath);  
        throw new GuiguException(ResultCodeEnum.IMAGE_AUDITION_FAIL);  
    }  
  
    //返回vo对象  
    CosUploadVo cosUploadVo = new CosUploadVo();  
    cosUploadVo.setUrl(uploadPath);  
    //图片临时访问url,回显使用  
    String imageUrl = this.getImageUrl(uploadPath);  
    cosUploadVo.setShowUrl(imageUrl);  
    return cosUploadVo;  
}

封装文本审核接口

司机微服务接口

CiController

@Autowired  
private CiService ciService;  
  
@Operation(summary = "文本审核")  
@PostMapping("/textAuditing")  
public Result<TextAuditingVo> textAuditing(@RequestBody String content) {  
    return Result.ok(ciService.textAuditing(content));  
}

service

// 文本审核  
@Override  
public TextAuditingVo textAuditing(String content) {  
    if(!StringUtils.hasText(content)) {  
        TextAuditingVo textAuditingVo = new TextAuditingVo();  
        textAuditingVo.setResult("0");  
        return textAuditingVo;  
    }  
  
    COSClient cosClient = this.getCosClient();  
  
    //1.创建任务请求对象  
    TextAuditingRequest request = new TextAuditingRequest();  
    //2.添加请求参数 参数详情请见 API 接口文档  
    request.setBucketName(tencentCloudProperties.getBucketPrivate());  
    //2.1.1设置请求内容,文本内容的Base64编码  
    byte[] encoder = org.apache.commons.codec.binary.Base64.encodeBase64(content.getBytes());  
    String contentBase64 = new String(encoder);  
    request.getInput().setContent(contentBase64);  
    request.getConf().setDetectType("all");  
  
    //3.调用接口,获取任务响应对象  
    TextAuditingResponse response = cosClient.createAuditingTextJobs(request);  
    AuditingJobsDetail detail = response.getJobsDetail();  
    TextAuditingVo textAuditingVo = new TextAuditingVo();  
    if ("Success".equals(detail.getState())) {  
        //检测结果: 0(审核正常),1 (判定为违规敏感文件),2(疑似敏感,建议人工复核)。  
        String result = detail.getResult();  
  
        //违规关键词  
        StringBuffer keywords = new StringBuffer();  
        List<SectionInfo> sectionInfoList = detail.getSectionList();  
        for (SectionInfo info : sectionInfoList) {  
  
            String pornInfoKeyword = info.getPornInfo().getKeywords();  
            String illegalInfoKeyword = info.getIllegalInfo().getKeywords();  
            String abuseInfoKeyword = info.getAbuseInfo().getKeywords();  
  
            if (pornInfoKeyword.length() > 0) {  
                keywords.append(pornInfoKeyword).append(",");  
            }  
            if (illegalInfoKeyword.length() > 0) {  
                keywords.append(illegalInfoKeyword).append(",");  
            }  
            if (abuseInfoKeyword.length() > 0) {  
                keywords.append(abuseInfoKeyword).append(",");  
            }  
        }  
        textAuditingVo.setResult(result);  
        textAuditingVo.setKeywords(keywords.toString());  
    }  
    return textAuditingVo;  
}

远程调用

CiFeignClient

/**  
 * 文本审核  
 * @param content  
 * @return  
 */  
@PostMapping("/ci/textAuditing")  
Result<TextAuditingVo> textAuditing(@RequestBody String content);

订单监控接口完善

修改web-driver的MonitorServiceImpl

@Autowired  
private FileService fileService;  
  
@Autowired  
private OrderMonitorFeignClient orderMonitorFeignClient;  
  
@Autowired  
private CiFeignClient ciFeignClient;  
  
// 上传录音  
@Override  
public Boolean upload(MultipartFile file, OrderMonitorForm orderMonitorForm) {  
    //上传文件  
    String url = fileService.upload(file);  
  
    OrderMonitorRecord orderMonitorRecord = new OrderMonitorRecord();  
    orderMonitorRecord.setOrderId(orderMonitorForm.getOrderId());  
    orderMonitorRecord.setFileUrl(url);  
    orderMonitorRecord.setContent(orderMonitorForm.getContent());  
  
    // 文本审核  
    TextAuditingVo textAuditingVo = ciFeignClient.textAuditing(orderMonitorForm.getContent()).getData();  
    orderMonitorRecord.setResult(textAuditingVo.getResult());  
    orderMonitorRecord.setKeywords(textAuditingVo.getKeywords());  
  
    orderMonitorFeignClient.saveMonitorRecord(orderMonitorRecord);  
  
    return true;  
}

订单执行(三)

计算订单实际里程

  • 在MongoDB保存订单过程中司机位置信息,把MongoDB存储司机位置信息获取出来,以时间排序,连接每个点,成为实际距离

准备计算距离工具类

common-util中的LocationUtil

public static double getDistance(double lat1, double lng1, double lat2,  
                                 double lng2) {  
    double radLat1 = rad(lat1);  
    double radLat2 = rad(lat2);  
    double a = radLat1 - radLat2;  
    double b = rad(lng1) - rad(lng2);  
    double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)  
            + Math.cos(radLat1) * Math.cos(radLat2)  
            * Math.pow(Math.sin(b / 2), 2)));  
    s = s * EARTH_RADIUS;  
    s = Math.round(s * 10000d) / 10000d;  
    s = s * 1000;  
    return s;  
}

地图微服务接口

LocationController

@Operation(summary = "代驾服务:计算订单实际里程")  
@GetMapping("/calculateOrderRealDistance/{orderId}")  
public Result<BigDecimal> calculateOrderRealDistance(@PathVariable Long orderId) {  
    return Result.ok(locationService.calculateOrderRealDistance(orderId));  
}

service

// 代驾服务:计算订单实际里程  
@Override  
public BigDecimal calculateOrderRealDistance(Long orderId) {  
    // 根据订单id获取位置信息,按照创建时间排序  
    List<OrderServiceLocation> list =  
            orderServiceLocationRepository.findByOrderIdOrderByCreateTimeAsc(orderId);  
  
    // 第一步查询返回订单位置信息list集合  
    // 把list集合遍历,得到每个位置信息,计算两个位置距离,把计算所有距离相加操作  
    double realDistance = 0;  
    if(!CollectionUtils.isEmpty(list)) {  
        for (int i = 0,size = list.size()-1; i < size; i++) {  
            OrderServiceLocation location1 = list.get(i);  
            OrderServiceLocation location2 = list.get(i + 1);  
  
            //计算位置距离  
            double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(),  
                    location1.getLongitude().doubleValue(),  
                    location2.getLatitude().doubleValue(),  
                    location2.getLongitude().doubleValue());  
  
            realDistance += distance;  
        }  
    }  
      
    return new BigDecimal(realDistance);  
}

远程调用

LocationFeignClient

/**
 * 代驾服务:计算订单实际里程
 * @param orderId
 * @return
 */
@GetMapping("/map/location/calculateOrderRealDistance/{orderId}")
Result<BigDecimal> calculateOrderRealDistance(@PathVariable Long orderId);

计算系统奖励

创建规则文件

//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问  
package  com.atguigu.daijia  
  
import com.atguigu.daijia.model.form.rules.RewardRuleRequest;  
import java.math.BigDecimal;  
import java.math.RoundingMode;  
  
global com.atguigu.daijia.model.vo.rules.RewardRuleResponse rewardRuleResponse;  
  
/**  
系统奖励  
    00:00:00-06:59:59  完成5单后 奖励5元  
    07:00:00-23:59:59  完成10单后 奖励2元  
*/  
rule "00:00:00-06:59:59  完成5单后 每单奖励5元"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:RewardRuleRequest(startTime >= "00:00:00" && startTime <= "06:59:59" && orderNum > 5)  
    then  
        rewardRuleResponse.setRewardAmount(new BigDecimal("5.0"));  
        System.out.println("00:00:00-06:59:59 奖励:" + rewardRuleResponse.getRewardAmount() + "元");  
end  
rule "07:00:00-23:59:59  完成10单后 每单奖励2元"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:RewardRuleRequest(startTime >= "07:00:00" && startTime <= "23:59:59" && orderNum > 10)  
    then  
        rewardRuleResponse.setRewardAmount(new BigDecimal("2.0"));  
        System.out.println("00:00:00-06:59:59 奖励:" + rewardRuleResponse.getRewardAmount() + "元");  
end

添加规则引擎工具类

在service-rules中utils包下创建

public class DroolsHelper {  
  
    private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";  
  
    public static KieSession loadForRule(String drlStr) {  
        KieServices kieServices = KieServices.Factory.get();  
  
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();  
        kieFileSystem.write(  
                ResourceFactory.newClassPathResource(drlStr));  
  
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);  
        kb.buildAll();  
  
        KieModule kieModule = kb.getKieModule();  
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());  
        return kieContainer.newKieSession();  
    }  
}

规则微服务接口

RewardRuleController

@Autowired  
private RewardRuleService rewardRuleService;  
  
@Operation(summary = "计算订单奖励费用")  
@PostMapping("/calculateOrderRewardFee")  
public Result<RewardRuleResponseVo>  
calculateOrderRewardFee(@RequestBody RewardRuleRequestForm rewardRuleRequestForm) {  
    return Result.ok(rewardRuleService.calculateOrderRewardFee(rewardRuleRequestForm));  
}

service

// 计算订单奖励费用  
@Override  
public RewardRuleResponseVo calculateOrderRewardFee(RewardRuleRequestForm rewardRuleRequestForm) {  
    //封装传入参数对象  
    RewardRuleRequest rewardRuleRequest = new RewardRuleRequest();  
    rewardRuleRequest.setOrderNum(rewardRuleRequestForm.getOrderNum());  
  
    //创建规则引擎对象  
    KieSession kieSession = DroolsHelper.loadForRule(RULES_CUSTOMER_RULES_DRL);  
  
    //封装返回对象  
    RewardRuleResponse rewardRuleResponse = new RewardRuleResponse();  
    kieSession.setGlobal("rewardRuleResponse",rewardRuleResponse);  
  
    //设置对象,触发规则  
    kieSession.insert(rewardRuleRequest);  
    kieSession.fireAllRules();  
  
    //终止会话  
    kieSession.dispose();  
  
    //封装RewardRuleResponseVo  
    RewardRuleResponseVo rewardRuleResponseVo = new RewardRuleResponseVo();  
    rewardRuleResponseVo.setRewardAmount(rewardRuleResponse.getRewardAmount());  
    return rewardRuleResponseVo;  
}

远程调用

RewardRuleFeignClient

/**
 * 计算订单奖励费用
 * @param rewardRuleRequestForm
 * @return
 */
@PostMapping("/rules/reward/calculateOrderRewardFee")
Result<RewardRuleResponseVo> calculateOrderRewardFee(@RequestBody RewardRuleRequestForm rewardRuleRequestForm);

根据时间段获取订单数量

订单微服务接口

OrderInfoController

@Operation(summary = "根据时间段获取订单数")  
@GetMapping("/getOrderNumByTime/{startTime}/{endTime}")  
public Result<Long> getOrderNumByTime(@PathVariable String startTime, @PathVariable String endTime) {  
    return Result.ok(orderInfoService.getOrderNumByTime(startTime, endTime));  
}

service

// 根据时间段获取订单数  
@Override  
public Long getOrderNumByTime(String startTime, String endTime) {  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.ge(OrderInfo::getStartServiceTime,startTime);  
    wrapper.lt(OrderInfo::getStartServiceTime,endTime);  
    Long count = orderInfoMapper.selectCount(wrapper);  
    return count;  
}

远程调用

OrderInfoFeignClient

/**  
 *  根据时间段获取订单数  
 * @param startTime  
 * @param endTime  
 * @return  
 */  
@GetMapping("/order/info/getOrderNumByTime/{startTime}/{endTime}")  
Result<Long> getOrderNumByTime(@PathVariable("startTime") String startTime, @PathVariable("endTime") String endTime);

计算分账信息

创建规则文件

//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问  
package  com.atguigu.daijia  
  
import com.atguigu.daijia.model.form.rules.ProfitsharingRuleRequest;  
import java.math.BigDecimal;  
import java.math.RoundingMode;  
  
global com.atguigu.daijia.model.vo.rules.ProfitsharingRuleResponse profitsharingRuleResponse;  
//支付微信平台费率:0.6%  
//global BigDecimal paymentRate = new BigDecimal(0.006);  
/**  
支付微信平台费用  
    平台费率:0.6%  
*/  
rule "支付微信平台费用 平台费率:0.6%"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:ProfitsharingRuleRequest()  
    then  
        profitsharingRuleResponse.setOrderAmount($rule.getOrderAmount());  
        profitsharingRuleResponse.setPaymentRate(new BigDecimal("0.006"));  
        BigDecimal paymentFee = profitsharingRuleResponse.getOrderAmount().multiply(profitsharingRuleResponse.getPaymentRate()).setScale(2, RoundingMode.HALF_UP);  
        profitsharingRuleResponse.setPaymentFee(paymentFee);  
        System.out.println("支付微信平台费用:" + profitsharingRuleResponse.getPaymentFee() + "元");  
end  
  
/**  
订单金额小于等于100  
    当天完成订单小于等于10单 平台抽成 20%  
    当天完成订单大于10单 平台抽成 18%  
*/  
rule "订单金额小于等于100 当天完成订单小于等于10单"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:ProfitsharingRuleRequest(orderAmount.doubleValue() <= 100.0 && orderNum <= 10)  
    then  
        BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());  
        BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.2")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);  
        //代驾司机个税,税率:10%  
        BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);  
        profitsharingRuleResponse.setPlatformIncome(platformIncome);  
        profitsharingRuleResponse.setDriverIncome(driverIncome);  
        profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));  
        profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);  
        System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");  
end  
rule "订单金额小于等于100 天完成订单大于10单"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:ProfitsharingRuleRequest(orderAmount.doubleValue() <= 100.0 && orderNum > 10)  
    then  
        BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());  
        BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.18")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);  
        //代驾司机个税,税率:10%  
        BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);  
        profitsharingRuleResponse.setPlatformIncome(platformIncome);  
        profitsharingRuleResponse.setDriverIncome(driverIncome);  
        profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));  
        profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);  
        System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");  
end  
  
/**  
订单金额大于100  
    当天完成订单小于等于10单 平台抽成 18%  
    当天完成订单大于10单 平台抽成 16%  
*/  
rule "订单金额大于100 当天完成订单小于等于10单"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:ProfitsharingRuleRequest(orderAmount.doubleValue() > 100.0 && orderNum <= 10)  
    then  
        BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());  
        BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.18")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);  
        //代驾司机个税,税率:10%  
        BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);  
        profitsharingRuleResponse.setPlatformIncome(platformIncome);  
        profitsharingRuleResponse.setDriverIncome(driverIncome);  
        profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));  
        profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);  
        System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");  
end  
rule "订单金额大于100 天完成订单大于10单"  
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行  
    no-loop true         //防止陷入死循环  
    when  
        /*规则条件,到工作内存中查找FeeRuleRequest对象  
        里面出来的结果只能是ture或者false  
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/  
        $rule:ProfitsharingRuleRequest(orderAmount.doubleValue() > 100.0 && orderNum > 10)  
    then  
        BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());  
        BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.18")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);  
        //代驾司机个税,税率:10%  
        BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);  
        BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);  
        profitsharingRuleResponse.setPlatformIncome(platformIncome);  
        profitsharingRuleResponse.setDriverIncome(driverIncome);  
        profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));  
        profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);  
        System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");  
end

规则微服务接口

ProfitsharingRuleController

@Autowired  
private ProfitsharingRuleService profitsharingRuleService;  
  
@Operation(summary = "计算系统分账费用")  
@PostMapping("/calculateOrderProfitsharingFee")  
public Result<ProfitsharingRuleResponseVo> calculateOrderProfitsharingFee(@RequestBody ProfitsharingRuleRequestForm profitsharingRuleRequestForm) {  
    return Result.ok(profitsharingRuleService.calculateOrderProfitsharingFee(profitsharingRuleRequestForm));  
}

service

@Autowired  
private ProfitsharingRuleMapper rewardRuleMapper;  
  
private static final String RULES_CUSTOMER_RULES_DRL = "rules/ProfitsharingRule.drl";  
  
@Override  
public ProfitsharingRuleResponseVo calculateOrderProfitsharingFee(ProfitsharingRuleRequestForm profitsharingRuleRequestForm) {  
    //传入参数对象封装  
    ProfitsharingRuleRequest profitsharingRuleRequest = new ProfitsharingRuleRequest();  
    profitsharingRuleRequest.setOrderAmount(profitsharingRuleRequestForm.getOrderAmount());  
    profitsharingRuleRequest.setOrderNum(profitsharingRuleRequestForm.getOrderNum());  
  
    //创建kieSession  
    KieSession kieSession = DroolsHelper.loadForRule(RULES_CUSTOMER_RULES_DRL);  
  
    //封装返回对象  
    ProfitsharingRuleResponse profitsharingRuleResponse = new ProfitsharingRuleResponse();  
    kieSession.setGlobal("profitsharingRuleResponse",profitsharingRuleResponse);  
  
    //触发规则,返回vo对象  
    kieSession.insert(profitsharingRuleRequest);  
    kieSession.fireAllRules();  
    kieSession.dispose();  
  
    ProfitsharingRuleResponseVo profitsharingRuleResponseVo = new ProfitsharingRuleResponseVo();  
    BeanUtils.copyProperties(profitsharingRuleResponse,profitsharingRuleResponseVo);  
  
    return profitsharingRuleResponseVo;  
}

远程调用

ProfitsharingRuleFeignClient

/**  
 * 计算订单分账数据  
 * @param profitsharingRuleRequestForm  
 * @return  
 */  
@PostMapping("/rules/profitsharing/calculateOrderProfitsharingFee")  
Result<ProfitsharingRuleResponseVo> calculateOrderProfitsharingFee(@RequestBody ProfitsharingRuleRequestForm profitsharingRuleRequestForm);

结束服务更新订单

  • 更新订单数据:订单状态、订单实际距离、订单实际金额等
  • 添加实际账单信息
  • 添加分账信息

订单微服务接口

OrderInfoController

@Operation(summary = "结束代驾服务更新订单账单")  
@PostMapping("/endDrive")  
public Result<Boolean> endDrive(@RequestBody UpdateOrderBillForm updateOrderBillForm) {  
    return Result.ok(orderInfoService.endDrive(updateOrderBillForm));  
}

service

@Autowired  
private OrderBillMapper orderBillMapper;

@Autowired  
private OrderProfitsharingMapper orderProfitsharingMapper;

// 结束代驾服务更新订单账单  
@Override  
public Boolean endDrive(UpdateOrderBillForm updateOrderBillForm) {  
    // 更新订单信息  
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderInfo::getId,updateOrderBillForm.getOrderId());  
    wrapper.eq(OrderInfo::getDriverId,updateOrderBillForm.getDriverId());  
  
    OrderInfo orderInfo = new OrderInfo();  
    orderInfo.setStatus(OrderStatus.END_SERVICE.getStatus());  
    orderInfo.setRealAmount(updateOrderBillForm.getTotalAmount());  
    orderInfo.setFavourFee(updateOrderBillForm.getFavourFee());  
    orderInfo.setRealDistance(updateOrderBillForm.getRealDistance());  
    orderInfo.setEndServiceTime(new Date());  
  
    int rows = orderInfoMapper.update(orderInfo, wrapper);  
  
    if(rows == 1) {  
        //添加账单数据  
        OrderBill orderBill = new OrderBill();  
        BeanUtils.copyProperties(updateOrderBillForm,orderBill);  
        orderBill.setOrderId(updateOrderBillForm.getOrderId());  
        orderBill.setPayAmount(updateOrderBillForm.getTotalAmount());  
        orderBillMapper.insert(orderBill);  
  
        //添加分账信息  
        OrderProfitsharing orderProfitsharing = new OrderProfitsharing();  
        BeanUtils.copyProperties(updateOrderBillForm, orderProfitsharing);  
        orderProfitsharing.setOrderId(updateOrderBillForm.getOrderId());  
        orderProfitsharing.setRuleId(updateOrderBillForm.getProfitsharingRuleId());  
        orderProfitsharing.setStatus(1);  
        orderProfitsharingMapper.insert(orderProfitsharing);  
  
    } else {  
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);  
    }  
    return true;  
}

远程调用

OrderInfoFeignClient

/**  
 * 结束代驾服务更新订单账单  
 * @param updateOrderBillForm  
 * @return  
 */  
@PostMapping("/order/info/endDrive")  
Result<Boolean> endDrive(@RequestBody UpdateOrderBillForm updateOrderBillForm);

结束服务

web-driver中的OrderController

@Operation(summary = "结束代驾服务更新订单账单")  
@LoginDetection  
@PostMapping("/endDrive")  
public Result<Boolean> endDrive(@RequestBody OrderFeeForm orderFeeForm) {  
    Long driverId = AuthContextHolder.getUserId();  
    orderFeeForm.setDriverId(driverId);  
    return Result.ok(orderService.endDrive(orderFeeForm));  
}

service

@Autowired  
private LocationFeignClient locationFeignClient;  
  
@Autowired  
private FeeRuleFeignClient feeRuleFeignClient;  
  
@Autowired  
private RewardRuleFeignClient rewardRuleFeignClient;  
  
@Autowired  
private ProfitsharingRuleFeignClient profitsharingRuleFeignClient;

// 结束代驾服务更新订单账单  
@Override  
public Boolean endDrive(OrderFeeForm orderFeeForm) {  
    // 根据orderId获取订单信息,判断当前订单是否司机接单  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderFeeForm.getOrderId()).getData();  
    if(orderInfo.getDriverId() != orderFeeForm.getDriverId()) {  
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
    }  
  
    // 计算订单实际里程  
    BigDecimal realDistance =  
            locationFeignClient.calculateOrderRealDistance(orderFeeForm.getOrderId()).getData();  
  
    // 计算代驾实际费用  
    //封装FeeRuleRequestForm  
    FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();  
    feeRuleRequestForm.setDistance(realDistance);  
    feeRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());  
  
    //计算司机到达代驾开始位置时间  
    Integer waitMinute =  
            Math.abs((int)((orderInfo.getStartServiceTime().getTime()-orderInfo.getArriveTime().getTime())/(1000 * 60)));  
    feeRuleRequestForm.setWaitMinute(waitMinute);  
    //远程调用 代驾费用  
    FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(feeRuleRequestForm).getData();  
    //实际费用 = 代驾费用 + 其他费用(停车费)  
    BigDecimal totalAmount =  
            feeRuleResponseVo.getTotalAmount().add(orderFeeForm.getTollFee())  
                    .add(orderFeeForm.getParkingFee())  
                    .add(orderFeeForm.getOtherFee())  
                    .add(orderInfo.getFavourFee());  
    feeRuleResponseVo.setTotalAmount(totalAmount);  
  
    // 计算系统奖励  
    String startTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 00:00:00";  
    String endTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 24:00:00";  
    Long orderNum = orderInfoFeignClient.getOrderNumByTime(startTime, endTime).getData();  
  
    RewardRuleRequestForm rewardRuleRequestForm = new RewardRuleRequestForm();  
    rewardRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());  
    rewardRuleRequestForm.setOrderNum(orderNum);  
  
    RewardRuleResponseVo rewardRuleResponseVo = rewardRuleFeignClient.calculateOrderRewardFee(rewardRuleRequestForm).getData();  
  
    // 计算分账信息  
    ProfitsharingRuleRequestForm profitsharingRuleRequestForm = new ProfitsharingRuleRequestForm();  
    profitsharingRuleRequestForm.setOrderAmount(feeRuleResponseVo.getTotalAmount());  
    profitsharingRuleRequestForm.setOrderNum(orderNum);  
  
    ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleFeignClient.calculateOrderProfitsharingFee(profitsharingRuleRequestForm).getData();  
  
    // 封装实体类,结束代驾更新订单,添加账单和分账信息  
    UpdateOrderBillForm updateOrderBillForm = new UpdateOrderBillForm();  
    updateOrderBillForm.setOrderId(orderFeeForm.getOrderId());  
    updateOrderBillForm.setDriverId(orderFeeForm.getDriverId());  
    //路桥费、停车费、其他费用  
    updateOrderBillForm.setTollFee(orderFeeForm.getTollFee());  
    updateOrderBillForm.setParkingFee(orderFeeForm.getParkingFee());  
    updateOrderBillForm.setOtherFee(orderFeeForm.getOtherFee());  
    //乘客好处费  
    updateOrderBillForm.setFavourFee(orderInfo.getFavourFee());  
  
    //实际里程  
    updateOrderBillForm.setRealDistance(realDistance);  
    //订单奖励信息  
    BeanUtils.copyProperties(rewardRuleResponseVo, updateOrderBillForm);  
    //代驾费用信息  
    BeanUtils.copyProperties(feeRuleResponseVo, updateOrderBillForm);  
    //分账相关信息  
    BeanUtils.copyProperties(profitsharingRuleResponseVo, updateOrderBillForm);  
    updateOrderBillForm.setProfitsharingRuleId(profitsharingRuleResponseVo.getProfitsharingRuleId());  
    orderInfoFeignClient.endDrive(updateOrderBillForm);  
  
    return true;  
}

添加位置限定

修改之前web-driver中OrderServiceImpl编写的方法,在开始订单做判断

//司机到达代驾起始地点  
@Override  
public Boolean driverArriveStartLocation(Long orderId, Long driverId) {  
    //判断  
    // orderInfo有代驾开始位置  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();  
  
    //司机当前位置  
    OrderLocationVo orderLocationVo = locationFeignClient.getCacheOrderLocation(orderId).getData();  
  
    //司机当前位置 和 代驾开始位置距离  
    double distance = LocationUtil.getDistance(orderInfo.getStartPointLatitude().doubleValue(),  
            orderInfo.getStartPointLongitude().doubleValue(),  
            orderLocationVo.getLatitude().doubleValue(),  
            orderLocationVo.getLongitude().doubleValue());  
    if(distance > SystemConstant.DRIVER_START_LOCATION_DISTION) {  
        throw new GuiguException(ResultCodeEnum.DRIVER_START_LOCATION_DISTION_ERROR);  
    }  
  
    return orderInfoFeignClient.driverArriveStartLocation(orderId,driverId).getData();  
}

结束订单也是一样

// 结束代驾服务更新订单账单  
@Override  
public Boolean endDrive(OrderFeeForm orderFeeForm) {  
    // 根据orderId获取订单信息,判断当前订单是否司机接单  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderFeeForm.getOrderId()).getData();  
    if(orderInfo.getDriverId() != orderFeeForm.getDriverId()) {  
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
    }  
  
    // 判断距离  
    OrderServiceLastLocationVo orderServiceLastLocationVo = locationFeignClient.getOrderServiceLastLocation(orderFeeForm.getOrderId()).getData();  
  
    //司机当前位置 距离 结束代驾位置  
    double distance = LocationUtil.getDistance(orderInfo.getEndPointLatitude().doubleValue(),  
            orderInfo.getEndPointLongitude().doubleValue(),  
            orderServiceLastLocationVo.getLatitude().doubleValue(),  
            orderServiceLastLocationVo.getLongitude().doubleValue());  
    if(distance > SystemConstant.DRIVER_END_LOCATION_DISTION) {  
        throw new GuiguException(ResultCodeEnum.DRIVER_END_LOCATION_DISTION_ERROR);  
    }  
  
    // 计算订单实际里程  
    BigDecimal realDistance =  
            locationFeignClient.calculateOrderRealDistance(orderFeeForm.getOrderId()).getData();  
  
    // 计算代驾实际费用  
    //封装FeeRuleRequestForm  
    FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();  
    feeRuleRequestForm.setDistance(realDistance);  
    feeRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());  
  
    //计算司机到达代驾开始位置时间  
    Integer waitMinute =  
            Math.abs((int)((orderInfo.getStartServiceTime().getTime()-orderInfo.getArriveTime().getTime())/(1000 * 60)));  
    feeRuleRequestForm.setWaitMinute(waitMinute);  
    //远程调用 代驾费用  
    FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(feeRuleRequestForm).getData();  
    //实际费用 = 代驾费用 + 其他费用(停车费)  
    BigDecimal totalAmount =  
            feeRuleResponseVo.getTotalAmount().add(orderFeeForm.getTollFee())  
                    .add(orderFeeForm.getParkingFee())  
                    .add(orderFeeForm.getOtherFee())  
                    .add(orderInfo.getFavourFee());  
    feeRuleResponseVo.setTotalAmount(totalAmount);  
  
    // 计算系统奖励  
    String startTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 00:00:00";  
    String endTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 24:00:00";  
    Long orderNum = orderInfoFeignClient.getOrderNumByTime(startTime, endTime).getData();  
  
    RewardRuleRequestForm rewardRuleRequestForm = new RewardRuleRequestForm();  
    rewardRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());  
    rewardRuleRequestForm.setOrderNum(orderNum);  
  
    RewardRuleResponseVo rewardRuleResponseVo = rewardRuleFeignClient.calculateOrderRewardFee(rewardRuleRequestForm).getData();  
  
    // 计算分账信息  
    ProfitsharingRuleRequestForm profitsharingRuleRequestForm = new ProfitsharingRuleRequestForm();  
    profitsharingRuleRequestForm.setOrderAmount(feeRuleResponseVo.getTotalAmount());  
    profitsharingRuleRequestForm.setOrderNum(orderNum);  
  
    ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleFeignClient.calculateOrderProfitsharingFee(profitsharingRuleRequestForm).getData();  
  
    // 封装实体类,结束代驾更新订单,添加账单和分账信息  
    UpdateOrderBillForm updateOrderBillForm = new UpdateOrderBillForm();  
    updateOrderBillForm.setOrderId(orderFeeForm.getOrderId());  
    updateOrderBillForm.setDriverId(orderFeeForm.getDriverId());  
    //路桥费、停车费、其他费用  
    updateOrderBillForm.setTollFee(orderFeeForm.getTollFee());  
    updateOrderBillForm.setParkingFee(orderFeeForm.getParkingFee());  
    updateOrderBillForm.setOtherFee(orderFeeForm.getOtherFee());  
    //乘客好处费  
    updateOrderBillForm.setFavourFee(orderInfo.getFavourFee());  
  
    //实际里程  
    updateOrderBillForm.setRealDistance(realDistance);  
    //订单奖励信息  
    BeanUtils.copyProperties(rewardRuleResponseVo, updateOrderBillForm);  
    //代驾费用信息  
    BeanUtils.copyProperties(feeRuleResponseVo, updateOrderBillForm);  
    //分账相关信息  
    BeanUtils.copyProperties(profitsharingRuleResponseVo, updateOrderBillForm);  
    updateOrderBillForm.setProfitsharingRuleId(profitsharingRuleResponseVo.getProfitsharingRuleId());  
    orderInfoFeignClient.endDrive(updateOrderBillForm);  
  
    return true;  
}

我的订单和异步编排

我的订单

  • 乘客端或司机端,都有我的订单,都可以查看用户所有的订单

乘客端我的订单

订单微服务接口

OrderInfoController

@Operation(summary = "获取乘客订单分页列表")  
@GetMapping("/findCustomerOrderPage/{customerId}/{page}/{limit}")  
public Result<PageVo> findCustomerOrderPage(@PathVariable Long customerId,  
                                            @PathVariable Long page,  
                                            @PathVariable Long limit) {  
    //创建page对象  
    Page<OrderInfo> pageParam = new Page<>(page,limit);  
    //调用service方法实现分页条件查询  
    PageVo pageVo = orderInfoService.findCustomerOrderPage(pageParam,customerId);  
    pageVo.setPage(page);  
    pageVo.setLimit(limit);  
    return Result.ok(pageVo);  
}

service

//获取乘客订单分页列表  
@Override  
public PageVo findCustomerOrderPage(Page<OrderInfo> pageParam, Long customerId) {  
    IPage<OrderListVo> pageInfo =  orderInfoMapper.selectCustomerOrderPage(pageParam,customerId);  
    return new PageVo<>(pageInfo.getRecords(),pageInfo.getPages(),pageInfo.getTotal());  
}

远程调用

OrderInfoFeignClient

/**  
 * 获取乘客订单分页列表  
 * @param customerId  
 * @param page  
 * @param limit  
 * @return  
 */  
@GetMapping("/order/info/findCustomerOrderPage/{customerId}/{page}/{limit}")  
Result<PageVo> findCustomerOrderPage(@PathVariable("customerId") Long customerId,  
                                     @PathVariable("page") Long page,  
                                     @PathVariable("limit") Long limit);

乘客web端调用

OrderController

@Operation(summary = "获取乘客订单分页列表")  
@LoginDetection  
@GetMapping("findCustomerOrderPage/{page}/{limit}")  
public Result<PageVo> findCustomerOrderPage(  
        @Parameter(name = "page", description = "当前页码", required = true)  
        @PathVariable Long page,  
  
        @Parameter(name = "limit", description = "每页记录数", required = true)  
        @PathVariable Long limit) {  
    Long customerId = AuthContextHolder.getUserId();  
    PageVo pageVo = orderService.findCustomerOrderPage(customerId, page, limit);  
    return Result.ok(pageVo);  
}

service

// 获取乘客订单分页列表  
@Override  
public PageVo findCustomerOrderPage(Long customerId, Long page, Long limit) {  
    return orderInfoFeignClient.findCustomerOrderPage(customerId,page,limit).getData();  
}

xml

    <!--查询乘客订单分页-->
    <select id="selectCustomerOrderPage" resultType="com.atguigu.daijia.model.vo.order.OrderListVo">
        select
            info.id,
            info.order_no,
            info.start_location,
            info.end_location,

            if(info.status &lt; 7, info.expect_amount, bill.pay_amount) as amount,

            info.status,
            info.create_time
        from order_info info left join order_bill bill on info.id = bill.order_id
        where info.customer_id = #{customerId}
          and info.is_deleted =0
        order by info.create_time desc
    </select>

司机端我的订单

订单微服务接口

OrderInfoController

@Operation(summary = "获取司机订单分页列表")  
@GetMapping("/findDriverOrderPage/{driverId}/{page}/{limit}")  
public Result<PageVo> findDriverOrderPage(  
        @Parameter(name = "driverId", description = "司机id", required = true)  
        @PathVariable Long driverId,  
        @Parameter(name = "page", description = "当前页码", required = true)  
        @PathVariable Long page,  
        @Parameter(name = "limit", description = "每页记录数", required = true)  
        @PathVariable Long limit) {  
    Page<OrderInfo> pageParam = new Page<>(page, limit);  
    PageVo pageVo = orderInfoService.findDriverOrderPage(pageParam, driverId);  
    pageVo.setPage(page);  
    pageVo.setLimit(limit);  
    return Result.ok(pageVo);  
}

service

// 获取司机订单分页列表  
@Override  
public PageVo findDriverOrderPage(Page<OrderInfo> pageParam, Long driverId) {  
    IPage<OrderListVo> pageInfo =  orderInfoMapper.selectDriverOrderPage(pageParam,driverId);  
    return new PageVo<>(pageInfo.getRecords(),pageInfo.getPages(),pageInfo.getTotal());  
}

xml

<select id="selectDriverOrderPage" resultType="com.atguigu.daijia.model.vo.order.OrderListVo">
    select
        info.id,
        info.order_no,
        info.start_location,
        info.end_location,
        real_amount as pay_amount,
        if(info.status &lt; 7, info.expect_amount, info.real_amount) as amount,

        info.status,
        info.create_time
    from order_info info
    where info.driver_id = #{driverId}
    and info.is_deleted =0
    order by info.create_time desc
</select>

远程调用

OrderInfoFeignClient

/**  
 * 获取司机订单分页列表  
 * @param driverId  
 * @param page  
 * @param limit  
 * @return  
 */  
@GetMapping("/order/info/findDriverOrderPage/{driverId}/{page}/{limit}")  
Result<PageVo> findDriverOrderPage(@PathVariable("driverId") Long driverId,  
                                   @PathVariable("page") Long page,  
                                   @PathVariable("limit") Long limit);

司机端web接口

OrderController

@Operation(summary = "获取司机订单分页列表")  
@LoginDetection  
@GetMapping("findDriverOrderPage/{page}/{limit}")  
public Result<PageVo> findDriverOrderPage(  
        @Parameter(name = "page", description = "当前页码", required = true)  
        @PathVariable Long page,  
        @Parameter(name = "limit", description = "每页记录数", required = true)  
        @PathVariable Long limit) {  
    Long driverId = AuthContextHolder.getUserId();  
    PageVo pageVo = orderService.findDriverOrderPage(driverId, page, limit);  
    return Result.ok(pageVo);  
}

service

// 获取司机订单分页列表  
@Override  
public PageVo findDriverOrderPage(Long driverId, Long page, Long limit) {  
    return orderInfoFeignClient.findDriverOrderPage(driverId,page,limit).getData();  
}

异步编排

  • 问题:司机结束订单后会有大量的远程调用,如果按照流程来会浪费大量时间
  • 解决:使用多线程方式来完成这些操作

创建自定义线程池

在config包下创建类ThreadPoolConfig

@Configuration  
public class ThreadPoolConfig {  
  
    @Bean  
    public ThreadPoolExecutor threadPoolExecutor() {  
  
        //动态获取服务器核数  
        int processors = Runtime.getRuntime().availableProcessors();  
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(  
                processors+1, // 核心线程个数 io:2n ,cpu: n+1  n:内核数据  
                processors+1,  
                0,  
                TimeUnit.SECONDS,  
                new ArrayBlockingQueue<>(3),  
                Executors.defaultThreadFactory(),  
                new ThreadPoolExecutor.AbortPolicy()  
        );  
        return threadPoolExecutor;  
    }  
}

修改web-driver之前的代码

修改前面driver-web里OrderServiceImpl的endDrive方法

//使用多线程CompletableFuture实现  
@SneakyThrows  
public Boolean endDrive(OrderFeeForm orderFeeForm) {  
  
    //1 根据orderId获取订单信息,判断当前订单是否司机接单  
    CompletableFuture<OrderInfo> orderInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {  
        OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderFeeForm.getOrderId()).getData();  
        if (orderInfo.getDriverId() != orderFeeForm.getDriverId()) {  
            throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
        }  
        return orderInfo;  
    });  
  
    //防止刷单  
    CompletableFuture<OrderServiceLastLocationVo> orderServiceLastLocationVoCompletableFuture = CompletableFuture.supplyAsync(() -> {  
        OrderServiceLastLocationVo orderServiceLastLocationVo = locationFeignClient.getOrderServiceLastLocation(orderFeeForm.getOrderId()).getData();  
        return orderServiceLastLocationVo;  
    });  
  
    //上面两个合并  
    CompletableFuture.allOf(orderInfoCompletableFuture,  
            orderServiceLastLocationVoCompletableFuture).join();  
  
    //获取两个线程执行结果  
    OrderInfo orderInfo = orderInfoCompletableFuture.get();  
  
    OrderServiceLastLocationVo orderServiceLastLocationVo = orderServiceLastLocationVoCompletableFuture.get();  
  
    //司机当前位置 距离 结束代驾位置  
    double distance = LocationUtil.getDistance(orderInfo.getEndPointLatitude().doubleValue(),  
            orderInfo.getEndPointLongitude().doubleValue(),  
            orderServiceLastLocationVo.getLatitude().doubleValue(),  
            orderServiceLastLocationVo.getLongitude().doubleValue());  
    if(distance > SystemConstant.DRIVER_END_LOCATION_DISTION) {  
        throw new GuiguException(ResultCodeEnum.DRIVER_END_LOCATION_DISTION_ERROR);  
    }  
  
    //2 计算订单实际里程  
    CompletableFuture<BigDecimal> realDistanceCompletableFuture = CompletableFuture.supplyAsync(() -> {  
        BigDecimal realDistance =  
                locationFeignClient.calculateOrderRealDistance(orderFeeForm.getOrderId()).getData();  
        return realDistance;  
    });  
  
    //3 计算代驾实际费用  
    CompletableFuture<FeeRuleResponseVo> feeRuleResponseVoCompletableFuture =  
            realDistanceCompletableFuture.thenApplyAsync((realDistance) -> {  
                //远程调用,计算代驾费用  
                //封装FeeRuleRequestForm  
                FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();  
                feeRuleRequestForm.setDistance(realDistance);  
                feeRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());  
  
                //计算司机到达代驾开始位置时间  
                //orderInfo.getArriveTime() - orderInfo.getAcceptTime()  
                // 分钟 = 毫秒 / 1000 * 60                Integer waitMinute =  
                        Math.abs((int) ((orderInfo.getArriveTime().getTime() - orderInfo.getAcceptTime().getTime()) / (1000 * 60)));  
                feeRuleRequestForm.setWaitMinute(waitMinute);  
                //远程调用 代驾费用  
                FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(feeRuleRequestForm).getData();  
                //实际费用 = 代驾费用 + 其他费用(停车费)  
                BigDecimal totalAmount =  
                        feeRuleResponseVo.getTotalAmount().add(orderFeeForm.getTollFee())  
                                .add(orderFeeForm.getParkingFee())  
                                .add(orderFeeForm.getOtherFee())  
                                .add(orderInfo.getFavourFee());  
                feeRuleResponseVo.setTotalAmount(totalAmount);  
                return feeRuleResponseVo;  
            });  
  
    //4 计算系统奖励  
    CompletableFuture<Long> orderNumCompletableFuture = CompletableFuture.supplyAsync(() -> {  
        String startTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 00:00:00";  
        String endTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 24:00:00";  
        Long orderNum = orderInfoFeignClient.getOrderNumByTime(startTime, endTime).getData();  
        return orderNum;  
    });  
  
    CompletableFuture<RewardRuleResponseVo> rewardRuleResponseVoCompletableFuture =  
            orderNumCompletableFuture.thenApplyAsync((orderNum) -> {  
                //4.2.封装参数  
                RewardRuleRequestForm rewardRuleRequestForm = new RewardRuleRequestForm();  
                rewardRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());  
                rewardRuleRequestForm.setOrderNum(orderNum);  
                RewardRuleResponseVo rewardRuleResponseVo = rewardRuleFeignClient.calculateOrderRewardFee(rewardRuleRequestForm).getData();  
  
                return rewardRuleResponseVo;  
            });  
  
  
    //5 计算分账信息  
    CompletableFuture<ProfitsharingRuleResponseVo> profitsharingRuleResponseVoCompletableFuture = feeRuleResponseVoCompletableFuture.thenCombineAsync(orderNumCompletableFuture,  
            (feeRuleResponseVo, orderNum) -> {  
                ProfitsharingRuleRequestForm profitsharingRuleRequestForm = new ProfitsharingRuleRequestForm();  
                profitsharingRuleRequestForm.setOrderAmount(feeRuleResponseVo.getTotalAmount());  
                profitsharingRuleRequestForm.setOrderNum(orderNum);  
  
                ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleFeignClient.calculateOrderProfitsharingFee(profitsharingRuleRequestForm).getData();  
                return profitsharingRuleResponseVo;  
            });  
  
    //合并  
    CompletableFuture.allOf(  
            orderInfoCompletableFuture,  
            realDistanceCompletableFuture,  
            feeRuleResponseVoCompletableFuture,  
            orderNumCompletableFuture,  
            rewardRuleResponseVoCompletableFuture,  
            profitsharingRuleResponseVoCompletableFuture  
    ).join();  
  
    //获取执行结果  
    BigDecimal realDistance = realDistanceCompletableFuture.get();  
    FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoCompletableFuture.get();  
    RewardRuleResponseVo rewardRuleResponseVo = rewardRuleResponseVoCompletableFuture.get();  
    ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleResponseVoCompletableFuture.get();  
  
    //6 封装实体类,结束代驾更新订单,添加账单和分账信息  
    UpdateOrderBillForm updateOrderBillForm = new UpdateOrderBillForm();  
    updateOrderBillForm.setOrderId(orderFeeForm.getOrderId());  
    updateOrderBillForm.setDriverId(orderFeeForm.getDriverId());  
    //路桥费、停车费、其他费用  
    updateOrderBillForm.setTollFee(orderFeeForm.getTollFee());  
    updateOrderBillForm.setParkingFee(orderFeeForm.getParkingFee());  
    updateOrderBillForm.setOtherFee(orderFeeForm.getOtherFee());  
    //乘客好处费  
    updateOrderBillForm.setFavourFee(orderInfo.getFavourFee());  
  
    //实际里程  
    updateOrderBillForm.setRealDistance(realDistance);  
    //订单奖励信息  
    BeanUtils.copyProperties(rewardRuleResponseVo, updateOrderBillForm);  
    //代驾费用信息  
    BeanUtils.copyProperties(feeRuleResponseVo, updateOrderBillForm);  
    //分账相关信息  
    BeanUtils.copyProperties(profitsharingRuleResponseVo, updateOrderBillForm);  
    updateOrderBillForm.setProfitsharingRuleId(profitsharingRuleResponseVo.getProfitsharingRuleId());  
    orderInfoFeignClient.endDrive(updateOrderBillForm);  
  
    return true;  
}

订单支付

账单信息

  • 司机结束代价后,生成账单(包含账单信息和分账信息)

获取账单信息

订单微服务接口

OrderInfoController

@Operation(summary = "根据订单id获取实际账单信息")  
@GetMapping("/getOrderBillInfo/{orderId}")  
public Result<OrderBillVo> getOrderBillInfo(@PathVariable Long orderId) {  
    return Result.ok(orderInfoService.getOrderBillInfo(orderId));  
}

service

// 根据订单id获取实际账单信息  
@Override  
public OrderBillVo getOrderBillInfo(Long orderId) {  
    LambdaQueryWrapper<OrderBill> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderBill::getOrderId,orderId);  
    OrderBill orderBill = orderBillMapper.selectOne(wrapper);  
  
    OrderBillVo orderBillVo = new OrderBillVo();  
    BeanUtils.copyProperties(orderBill,orderBillVo);  
    return orderBillVo;  
}

远程调用

OrderInfoFeignClient

/**  
 * 根据订单id获取实际账单信息  
 * @param orderId  
 * @return  
 */  
@GetMapping("/order/info/getOrderBillInfo/{orderId}")  
Result<OrderBillVo> getOrderBillInfo(@PathVariable("orderId") Long orderId);

获取分账信息

订单微服务接口

OrderInfoController

@Operation(summary = "根据订单id获取实际分账信息")  
@GetMapping("/getOrderProfitsharing/{orderId}")  
public Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable Long orderId) {  
    return Result.ok(orderInfoService.getOrderProfitsharing(orderId));  
}

service

// 根据订单id获取实际分账信息  
@Override  
public OrderProfitsharingVo getOrderProfitsharing(Long orderId) {  
    LambdaQueryWrapper<OrderProfitsharing> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(OrderProfitsharing::getOrderId,orderId);  
    OrderProfitsharing orderProfitsharing = orderProfitsharingMapper.selectOne(wrapper);  
  
    OrderProfitsharingVo orderProfitsharingVo = new OrderProfitsharingVo();  
    BeanUtils.copyProperties(orderProfitsharing,orderProfitsharingVo);  
    return orderProfitsharingVo;  
}

远程调用

OrderInfoFeignClient

/**
 * 根据订单id获取实际分账信息
 * @param orderId
 * @return
 */
@GetMapping("/order/info/getOrderProfitsharing/{orderId}")
Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable("orderId") Long orderId);

司机端获取账单信息

方法之前有,只需要改service OrderController`

@Operation(summary = "获取订单账单详细信息")  
@LoginDetection  
@GetMapping("/getOrderInfo/{orderId}")  
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.getOrderInfo(orderId, driverId));  
}

service

// 获取订单账单详细信息  
@Override  
public OrderInfoVo getOrderInfo(Long orderId, Long driverId) {  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();  
    if(orderInfo.getDriverId() != driverId) {  
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
    }  
  
    //获取账单和分账数据,封装到vo里面  
    OrderBillVo orderBillVo = null;  
    OrderProfitsharingVo orderProfitsharingVo = null;  
    //判断  
    if(orderInfo.getStatus() >= OrderStatus.END_SERVICE.getStatus()) {  
        //账单信息  
        orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();  
  
        //分账信息  
        orderProfitsharingVo = orderInfoFeignClient.getOrderProfitsharing(orderId).getData();  
    }  
  
    OrderInfoVo orderInfoVo = new OrderInfoVo();  
    orderInfoVo.setOrderId(orderId);  
    BeanUtils.copyProperties(orderInfo,orderInfoVo);  
    orderInfoVo.setOrderBillVo(orderBillVo);  
    orderInfoVo.setOrderProfitsharingVo(orderProfitsharingVo);  
    return orderInfoVo;  
}

司机发送账单

订单微服务接口

OrderInfoController

@Operation(summary = "发送账单信息")  
@GetMapping("/sendOrderBillInfo/{orderId}/{driverId}")  
Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId, @PathVariable Long driverId) {  
    return Result.ok(orderInfoService.sendOrderBillInfo(orderId, driverId));  
}

service

// 发送账单信息  
@Override  
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {  
    //更新订单信息  
    LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();  
    queryWrapper.eq(OrderInfo::getId, orderId);  
    queryWrapper.eq(OrderInfo::getDriverId, driverId);  
    //更新字段  
    OrderInfo updateOrderInfo = new OrderInfo();  
    updateOrderInfo.setStatus(OrderStatus.UNPAID.getStatus());  
    //只能更新自己的订单  
    int row = orderInfoMapper.update(updateOrderInfo, queryWrapper);  
    if(row == 1) {  
        return true;  
    } else {  
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);  
    }  
}

远程调用

OrderInfoFeignClient

/**  
 * 司机发送账单信息  
 * @param orderId  
 * @param driverId  
 * @return  
 */  
@GetMapping("/order/info/sendOrderBillInfo/{orderId}/{driverId}")  
Result<Boolean> sendOrderBillInfo(@PathVariable("orderId") Long orderId, @PathVariable("driverId") Long driverId);

司机web端调用

OrderController

@Operation(summary = "司机发送账单信息")  
@LoginDetection  
@GetMapping("/sendOrderBillInfo/{orderId}")  
public Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId) {  
    Long driverId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.sendOrderBillInfo(orderId, driverId));  
}

service

// 司机发送账单信息  
@Override  
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {  
    return orderInfoFeignClient.sendOrderBillInfo(orderId, driverId).getData();  
}

乘客获取账单

也是修改之前写过的代码,只需要修改service OrderController

@Operation(summary = "获取订单信息")  
@LoginDetection  
@GetMapping("/getOrderInfo/{orderId}")  
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {  
    Long customerId = AuthContextHolder.getUserId();  
    return Result.ok(orderService.getOrderInfo(orderId, customerId));  
}

service

// 获取订单信息  
@Override  
public OrderInfoVo getOrderInfo(Long orderId, Long customerId) {  
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();  
    //判断  
    if(orderInfo.getCustomerId() != customerId) {  
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
    }  
  
    //获取司机信息  
    DriverInfoVo driverInfoVo = null;  
    Long driverId = orderInfo.getDriverId();  
    if(driverId != null) {  
        driverInfoVo = driverInfoFeignClient.getDriverInfo(driverId).getData();  
    }  
  
    //获取账单信息  
    OrderBillVo orderBillVo = null;  
    if(orderInfo.getStatus() >= OrderStatus.UNPAID.getStatus()) {  
        orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();  
    }  
  
    OrderInfoVo orderInfoVo = new OrderInfoVo();  
    orderInfoVo.setOrderId(orderId);  
    BeanUtils.copyProperties(orderInfo,orderInfoVo);  
    orderInfoVo.setOrderBillVo(orderBillVo);  
    orderInfoVo.setDriverInfoVo(driverInfoVo);  
    return orderInfoVo;  
}

微信支付

准备接口

获取乘客openid

service-customer中的CustomerInfoController

@Operation(summary = "获取客户OpenId")  
@GetMapping("/getCustomerOpenId/{customerId}")  
public Result<String> getCustomerOpenId(@PathVariable Long customerId) {  
    return Result.ok(customerInfoService.getCustomerOpenId(customerId));  
}

service

// 获取客户OpenId  
@Override  
public String getCustomerOpenId(Long customerId) {  
    LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper<>();  
    wrapper.eq(CustomerInfo::getId,customerId);  
    CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);  
    return customerInfo.getWxOpenId();  
}

远程调用

CustomerInfoFeignClient

/**  
 * 获取客户OpenId  
 * @param customerId  
 * @return  
 */  
@GetMapping("/customer/info/getCustomerOpenId/{customerId}")  
Result<String> getCustomerOpenId(@PathVariable("customerId") Long customerId);

获取司机openid

service-driver中DriverInfoController

@Operation(summary = "获取司机OpenId")  
@GetMapping("/getDriverOpenId/{driverId}")  
public Result<String> getDriverOpenId(@PathVariable Long driverId) {  
    return Result.ok(driverInfoService.getDriverOpenId(driverId));  
}

serivce

// 获取司机OpenId  
@Override  
public String getDriverOpenId(Long driverId) {  
    DriverInfo driverInfo = this.getOne(new LambdaQueryWrapper<DriverInfo>().eq(DriverInfo::getId, driverId).select(DriverInfo::getWxOpenId));  
    return driverInfo.getWxOpenId();  
}

远程调用

DriverInfoFeignClient

/**  
 * 获取司机OpenId  
 * @param driverId  
 * @return  
 */  
@GetMapping("/driver/info/getDriverOpenId/{driverId}")  
Result<String> getDriverOpenId(@PathVariable("driverId") Long driverId);

获取支付信息

service-order中OrderInfoController

@Operation(summary = "获取订单支付信息")
@GetMapping("/getOrderPayVo/{orderNo}/{customerId}")
public Result<OrderPayVo> getOrderPayVo(@PathVariable String orderNo, @PathVariable Long customerId) {
    return Result.ok(orderInfoService.getOrderPayVo(orderNo, customerId));
}

service

// 获取订单支付信息  
@Override  
public OrderPayVo getOrderPayVo(String orderNo, Long customerId) {  
    OrderPayVo orderPayVo = orderInfoMapper.selectOrderPayVo(orderNo,customerId);  
    if(orderPayVo != null) {  
        String content = orderPayVo.getStartLocation() + " 到 "+orderPayVo.getEndLocation();  
        orderPayVo.setContent(content);  
    }  
    return orderPayVo;  
}

远程调用

/**  
 * 获取订单支付信息  
 * @param orderNo  
 * @param customerId  
 * @return  
 */  
@GetMapping("/order/info/getOrderPayVo/{orderNo}/{customerId}")  
Result<OrderPayVo> getOrderPayVo(@PathVariable("orderNo") String orderNo, @PathVariable("customerId") Long customerId);

微信支付接口

在service-payment下

  • 导入依赖
<dependency>  
    <groupId>com.github.wechatpay-apiv3</groupId>  
    <artifactId>wechatpay-java</artifactId>  
</dependency>
  • 创建配置类
@Configuration  
@ConfigurationProperties(prefix="wx.v3pay") //读取节点  
@Data  
public class WxPayV3Properties {  
  
    private String appid;  
    /** 商户号 */  
    public String merchantId;  
    /** 商户API私钥路径 */  
    public String privateKeyPath;  
    /** 商户证书序列号 */  
    public String merchantSerialNumber;  
    /** 商户APIV3密钥 */  
    public String apiV3key;  
    /** 回调地址 */  
    private String notifyUrl;  
  
    @Bean  
    public RSAAutoCertificateConfig getConfig(){  
        return new RSAAutoCertificateConfig.Builder()  
                .merchantId(this.getMerchantId())  
                .privateKeyFromPath(this.getPrivateKeyPath())  
                .merchantSerialNumber(this.getMerchantSerialNumber())  
                .apiV3Key(this.getApiV3key())  
                .build();  
  
    }  
}

WxPayController

@Autowired  
private WxPayService wxPayService;  
  
@Operation(summary = "创建微信支付")  
@PostMapping("/createJsapi")  
public Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm) {  
    return Result.ok(wxPayService.createWxPayment(paymentInfoForm));  
}
  • 远程调用

WxPayFeignClient

/**  
 * 创建微信支付  
 * @param paymentInfoForm  
 * @return  
 */  
@PostMapping("/payment/wxPay/createWxPayment")  
Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm);
  • 乘客web端调用

OrderController

@Operation(summary = "创建微信支付")  
@LoginDetection  
@PostMapping("/createWxPayment")  
public Result<WxPrepayVo> createWxPayment(@RequestBody CreateWxPaymentForm createWxPaymentForm) {  
    Long customerId = AuthContextHolder.getUserId();  
    createWxPaymentForm.setCustomerId(customerId);  
    return Result.ok(orderService.createWxPayment(createWxPaymentForm));  
}

service

@Override  
public WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm) {  
    //获取订单支付信息  
    OrderPayVo orderPayVo = orderInfoFeignClient.getOrderPayVo(createWxPaymentForm.getOrderNo(),  
            createWxPaymentForm.getCustomerId()).getData();  
    //判断  
    if(orderPayVo.getStatus() != OrderStatus.UNPAID.getStatus()) {  
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);  
    }  
  
    //获取乘客和司机openid  
    String customerOpenId = customerInfoFeignClient.getCustomerOpenId(orderPayVo.getCustomerId()).getData();  
  
    String driverOpenId = driverInfoFeignClient.getDriverOpenId(orderPayVo.getDriverId()).getData();  
  
    //封装需要数据到实体类,远程调用发起微信支付  
    PaymentInfoForm paymentInfoForm = new PaymentInfoForm();  
    paymentInfoForm.setCustomerOpenId(customerOpenId);  
    paymentInfoForm.setDriverOpenId(driverOpenId);  
    paymentInfoForm.setOrderNo(orderPayVo.getOrderNo());  
    paymentInfoForm.setAmount(orderPayVo.getPayAmount());  
    paymentInfoForm.setContent(orderPayVo.getContent());  
    paymentInfoForm.setPayWay(1);  
  
    WxPrepayVo wxPrepayVo = wxPayFeignClient.createWxPayment(paymentInfoForm).getData();  
    return wxPrepayVo;  
}

支付结果查询

Posted on:
April 17, 2025
Length:
56 minute read, 11787 words
Tags:
project
See Also:
CompletableFuture异步编排
MongoDB
分布式事物锁