校园闪送项目笔记
搭建前端环境
注册微信开发者账号
打开微信公总平台,按照流程一步步注册: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;
}
- 根据腾讯云文档来实现代码 API Explorer - 云 API - 控制台
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();
}
人脸识别接口
进行人脸识别,基于腾讯云实现
之前创建人脸识别模型,基于之前创建人脸模型完成当前识别功能
找到腾讯云文档1:照片比对 https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2020-03-03&Action=VerifyFace
找到腾讯云文档2:人脸静态活体检测 https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2020-03-03&Action=DetectLiveFace
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 < 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 < 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
- 分布式事物锁