Commit a5543e29205911fc16ef924f798a02f8f69de78b
0 parents
初始化项目
Showing
158 changed files
with
9319 additions
and
0 deletions
.gitignore
0 → 100644
pom.xml
0 → 100644
| 1 | +++ a/pom.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | |
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 5 | + <modelVersion>4.0.0</modelVersion> | |
| 6 | + | |
| 7 | + <parent> | |
| 8 | + <groupId>org.springframework.boot</groupId> | |
| 9 | + <artifactId>spring-boot-starter-parent</artifactId> | |
| 10 | + <version>3.2.0</version> | |
| 11 | + <relativePath/> | |
| 12 | + </parent> | |
| 13 | + | |
| 14 | + <groupId>com.diligrp.tms</groupId> | |
| 15 | + <artifactId>rider-service</artifactId> | |
| 16 | + <version>1.0.0</version> | |
| 17 | + <name>rider-service</name> | |
| 18 | + <description>地利外卖骑手配送系统</description> | |
| 19 | + | |
| 20 | + <properties> | |
| 21 | + <java.version>17</java.version> | |
| 22 | + <mybatis-plus.version>3.5.7</mybatis-plus.version> | |
| 23 | + </properties> | |
| 24 | + | |
| 25 | + <dependencies> | |
| 26 | + <!-- Spring Boot Web --> | |
| 27 | + <dependency> | |
| 28 | + <groupId>org.springframework.boot</groupId> | |
| 29 | + <artifactId>spring-boot-starter-web</artifactId> | |
| 30 | + </dependency> | |
| 31 | + | |
| 32 | + <!-- MyBatis-Plus (Spring Boot 3 专用 starter) --> | |
| 33 | + <dependency> | |
| 34 | + <groupId>com.baomidou</groupId> | |
| 35 | + <artifactId>mybatis-plus-spring-boot3-starter</artifactId> | |
| 36 | + <version>${mybatis-plus.version}</version> | |
| 37 | + </dependency> | |
| 38 | + | |
| 39 | + <!-- MySQL --> | |
| 40 | + <dependency> | |
| 41 | + <groupId>com.mysql</groupId> | |
| 42 | + <artifactId>mysql-connector-j</artifactId> | |
| 43 | + <scope>runtime</scope> | |
| 44 | + </dependency> | |
| 45 | + | |
| 46 | + <!-- Redis --> | |
| 47 | + <dependency> | |
| 48 | + <groupId>org.springframework.boot</groupId> | |
| 49 | + <artifactId>spring-boot-starter-data-redis</artifactId> | |
| 50 | + </dependency> | |
| 51 | + | |
| 52 | + <!-- Validation --> | |
| 53 | + <dependency> | |
| 54 | + <groupId>org.springframework.boot</groupId> | |
| 55 | + <artifactId>spring-boot-starter-validation</artifactId> | |
| 56 | + </dependency> | |
| 57 | + | |
| 58 | + <!-- Lombok --> | |
| 59 | + <dependency> | |
| 60 | + <groupId>org.projectlombok</groupId> | |
| 61 | + <artifactId>lombok</artifactId> | |
| 62 | + <optional>true</optional> | |
| 63 | + </dependency> | |
| 64 | + | |
| 65 | + <!-- JWT --> | |
| 66 | + <dependency> | |
| 67 | + <groupId>io.jsonwebtoken</groupId> | |
| 68 | + <artifactId>jjwt-api</artifactId> | |
| 69 | + <version>0.11.5</version> | |
| 70 | + </dependency> | |
| 71 | + <dependency> | |
| 72 | + <groupId>io.jsonwebtoken</groupId> | |
| 73 | + <artifactId>jjwt-impl</artifactId> | |
| 74 | + <version>0.11.5</version> | |
| 75 | + <scope>runtime</scope> | |
| 76 | + </dependency> | |
| 77 | + <dependency> | |
| 78 | + <groupId>io.jsonwebtoken</groupId> | |
| 79 | + <artifactId>jjwt-jackson</artifactId> | |
| 80 | + <version>0.11.5</version> | |
| 81 | + <scope>runtime</scope> | |
| 82 | + </dependency> | |
| 83 | + | |
| 84 | + <!-- Test --> | |
| 85 | + <dependency> | |
| 86 | + <groupId>org.springframework.boot</groupId> | |
| 87 | + <artifactId>spring-boot-starter-test</artifactId> | |
| 88 | + <scope>test</scope> | |
| 89 | + </dependency> | |
| 90 | + </dependencies> | |
| 91 | + | |
| 92 | + <build> | |
| 93 | + <plugins> | |
| 94 | + <plugin> | |
| 95 | + <groupId>org.springframework.boot</groupId> | |
| 96 | + <artifactId>spring-boot-maven-plugin</artifactId> | |
| 97 | + <configuration> | |
| 98 | + <excludes> | |
| 99 | + <exclude> | |
| 100 | + <groupId>org.projectlombok</groupId> | |
| 101 | + <artifactId>lombok</artifactId> | |
| 102 | + </exclude> | |
| 103 | + </excludes> | |
| 104 | + </configuration> | |
| 105 | + </plugin> | |
| 106 | + </plugins> | |
| 107 | + </build> | |
| 108 | +</project> | ... | ... |
src/main/java/com/diligrp/rider/RiderServiceApplication.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/RiderServiceApplication.java | |
| 1 | +package com.diligrp.rider; | |
| 2 | + | |
| 3 | +import org.mybatis.spring.annotation.MapperScan; | |
| 4 | +import org.springframework.boot.SpringApplication; | |
| 5 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | |
| 6 | +import org.springframework.scheduling.annotation.EnableAsync; | |
| 7 | + | |
| 8 | +@SpringBootApplication | |
| 9 | +@MapperScan("com.diligrp.rider.mapper") | |
| 10 | +@EnableAsync | |
| 11 | +public class RiderServiceApplication { | |
| 12 | + public static void main(String[] args) { | |
| 13 | + SpringApplication.run(RiderServiceApplication.class, args); | |
| 14 | + } | |
| 15 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/enums/IncomeStatusEnum.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/enums/IncomeStatusEnum.java | |
| 1 | +package com.diligrp.rider.common.enums; | |
| 2 | + | |
| 3 | +import lombok.Getter; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 结算状态枚举 | |
| 7 | + * 0=未结算 1=待结算 2=已结算 | |
| 8 | + */ | |
| 9 | +@Getter | |
| 10 | +public enum IncomeStatusEnum { | |
| 11 | + NOT_SETTLED(0, "未结算"), | |
| 12 | + PENDING(1, "待结算"), | |
| 13 | + SETTLED(2, "已结算"); | |
| 14 | + | |
| 15 | + private final int code; | |
| 16 | + private final String desc; | |
| 17 | + | |
| 18 | + IncomeStatusEnum(int code, String desc) { | |
| 19 | + this.code = code; | |
| 20 | + this.desc = desc; | |
| 21 | + } | |
| 22 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/enums/OrderStatusEnum.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/enums/OrderStatusEnum.java | |
| 1 | +package com.diligrp.rider.common.enums; | |
| 2 | + | |
| 3 | +import lombok.Getter; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 订单状态枚举 | |
| 7 | + * 1=待支付 2=已支付 3=已接单 4=服务中 6=已完成 | |
| 8 | + * 7=退款申请 8=退款成功 9=退款拒绝 10=已取消 | |
| 9 | + */ | |
| 10 | +@Getter | |
| 11 | +public enum OrderStatusEnum { | |
| 12 | + WAIT_PAY(1, "待支付"), | |
| 13 | + PAID(2, "已支付"), | |
| 14 | + ACCEPTED(3, "已接单"), | |
| 15 | + IN_SERVICE(4, "服务中"), | |
| 16 | + COMPLETED(6, "已完成"), | |
| 17 | + REFUND_APPLY(7, "退款申请"), | |
| 18 | + REFUND_SUCCESS(8, "退款成功"), | |
| 19 | + REFUND_REJECT(9, "退款拒绝"), | |
| 20 | + CANCELLED(10, "已取消"); | |
| 21 | + | |
| 22 | + private final int code; | |
| 23 | + private final String desc; | |
| 24 | + | |
| 25 | + OrderStatusEnum(int code, String desc) { | |
| 26 | + this.code = code; | |
| 27 | + this.desc = desc; | |
| 28 | + } | |
| 29 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/enums/RiderStatusEnum.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/enums/RiderStatusEnum.java | |
| 1 | +package com.diligrp.rider.common.enums; | |
| 2 | + | |
| 3 | +import lombok.Getter; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 骑手状态枚举 | |
| 7 | + * 0=拒绝 1=通过 2=待审核 | |
| 8 | + */ | |
| 9 | +@Getter | |
| 10 | +public enum RiderStatusEnum { | |
| 11 | + REJECTED(0, "拒绝"), | |
| 12 | + APPROVED(1, "通过"), | |
| 13 | + PENDING(2, "待审核"); | |
| 14 | + | |
| 15 | + private final int code; | |
| 16 | + private final String desc; | |
| 17 | + | |
| 18 | + RiderStatusEnum(int code, String desc) { | |
| 19 | + this.code = code; | |
| 20 | + this.desc = desc; | |
| 21 | + } | |
| 22 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/enums/TransStatusEnum.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/enums/TransStatusEnum.java | |
| 1 | +package com.diligrp.rider.common.enums; | |
| 2 | + | |
| 3 | +import lombok.Getter; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 转单状态枚举 | |
| 7 | + * 0=未转单 1=转单申请通过 2=转单申请中 3=转单申请拒绝 | |
| 8 | + */ | |
| 9 | +@Getter | |
| 10 | +public enum TransStatusEnum { | |
| 11 | + NONE(0, "未转单"), | |
| 12 | + APPROVED(1, "转单申请通过"), | |
| 13 | + PENDING(2, "转单申请中"), | |
| 14 | + REJECTED(3, "转单申请拒绝"); | |
| 15 | + | |
| 16 | + private final int code; | |
| 17 | + private final String desc; | |
| 18 | + | |
| 19 | + TransStatusEnum(int code, String desc) { | |
| 20 | + this.code = code; | |
| 21 | + this.desc = desc; | |
| 22 | + } | |
| 23 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/exception/BizException.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/exception/BizException.java | |
| 1 | +package com.diligrp.rider.common.exception; | |
| 2 | + | |
| 3 | +import lombok.Getter; | |
| 4 | + | |
| 5 | +@Getter | |
| 6 | +public class BizException extends RuntimeException { | |
| 7 | + private final int code; | |
| 8 | + | |
| 9 | + public BizException(String message) { | |
| 10 | + super(message); | |
| 11 | + this.code = 1; | |
| 12 | + } | |
| 13 | + | |
| 14 | + public BizException(int code, String message) { | |
| 15 | + super(message); | |
| 16 | + this.code = code; | |
| 17 | + } | |
| 18 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/exception/GlobalExceptionHandler.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/exception/GlobalExceptionHandler.java | |
| 1 | +package com.diligrp.rider.common.exception; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import lombok.extern.slf4j.Slf4j; | |
| 5 | +import org.springframework.validation.BindException; | |
| 6 | +import org.springframework.web.bind.MethodArgumentNotValidException; | |
| 7 | +import org.springframework.web.bind.annotation.ExceptionHandler; | |
| 8 | +import org.springframework.web.bind.annotation.RestControllerAdvice; | |
| 9 | + | |
| 10 | +@Slf4j | |
| 11 | +@RestControllerAdvice | |
| 12 | +public class GlobalExceptionHandler { | |
| 13 | + | |
| 14 | + @ExceptionHandler(BizException.class) | |
| 15 | + public Result<Void> handleBizException(BizException e) { | |
| 16 | + return Result.error(e.getCode(), e.getMessage()); | |
| 17 | + } | |
| 18 | + | |
| 19 | + @ExceptionHandler(MethodArgumentNotValidException.class) | |
| 20 | + public Result<Void> handleValidException(MethodArgumentNotValidException e) { | |
| 21 | + String msg = e.getBindingResult().getFieldErrors().stream() | |
| 22 | + .map(f -> f.getField() + ": " + f.getDefaultMessage()) | |
| 23 | + .findFirst().orElse("参数错误"); | |
| 24 | + return Result.error(400, msg); | |
| 25 | + } | |
| 26 | + | |
| 27 | + @ExceptionHandler(BindException.class) | |
| 28 | + public Result<Void> handleBindException(BindException e) { | |
| 29 | + String msg = e.getFieldErrors().stream() | |
| 30 | + .map(f -> f.getField() + ": " + f.getDefaultMessage()) | |
| 31 | + .findFirst().orElse("参数错误"); | |
| 32 | + return Result.error(400, msg); | |
| 33 | + } | |
| 34 | + | |
| 35 | + @ExceptionHandler(Exception.class) | |
| 36 | + public Result<Void> handleException(Exception e) { | |
| 37 | + log.error("系统异常", e); | |
| 38 | + return Result.error(500, "系统内部错误"); | |
| 39 | + } | |
| 40 | +} | ... | ... |
src/main/java/com/diligrp/rider/common/result/Result.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/common/result/Result.java | |
| 1 | +package com.diligrp.rider.common.result; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +@Data | |
| 6 | +public class Result<T> { | |
| 7 | + private int code; | |
| 8 | + private String msg; | |
| 9 | + private T data; | |
| 10 | + | |
| 11 | + private Result(int code, String msg, T data) { | |
| 12 | + this.code = code; | |
| 13 | + this.msg = msg; | |
| 14 | + this.data = data; | |
| 15 | + } | |
| 16 | + | |
| 17 | + public static <T> Result<T> success(T data) { | |
| 18 | + return new Result<>(0, "success", data); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public static <T> Result<T> success() { | |
| 22 | + return new Result<>(0, "success", null); | |
| 23 | + } | |
| 24 | + | |
| 25 | + public static <T> Result<T> error(int code, String msg) { | |
| 26 | + return new Result<>(code, msg, null); | |
| 27 | + } | |
| 28 | + | |
| 29 | + public static <T> Result<T> error(String msg) { | |
| 30 | + return new Result<>(1, msg, null); | |
| 31 | + } | |
| 32 | +} | ... | ... |
src/main/java/com/diligrp/rider/config/AuthInterceptor.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/config/AuthInterceptor.java | |
| 1 | +package com.diligrp.rider.config; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 4 | +import com.diligrp.rider.common.exception.BizException; | |
| 5 | +import com.diligrp.rider.common.result.Result; | |
| 6 | +import com.diligrp.rider.entity.Substation; | |
| 7 | +import com.diligrp.rider.mapper.SubstationMapper; | |
| 8 | +import jakarta.servlet.http.HttpServletRequest; | |
| 9 | +import jakarta.servlet.http.HttpServletResponse; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import org.springframework.stereotype.Component; | |
| 12 | +import org.springframework.util.StringUtils; | |
| 13 | +import org.springframework.web.servlet.HandlerInterceptor; | |
| 14 | + | |
| 15 | +@Component | |
| 16 | +@RequiredArgsConstructor | |
| 17 | +public class AuthInterceptor implements HandlerInterceptor { | |
| 18 | + | |
| 19 | + private final JwtUtil jwtUtil; | |
| 20 | + private final ObjectMapper objectMapper; | |
| 21 | + private final SubstationMapper substationMapper; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | |
| 25 | + String token = request.getHeader("Authorization"); | |
| 26 | + if (!StringUtils.hasText(token)) { | |
| 27 | + token = request.getParameter("token"); | |
| 28 | + } | |
| 29 | + if (!StringUtils.hasText(token)) { | |
| 30 | + writeError(response, 700, "请先登录"); | |
| 31 | + return false; | |
| 32 | + } | |
| 33 | + if (token.startsWith("Bearer ")) { | |
| 34 | + token = token.substring(7); | |
| 35 | + } | |
| 36 | + | |
| 37 | + String path = request.getRequestURI(); | |
| 38 | + | |
| 39 | + try { | |
| 40 | + io.jsonwebtoken.Claims claims = jwtUtil.getAdminClaims(token); | |
| 41 | + | |
| 42 | + if (claims.get("adminId") != null) { | |
| 43 | + // 管理员 token | |
| 44 | + Long adminId = ((Number) claims.get("adminId")).longValue(); | |
| 45 | + String role = (String) claims.get("role"); | |
| 46 | + | |
| 47 | + // /api/platform/** 仅超级管理员可访问 | |
| 48 | + if (path.startsWith("/api/platform/") && !"admin".equals(role)) { | |
| 49 | + writeError(response, 403, "权限不足,需要超级管理员权限"); | |
| 50 | + return false; | |
| 51 | + } | |
| 52 | + | |
| 53 | + request.setAttribute("adminId", adminId); | |
| 54 | + request.setAttribute("role", role); | |
| 55 | + | |
| 56 | + // 分站管理员:注入 cityId 供 Service 层做城市隔离 | |
| 57 | + if ("substation".equals(role)) { | |
| 58 | + Substation sub = substationMapper.selectById(adminId); | |
| 59 | + if (sub != null) { | |
| 60 | + request.setAttribute("cityId", sub.getCityId()); | |
| 61 | + } | |
| 62 | + } | |
| 63 | + | |
| 64 | + } else if (claims.get("riderId") != null) { | |
| 65 | + // 骑手 token | |
| 66 | + request.setAttribute("riderId", ((Number) claims.get("riderId")).longValue()); | |
| 67 | + if (claims.get("cityId") != null) { | |
| 68 | + request.setAttribute("cityId", ((Number) claims.get("cityId")).longValue()); | |
| 69 | + } | |
| 70 | + } else { | |
| 71 | + writeError(response, 700, "登录状态失效,请重新登录"); | |
| 72 | + return false; | |
| 73 | + } | |
| 74 | + } catch (BizException e) { | |
| 75 | + writeError(response, e.getCode(), e.getMessage()); | |
| 76 | + return false; | |
| 77 | + } | |
| 78 | + return true; | |
| 79 | + } | |
| 80 | + | |
| 81 | + private void writeError(HttpServletResponse response, int code, String msg) throws Exception { | |
| 82 | + response.setContentType("application/json;charset=UTF-8"); | |
| 83 | + response.getWriter().write(objectMapper.writeValueAsString(Result.error(code, msg))); | |
| 84 | + } | |
| 85 | +} | ... | ... |
src/main/java/com/diligrp/rider/config/CorsConfig.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/config/CorsConfig.java | |
| 1 | +package com.diligrp.rider.config; | |
| 2 | + | |
| 3 | +import org.springframework.context.annotation.Bean; | |
| 4 | +import org.springframework.context.annotation.Configuration; | |
| 5 | +import org.springframework.web.cors.CorsConfiguration; | |
| 6 | +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; | |
| 7 | +import org.springframework.web.filter.CorsFilter; | |
| 8 | + | |
| 9 | +@Configuration | |
| 10 | +public class CorsConfig { | |
| 11 | + | |
| 12 | + @Bean | |
| 13 | + public CorsFilter corsFilter() { | |
| 14 | + CorsConfiguration config = new CorsConfiguration(); | |
| 15 | + // 允许的来源(开发环境放开,生产环境改为具体域名) | |
| 16 | + config.addAllowedOriginPattern("*"); | |
| 17 | + config.addAllowedHeader("*"); | |
| 18 | + config.addAllowedMethod("*"); | |
| 19 | + config.setAllowCredentials(true); | |
| 20 | + config.setMaxAge(3600L); | |
| 21 | + | |
| 22 | + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | |
| 23 | + source.registerCorsConfiguration("/**", config); | |
| 24 | + return new CorsFilter(source); | |
| 25 | + } | |
| 26 | +} | ... | ... |
src/main/java/com/diligrp/rider/config/JwtUtil.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/config/JwtUtil.java | |
| 1 | +package com.diligrp.rider.config; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.exception.BizException; | |
| 4 | +import io.jsonwebtoken.*; | |
| 5 | +import io.jsonwebtoken.security.Keys; | |
| 6 | +import org.springframework.beans.factory.annotation.Value; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +import java.nio.charset.StandardCharsets; | |
| 10 | +import java.security.Key; | |
| 11 | +import java.util.Date; | |
| 12 | +import java.util.HashMap; | |
| 13 | +import java.util.Map; | |
| 14 | + | |
| 15 | +@Component | |
| 16 | +public class JwtUtil { | |
| 17 | + | |
| 18 | + @Value("${jwt.secret}") | |
| 19 | + private String secret; | |
| 20 | + | |
| 21 | + @Value("${jwt.expire}") | |
| 22 | + private long expire; | |
| 23 | + | |
| 24 | + private Key getKey() { | |
| 25 | + return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); | |
| 26 | + } | |
| 27 | + | |
| 28 | + public String generateToken(Long riderId) { | |
| 29 | + Map<String, Object> claims = new HashMap<>(); | |
| 30 | + claims.put("riderId", riderId); | |
| 31 | + return buildToken(claims); | |
| 32 | + } | |
| 33 | + | |
| 34 | + /** 生成骑手 token,携带 riderId + cityId */ | |
| 35 | + public String generateRiderToken(Long riderId, Long cityId) { | |
| 36 | + Map<String, Object> claims = new HashMap<>(); | |
| 37 | + claims.put("riderId", riderId); | |
| 38 | + claims.put("cityId", cityId); | |
| 39 | + return buildToken(claims); | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** 生成管理员 token,携带 role 和 id */ | |
| 43 | + public String generateAdminToken(Long adminId, String role) { | |
| 44 | + Map<String, Object> claims = new HashMap<>(); | |
| 45 | + claims.put("adminId", adminId); | |
| 46 | + claims.put("role", role); | |
| 47 | + return buildToken(claims); | |
| 48 | + } | |
| 49 | + | |
| 50 | + private String buildToken(Map<String, Object> claims) { | |
| 51 | + return Jwts.builder() | |
| 52 | + .setClaims(claims) | |
| 53 | + .setIssuedAt(new Date()) | |
| 54 | + .setExpiration(new Date(System.currentTimeMillis() + expire * 1000)) | |
| 55 | + .signWith(getKey(), SignatureAlgorithm.HS256) | |
| 56 | + .compact(); | |
| 57 | + } | |
| 58 | + | |
| 59 | + public Long getRiderIdFromToken(String token) { | |
| 60 | + try { | |
| 61 | + Claims claims = parseClaims(token); | |
| 62 | + return ((Number) claims.get("riderId")).longValue(); | |
| 63 | + } catch (ExpiredJwtException e) { | |
| 64 | + throw new BizException(700, "登录状态已过期,请重新登录"); | |
| 65 | + } catch (Exception e) { | |
| 66 | + throw new BizException(700, "登录状态失效,请重新登录"); | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + public Claims getAdminClaims(String token) { | |
| 71 | + try { | |
| 72 | + return parseClaims(token); | |
| 73 | + } catch (ExpiredJwtException e) { | |
| 74 | + throw new BizException(700, "登录状态已过期,请重新登录"); | |
| 75 | + } catch (Exception e) { | |
| 76 | + throw new BizException(700, "登录状态失效,请重新登录"); | |
| 77 | + } | |
| 78 | + } | |
| 79 | + | |
| 80 | + private Claims parseClaims(String token) { | |
| 81 | + return Jwts.parserBuilder() | |
| 82 | + .setSigningKey(getKey()) | |
| 83 | + .build() | |
| 84 | + .parseClaimsJws(token) | |
| 85 | + .getBody(); | |
| 86 | + } | |
| 87 | +} | ... | ... |
src/main/java/com/diligrp/rider/config/OpenApiInterceptor.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/config/OpenApiInterceptor.java | |
| 1 | +package com.diligrp.rider.config; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.service.OpenAppService; | |
| 6 | +import jakarta.servlet.http.HttpServletRequest; | |
| 7 | +import jakarta.servlet.http.HttpServletResponse; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.stereotype.Component; | |
| 10 | +import org.springframework.util.StringUtils; | |
| 11 | +import org.springframework.web.servlet.HandlerInterceptor; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * 开放平台签名拦截器 | |
| 15 | + * 验证请求头:X-App-Key, X-Timestamp, X-Nonce, X-Sign | |
| 16 | + */ | |
| 17 | +@Component | |
| 18 | +@RequiredArgsConstructor | |
| 19 | +public class OpenApiInterceptor implements HandlerInterceptor { | |
| 20 | + | |
| 21 | + private final OpenAppService openAppService; | |
| 22 | + private final ObjectMapper objectMapper; | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | |
| 26 | + String appKey = request.getHeader("X-App-Key"); | |
| 27 | + String timestamp = request.getHeader("X-Timestamp"); | |
| 28 | + String nonce = request.getHeader("X-Nonce"); | |
| 29 | + String sign = request.getHeader("X-Sign"); | |
| 30 | + | |
| 31 | + if (!StringUtils.hasText(appKey) || !StringUtils.hasText(timestamp) | |
| 32 | + || !StringUtils.hasText(nonce) || !StringUtils.hasText(sign)) { | |
| 33 | + writeError(response, 401, "缺少认证头信息(X-App-Key/X-Timestamp/X-Nonce/X-Sign)"); | |
| 34 | + return false; | |
| 35 | + } | |
| 36 | + | |
| 37 | + boolean valid = openAppService.verifySign(appKey, timestamp, nonce, sign); | |
| 38 | + if (!valid) { | |
| 39 | + writeError(response, 401, "签名验证失败或已过期"); | |
| 40 | + return false; | |
| 41 | + } | |
| 42 | + return true; | |
| 43 | + } | |
| 44 | + | |
| 45 | + private void writeError(HttpServletResponse response, int code, String msg) throws Exception { | |
| 46 | + response.setContentType("application/json;charset=UTF-8"); | |
| 47 | + response.setStatus(200); | |
| 48 | + response.getWriter().write(objectMapper.writeValueAsString(Result.error(code, msg))); | |
| 49 | + } | |
| 50 | +} | ... | ... |
src/main/java/com/diligrp/rider/config/WebMvcConfig.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/config/WebMvcConfig.java | |
| 1 | +package com.diligrp.rider.config; | |
| 2 | + | |
| 3 | +import lombok.RequiredArgsConstructor; | |
| 4 | +import org.springframework.context.annotation.Configuration; | |
| 5 | +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | |
| 6 | +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |
| 7 | + | |
| 8 | +@Configuration | |
| 9 | +@RequiredArgsConstructor | |
| 10 | +public class WebMvcConfig implements WebMvcConfigurer { | |
| 11 | + | |
| 12 | + private final AuthInterceptor authInterceptor; | |
| 13 | + private final OpenApiInterceptor openApiInterceptor; | |
| 14 | + | |
| 15 | + @Override | |
| 16 | + public void addInterceptors(InterceptorRegistry registry) { | |
| 17 | + // JWT 鉴权:骑手端、管理端、平台端 | |
| 18 | + registry.addInterceptor(authInterceptor) | |
| 19 | + .addPathPatterns("/api/rider/**", "/api/admin/**", "/api/platform/**") | |
| 20 | + .excludePathPatterns( | |
| 21 | + "/api/rider/login/**", | |
| 22 | + "/api/rider/apply", | |
| 23 | + "/api/merchant/enter", | |
| 24 | + "/api/admin/auth/login" // 管理员登录无需鉴权 | |
| 25 | + ); | |
| 26 | + | |
| 27 | + // 开放平台签名鉴权:/api/open/** 需要 AppKey+签名 | |
| 28 | + registry.addInterceptor(openApiInterceptor) | |
| 29 | + .addPathPatterns("/api/open/**"); | |
| 30 | + | |
| 31 | + // /api/delivery/fee/** 对内中台接口,内网调用,不做鉴权 | |
| 32 | + } | |
| 33 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/AdminAuthController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/AdminAuthController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.AdminLoginDTO; | |
| 5 | +import com.diligrp.rider.service.impl.AdminAuthServiceImpl; | |
| 6 | +import com.diligrp.rider.vo.AdminLoginVO; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +@RestController | |
| 12 | +@RequestMapping("/api/admin/auth") | |
| 13 | +@RequiredArgsConstructor | |
| 14 | +public class AdminAuthController { | |
| 15 | + | |
| 16 | + private final AdminAuthServiceImpl adminAuthService; | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 管理员登录(超级管理员 + 分站管理员统一入口) | |
| 20 | + * 请求体:{ "account": "gz_admin", "pass": "admin123", "role": "substation" } | |
| 21 | + * role 可选值:admin(超级管理员)| substation(分站管理员,默认) | |
| 22 | + */ | |
| 23 | + @PostMapping("/login") | |
| 24 | + public Result<AdminLoginVO> login(@Valid @RequestBody AdminLoginDTO dto) { | |
| 25 | + return Result.success(adminAuthService.login(dto)); | |
| 26 | + } | |
| 27 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/AdminRefundController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/AdminRefundController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.entity.OrderRefundReason; | |
| 5 | +import com.diligrp.rider.entity.OrderRefundRecord; | |
| 6 | +import com.diligrp.rider.service.RefundService; | |
| 7 | +import com.diligrp.rider.service.RiderEvaluateService; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +import java.util.List; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * 平台/分站管理:退款审核 + 评价管理 | |
| 15 | + */ | |
| 16 | +@RestController | |
| 17 | +@RequestMapping("/api/admin") | |
| 18 | +@RequiredArgsConstructor | |
| 19 | +public class AdminRefundController { | |
| 20 | + | |
| 21 | + private final RefundService refundService; | |
| 22 | + private final RiderEvaluateService evaluateService; | |
| 23 | + | |
| 24 | + /** 退款原因列表管理(全部角色) */ | |
| 25 | + @GetMapping("/refund/reasons") | |
| 26 | + public Result<List<OrderRefundReason>> reasons(@RequestParam(defaultValue = "0") int role) { | |
| 27 | + return Result.success(refundService.getReasons(role)); | |
| 28 | + } | |
| 29 | + | |
| 30 | + /** 查看订单退款记录 */ | |
| 31 | + @GetMapping("/refund/record") | |
| 32 | + public Result<OrderRefundRecord> record(@RequestParam Long orderId) { | |
| 33 | + return Result.success(refundService.getByOrderId(orderId)); | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 审核退款申请 | |
| 38 | + * status=1 通过(退款成功) | |
| 39 | + * status=2 拒绝 | |
| 40 | + */ | |
| 41 | + @PostMapping("/refund/handle") | |
| 42 | + public Result<Void> handle( | |
| 43 | + @RequestParam Long recordId, | |
| 44 | + @RequestParam int status, | |
| 45 | + @RequestParam(required = false, defaultValue = "") String remark) { | |
| 46 | + refundService.handleRefund(recordId, status, remark); | |
| 47 | + return Result.success(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + /** 骑手评价列表(运营查看) */ | |
| 51 | + @GetMapping("/evaluate/list") | |
| 52 | + public Result<List<?>> evaluateList( | |
| 53 | + @RequestParam Long riderId, | |
| 54 | + @RequestParam(defaultValue = "0") int type, | |
| 55 | + @RequestParam(defaultValue = "1") int page) { | |
| 56 | + return Result.success(evaluateService.getRiderEvaluates(riderId, type, page)); | |
| 57 | + } | |
| 58 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/AdminRiderController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/AdminRiderController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.dto.AdminRiderAddDTO; | |
| 6 | +import com.diligrp.rider.entity.Orders; | |
| 7 | +import com.diligrp.rider.entity.Rider; | |
| 8 | +import com.diligrp.rider.mapper.OrdersMapper; | |
| 9 | +import com.diligrp.rider.service.AdminRiderService; | |
| 10 | +import jakarta.servlet.http.HttpServletRequest; | |
| 11 | +import jakarta.validation.Valid; | |
| 12 | +import lombok.RequiredArgsConstructor; | |
| 13 | +import org.springframework.web.bind.annotation.*; | |
| 14 | + | |
| 15 | +import java.util.List; | |
| 16 | + | |
| 17 | +@RestController | |
| 18 | +@RequestMapping("/api/admin/rider") | |
| 19 | +@RequiredArgsConstructor | |
| 20 | +public class AdminRiderController { | |
| 21 | + | |
| 22 | + private final AdminRiderService adminRiderService; | |
| 23 | + private final OrdersMapper ordersMapper; | |
| 24 | + | |
| 25 | + /** 手动新增骑手(分站管理员自动绑定本城市) */ | |
| 26 | + @PostMapping("/add") | |
| 27 | + public Result<Void> add(@Valid @RequestBody AdminRiderAddDTO dto, HttpServletRequest request) { | |
| 28 | + Long cityId = "substation".equals(request.getAttribute("role")) | |
| 29 | + ? (Long) request.getAttribute("cityId") | |
| 30 | + : dto.getCityId(); | |
| 31 | + adminRiderService.add(dto, cityId); | |
| 32 | + return Result.success(); | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** 骑手列表(分站管理员仅看本城市,超管看全部) */ | |
| 36 | + @GetMapping("/list") | |
| 37 | + public Result<List<Rider>> list( | |
| 38 | + @RequestParam(required = false) String keyword, | |
| 39 | + @RequestParam(required = false) Integer userStatus, | |
| 40 | + HttpServletRequest request) { | |
| 41 | + Long cityId = "substation".equals(request.getAttribute("role")) | |
| 42 | + ? (Long) request.getAttribute("cityId") | |
| 43 | + : null; | |
| 44 | + return Result.success(adminRiderService.list(keyword, userStatus, cityId)); | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** 指派骑手候选列表 */ | |
| 48 | + @GetMapping("/order/candidates") | |
| 49 | + public Result<List<Rider>> designateCandidates(@RequestParam Long orderId, HttpServletRequest request) { | |
| 50 | + Long cityId = "substation".equals(request.getAttribute("role")) | |
| 51 | + ? (Long) request.getAttribute("cityId") | |
| 52 | + : null; | |
| 53 | + return Result.success(adminRiderService.designateCandidates(orderId, cityId)); | |
| 54 | + } | |
| 55 | + | |
| 56 | + /** 审核骑手:status=0拒绝 1通过 */ | |
| 57 | + @PostMapping("/setStatus") | |
| 58 | + public Result<Void> setStatus(@RequestParam Long riderId, @RequestParam int status) { | |
| 59 | + adminRiderService.setStatus(riderId, status); | |
| 60 | + return Result.success(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** 设置骑手等级,levelId 为空时使用默认等级 */ | |
| 64 | + @PostMapping("/setLevel") | |
| 65 | + public Result<Void> setLevel(@RequestParam Long riderId, | |
| 66 | + @RequestParam(required = false) Long levelId, | |
| 67 | + HttpServletRequest request) { | |
| 68 | + Long cityId = "substation".equals(request.getAttribute("role")) | |
| 69 | + ? (Long) request.getAttribute("cityId") | |
| 70 | + : null; | |
| 71 | + adminRiderService.setLevel(riderId, levelId, cityId); | |
| 72 | + return Result.success(); | |
| 73 | + } | |
| 74 | + | |
| 75 | + /** 启用/禁用骑手账号:status=0禁用 1启用 */ | |
| 76 | + @PostMapping("/setEnableStatus") | |
| 77 | + public Result<Void> setEnableStatus(@RequestParam Long riderId, @RequestParam int status) { | |
| 78 | + adminRiderService.setEnableStatus(riderId, status); | |
| 79 | + return Result.success(); | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** 切换骑手类型:type=1兼职 2全职 */ | |
| 83 | + @PostMapping("/setType") | |
| 84 | + public Result<Void> setType(@RequestParam Long riderId, @RequestParam int type) { | |
| 85 | + adminRiderService.setType(riderId, type); | |
| 86 | + return Result.success(); | |
| 87 | + } | |
| 88 | + | |
| 89 | + /** 指派骑手接单 */ | |
| 90 | + @PostMapping("/order/designate") | |
| 91 | + public Result<Void> designate(@RequestParam Long orderId, @RequestParam Long riderId) { | |
| 92 | + adminRiderService.designate(orderId, riderId); | |
| 93 | + return Result.success(); | |
| 94 | + } | |
| 95 | + | |
| 96 | + /** 处理转单申请:trans=1通过 3拒绝 */ | |
| 97 | + @PostMapping("/order/setTrans") | |
| 98 | + public Result<Void> setTrans(@RequestParam Long orderId, @RequestParam int trans) { | |
| 99 | + adminRiderService.setTrans(orderId, trans); | |
| 100 | + return Result.success(); | |
| 101 | + } | |
| 102 | + | |
| 103 | + /** | |
| 104 | + * 订单列表(分站管理员查看本城市订单 / 超管查看所有) | |
| 105 | + * 分站管理员:cityId 从 token 自动注入,无需传参 | |
| 106 | + * 超管:可传 cityId 筛选某城市 | |
| 107 | + */ | |
| 108 | + @GetMapping("/order/list") | |
| 109 | + public Result<List<Orders>> orderList( | |
| 110 | + @RequestParam(required = false) Integer status, | |
| 111 | + @RequestParam(required = false) Integer isTrans, | |
| 112 | + @RequestParam(required = false) String keyword, | |
| 113 | + @RequestParam(defaultValue = "1") int page, | |
| 114 | + HttpServletRequest request) { | |
| 115 | + Long cityId = (Long) request.getAttribute("cityId"); | |
| 116 | + String role = (String) request.getAttribute("role"); | |
| 117 | + | |
| 118 | + LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<Orders>() | |
| 119 | + .eq(Orders::getIsDel, 0) | |
| 120 | + .orderByDesc(Orders::getId); | |
| 121 | + | |
| 122 | + if ("substation".equals(role) && cityId != null) { | |
| 123 | + wrapper.eq(Orders::getCityId, cityId); | |
| 124 | + } | |
| 125 | + if (status != null) wrapper.eq(Orders::getStatus, status); | |
| 126 | + if (isTrans != null) wrapper.eq(Orders::getIsTrans, isTrans); | |
| 127 | + if (keyword != null && !keyword.isBlank()) { | |
| 128 | + wrapper.and(w -> w.like(Orders::getOrderNo, keyword) | |
| 129 | + .or().like(Orders::getOutOrderNo, keyword) | |
| 130 | + .or().like(Orders::getRecipName, keyword) | |
| 131 | + .or().like(Orders::getRecipPhone, keyword)); | |
| 132 | + } | |
| 133 | + int offset = (page - 1) * 20; | |
| 134 | + wrapper.last("LIMIT " + offset + ",20"); | |
| 135 | + | |
| 136 | + return Result.success(ordersMapper.selectList(wrapper)); | |
| 137 | + } | |
| 138 | + | |
| 139 | + /** | |
| 140 | + * 配送订单列表(平台管理员查看外部系统推入的订单) | |
| 141 | + * 按 appKey / outOrderNo 查询 | |
| 142 | + */ | |
| 143 | + @GetMapping("/order/delivery/list") | |
| 144 | + public Result<List<Orders>> deliveryOrderList( | |
| 145 | + @RequestParam(required = false) String appKey, | |
| 146 | + @RequestParam(required = false) String outOrderNo, | |
| 147 | + @RequestParam(required = false) Integer status, | |
| 148 | + @RequestParam(defaultValue = "1") int page) { | |
| 149 | + LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<Orders>() | |
| 150 | + .eq(Orders::getIsDel, 0) | |
| 151 | + .orderByDesc(Orders::getId); | |
| 152 | + if (appKey != null && !appKey.isBlank()) wrapper.eq(Orders::getAppKey, appKey); | |
| 153 | + if (outOrderNo != null && !outOrderNo.isBlank()) wrapper.like(Orders::getOutOrderNo, outOrderNo); | |
| 154 | + if (status != null) wrapper.eq(Orders::getStatus, status); | |
| 155 | + int offset = (page - 1) * 20; | |
| 156 | + wrapper.last("LIMIT " + offset + ",20"); | |
| 157 | + return Result.success(ordersMapper.selectList(wrapper)); | |
| 158 | + } | |
| 159 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/AdminRiderLevelController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/AdminRiderLevelController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.exception.BizException; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.dto.AdminRiderLevelSaveDTO; | |
| 6 | +import com.diligrp.rider.entity.RiderLevel; | |
| 7 | +import com.diligrp.rider.service.AdminRiderLevelService; | |
| 8 | +import jakarta.servlet.http.HttpServletRequest; | |
| 9 | +import jakarta.validation.Valid; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import org.springframework.web.bind.annotation.*; | |
| 12 | + | |
| 13 | +import java.util.List; | |
| 14 | + | |
| 15 | +@RestController | |
| 16 | +@RequestMapping("/api/admin/rider/level") | |
| 17 | +@RequiredArgsConstructor | |
| 18 | +public class AdminRiderLevelController { | |
| 19 | + | |
| 20 | + private final AdminRiderLevelService adminRiderLevelService; | |
| 21 | + | |
| 22 | + @GetMapping("/list") | |
| 23 | + public Result<List<RiderLevel>> list(@RequestParam(required = false) Long cityId, HttpServletRequest request) { | |
| 24 | + return Result.success(adminRiderLevelService.list(resolveCityId(cityId, request))); | |
| 25 | + } | |
| 26 | + | |
| 27 | + @PostMapping("/add") | |
| 28 | + public Result<Void> add(@Valid @RequestBody AdminRiderLevelSaveDTO dto, HttpServletRequest request) { | |
| 29 | + adminRiderLevelService.add(dto, resolveCityId(dto.getCityId(), request)); | |
| 30 | + return Result.success(); | |
| 31 | + } | |
| 32 | + | |
| 33 | + @PutMapping("/edit") | |
| 34 | + public Result<Void> edit(@Valid @RequestBody AdminRiderLevelSaveDTO dto, HttpServletRequest request) { | |
| 35 | + adminRiderLevelService.edit(dto, resolveCityId(dto.getCityId(), request)); | |
| 36 | + return Result.success(); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @PostMapping("/setDefault") | |
| 40 | + public Result<Void> setDefault(@RequestParam Long id, | |
| 41 | + @RequestParam(required = false) Long cityId, | |
| 42 | + HttpServletRequest request) { | |
| 43 | + adminRiderLevelService.setDefault(id, resolveCityId(cityId, request)); | |
| 44 | + return Result.success(); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @DeleteMapping("/del") | |
| 48 | + public Result<Void> delete(@RequestParam Long id, | |
| 49 | + @RequestParam(required = false) Long cityId, | |
| 50 | + HttpServletRequest request) { | |
| 51 | + adminRiderLevelService.delete(id, resolveCityId(cityId, request)); | |
| 52 | + return Result.success(); | |
| 53 | + } | |
| 54 | + | |
| 55 | + private Long resolveCityId(Long cityId, HttpServletRequest request) { | |
| 56 | + if ("substation".equals(request.getAttribute("role"))) { | |
| 57 | + Long requestCityId = (Long) request.getAttribute("cityId"); | |
| 58 | + if (requestCityId == null || requestCityId < 1) { | |
| 59 | + throw new BizException("当前账号未绑定城市"); | |
| 60 | + } | |
| 61 | + return requestCityId; | |
| 62 | + } | |
| 63 | + if (cityId == null || cityId < 1) { | |
| 64 | + throw new BizException("城市不能为空"); | |
| 65 | + } | |
| 66 | + return cityId; | |
| 67 | + } | |
| 68 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/DeliveryFeeController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/DeliveryFeeController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 5 | +import com.diligrp.rider.service.DeliveryFeeService; | |
| 6 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 配送费计算接口(对内中台核心接口) | |
| 13 | + * 电商系统下单前调用此接口获取配送费 | |
| 14 | + */ | |
| 15 | +@RestController | |
| 16 | +@RequestMapping("/api/delivery/fee") | |
| 17 | +@RequiredArgsConstructor | |
| 18 | +public class DeliveryFeeController { | |
| 19 | + | |
| 20 | + private final DeliveryFeeService deliveryFeeService; | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 计算配送费 | |
| 24 | + * 入参:cityId, orderType, startLng, startLat, endLng, endLat, weight, serviceTime | |
| 25 | + * 出参:各项费用明细 + 总费用 + 预计送达时间 | |
| 26 | + */ | |
| 27 | + @PostMapping("/calc") | |
| 28 | + public Result<DeliveryFeeResultVO> calc(@Valid @RequestBody DeliveryFeeCalcDTO dto) { | |
| 29 | + return Result.success(deliveryFeeService.calcFee(dto)); | |
| 30 | + } | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 检查城市是否开通某服务 | |
| 34 | + * orderType: 1=帮我送 2=帮我取 6=外卖配送 | |
| 35 | + */ | |
| 36 | + @GetMapping("/check") | |
| 37 | + public Result<Boolean> check(@RequestParam Long cityId, @RequestParam int orderType) { | |
| 38 | + return Result.success(deliveryFeeService.isServiceEnabled(cityId, orderType)); | |
| 39 | + } | |
| 40 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/OpenApiController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/OpenApiController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.exception.BizException; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 6 | +import com.diligrp.rider.dto.OpenDeliveryFeeCalcDTO; | |
| 7 | +import com.diligrp.rider.entity.OpenApp; | |
| 8 | +import com.diligrp.rider.service.DeliveryFeeService; | |
| 9 | +import com.diligrp.rider.service.OpenAppService; | |
| 10 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 11 | +import jakarta.servlet.http.HttpServletRequest; | |
| 12 | +import jakarta.validation.Valid; | |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 14 | +import org.springframework.web.bind.annotation.*; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 开放平台对外接口 | |
| 18 | + * 路径前缀 /api/open/** 受 OpenApiInterceptor 签名鉴权保护 | |
| 19 | + * 第三方系统需携带 X-App-Key / X-Timestamp / X-Nonce / X-Sign 请求头 | |
| 20 | + */ | |
| 21 | +@RestController | |
| 22 | +@RequestMapping("/api/open") | |
| 23 | +@RequiredArgsConstructor | |
| 24 | +public class OpenApiController { | |
| 25 | + | |
| 26 | + private final DeliveryFeeService deliveryFeeService; | |
| 27 | + private final OpenAppService openAppService; | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 计算配送费(对外开放版) | |
| 31 | + * cityId 由 AppKey 绑定的租户自动确定,不信任外部传参 | |
| 32 | + */ | |
| 33 | + @PostMapping("/delivery/fee/calc") | |
| 34 | + public Result<DeliveryFeeResultVO> calcFee(@Valid @RequestBody OpenDeliveryFeeCalcDTO dto, | |
| 35 | + HttpServletRequest request) { | |
| 36 | + DeliveryFeeCalcDTO calcDTO = new DeliveryFeeCalcDTO(); | |
| 37 | + calcDTO.setCityId(resolveCityId(request)); | |
| 38 | + calcDTO.setOrderType(dto.getOrderType()); | |
| 39 | + calcDTO.setStartLng(dto.getStartLng()); | |
| 40 | + calcDTO.setStartLat(dto.getStartLat()); | |
| 41 | + calcDTO.setEndLng(dto.getEndLng()); | |
| 42 | + calcDTO.setEndLat(dto.getEndLat()); | |
| 43 | + calcDTO.setWeight(dto.getWeight()); | |
| 44 | + calcDTO.setPieces(dto.getPieces()); | |
| 45 | + calcDTO.setServiceTime(dto.getServiceTime()); | |
| 46 | + return Result.success(deliveryFeeService.calcFee(calcDTO)); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 检查城市服务是否开通 | |
| 51 | + * cityId 由 AppKey 绑定的租户自动确定 | |
| 52 | + */ | |
| 53 | + @GetMapping("/delivery/check") | |
| 54 | + public Result<Boolean> check(@RequestParam int orderType, HttpServletRequest request) { | |
| 55 | + return Result.success(deliveryFeeService.isServiceEnabled(resolveCityId(request), orderType)); | |
| 56 | + } | |
| 57 | + | |
| 58 | + private Long resolveCityId(HttpServletRequest request) { | |
| 59 | + String appKey = request.getHeader("X-App-Key"); | |
| 60 | + OpenApp app = openAppService.getByAppKey(appKey); | |
| 61 | + if (app == null || app.getStatus() != 1) { | |
| 62 | + throw new BizException("应用不存在或已禁用"); | |
| 63 | + } | |
| 64 | + if (app.getCityId() == null || app.getCityId() < 1) { | |
| 65 | + throw new BizException("该应用未绑定租户,请联系平台管理员"); | |
| 66 | + } | |
| 67 | + return app.getCityId(); | |
| 68 | + } | |
| 69 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/OpenDeliveryOrderController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/OpenDeliveryOrderController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.DeliveryOrderCreateDTO; | |
| 5 | +import com.diligrp.rider.service.DeliveryOrderService; | |
| 6 | +import com.diligrp.rider.vo.DeliveryOrderCreateVO; | |
| 7 | +import jakarta.servlet.http.HttpServletRequest; | |
| 8 | +import jakarta.validation.Valid; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import org.springframework.web.bind.annotation.*; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 开放平台:配送订单接口 | |
| 14 | + * 路径 /api/open/** 由 OpenApiInterceptor 做签名鉴权 | |
| 15 | + * X-App-Key 会由拦截器写入 request attribute | |
| 16 | + */ | |
| 17 | +@RestController | |
| 18 | +@RequestMapping("/api/open/delivery/order") | |
| 19 | +@RequiredArgsConstructor | |
| 20 | +public class OpenDeliveryOrderController { | |
| 21 | + | |
| 22 | + private final DeliveryOrderService deliveryOrderService; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 创建配送订单(推单) | |
| 26 | + * 外部系统在自己完成支付后调用此接口,中台接管配送 | |
| 27 | + */ | |
| 28 | + @PostMapping("/create") | |
| 29 | + public Result<DeliveryOrderCreateVO> create( | |
| 30 | + @Valid @RequestBody DeliveryOrderCreateDTO dto, | |
| 31 | + HttpServletRequest request) { | |
| 32 | + String appKey = request.getHeader("X-App-Key"); | |
| 33 | + return Result.success(deliveryOrderService.create(appKey, dto)); | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 查询配送订单状态 | |
| 38 | + * 接入方可轮询此接口跟踪配送进度 | |
| 39 | + */ | |
| 40 | + @GetMapping("/query") | |
| 41 | + public Result<DeliveryOrderCreateVO> query( | |
| 42 | + @RequestParam String outOrderNo, | |
| 43 | + HttpServletRequest request) { | |
| 44 | + String appKey = request.getHeader("X-App-Key"); | |
| 45 | + return Result.success(deliveryOrderService.queryByOutOrderNo(appKey, outOrderNo)); | |
| 46 | + } | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * 取消配送订单 | |
| 50 | + * 仅 status=2(待接单)可取消 | |
| 51 | + */ | |
| 52 | + @PostMapping("/cancel") | |
| 53 | + public Result<Void> cancel( | |
| 54 | + @RequestParam String outOrderNo, | |
| 55 | + HttpServletRequest request) { | |
| 56 | + String appKey = request.getHeader("X-App-Key"); | |
| 57 | + deliveryOrderService.cancel(appKey, outOrderNo); | |
| 58 | + return Result.success(); | |
| 59 | + } | |
| 60 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/OpenStoreController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/OpenStoreController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.MerchantStoreDTO; | |
| 5 | +import com.diligrp.rider.entity.MerchantStore; | |
| 6 | +import com.diligrp.rider.service.MerchantService; | |
| 7 | +import jakarta.servlet.http.HttpServletRequest; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +import java.util.List; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * 开放平台:门店同步接口 | |
| 15 | + * 接入方用 AppKey 签名后调用,同步自己系统的门店数据到配送中台 | |
| 16 | + */ | |
| 17 | +@RestController | |
| 18 | +@RequestMapping("/api/open/store") | |
| 19 | +@RequiredArgsConstructor | |
| 20 | +public class OpenStoreController { | |
| 21 | + | |
| 22 | + private final MerchantService merchantService; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 同步门店(新增或更新) | |
| 26 | + * 以 appKey + outStoreId 为唯一键,存在则更新,不存在则新增 | |
| 27 | + * dto.outStoreId 必填 | |
| 28 | + */ | |
| 29 | + @PostMapping("/sync") | |
| 30 | + public Result<MerchantStore> sync( | |
| 31 | + @RequestBody MerchantStoreDTO dto, | |
| 32 | + HttpServletRequest request) { | |
| 33 | + String appKey = request.getHeader("X-App-Key"); | |
| 34 | + return Result.success(merchantService.syncStore(appKey, dto)); | |
| 35 | + } | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * 查询本应用下的门店列表 | |
| 39 | + */ | |
| 40 | + @GetMapping("/list") | |
| 41 | + public Result<List<MerchantStore>> list( | |
| 42 | + HttpServletRequest request, | |
| 43 | + @RequestParam(required = false) Long cityId, | |
| 44 | + @RequestParam(defaultValue = "1") int page) { | |
| 45 | + String appKey = request.getHeader("X-App-Key"); | |
| 46 | + return Result.success(merchantService.storeList(cityId, null, page)); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 根据外部门店编号查询 | |
| 51 | + */ | |
| 52 | + @GetMapping("/getByOutStoreId") | |
| 53 | + public Result<MerchantStore> getByOutStoreId( | |
| 54 | + @RequestParam String outStoreId, | |
| 55 | + HttpServletRequest request) { | |
| 56 | + String appKey = request.getHeader("X-App-Key"); | |
| 57 | + return Result.success(merchantService.getByOutStoreId(appKey, outStoreId)); | |
| 58 | + } | |
| 59 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/PlatformCityController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/PlatformCityController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.entity.City; | |
| 5 | +import com.diligrp.rider.service.CityService; | |
| 6 | +import com.diligrp.rider.vo.CityVO; | |
| 7 | +import lombok.RequiredArgsConstructor; | |
| 8 | +import org.springframework.web.bind.annotation.*; | |
| 9 | + | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 12 | +@RestController | |
| 13 | +@RequestMapping("/api/platform/city") | |
| 14 | +@RequiredArgsConstructor | |
| 15 | +public class PlatformCityController { | |
| 16 | + | |
| 17 | + private final CityService cityService; | |
| 18 | + | |
| 19 | + /** 获取两级城市树(省→市) */ | |
| 20 | + @GetMapping("/tree") | |
| 21 | + public Result<List<CityVO>> tree() { | |
| 22 | + return Result.success(cityService.getTree()); | |
| 23 | + } | |
| 24 | + | |
| 25 | + /** 已开通城市列表 */ | |
| 26 | + @GetMapping("/open") | |
| 27 | + public Result<List<CityVO>> openList() { | |
| 28 | + return Result.success(cityService.getOpenList()); | |
| 29 | + } | |
| 30 | + | |
| 31 | + /** 新增城市 */ | |
| 32 | + @PostMapping("/add") | |
| 33 | + public Result<Void> add(@RequestBody City city) { | |
| 34 | + cityService.add(city); | |
| 35 | + return Result.success(); | |
| 36 | + } | |
| 37 | + | |
| 38 | + /** 编辑城市基础信息 */ | |
| 39 | + @PutMapping("/edit") | |
| 40 | + public Result<Void> edit(@RequestBody City city) { | |
| 41 | + cityService.edit(city); | |
| 42 | + return Result.success(); | |
| 43 | + } | |
| 44 | + | |
| 45 | + /** 开通/关闭城市:status=0关闭 1开通 */ | |
| 46 | + @PostMapping("/setStatus") | |
| 47 | + public Result<Void> setStatus(@RequestParam Long cityId, @RequestParam int status) { | |
| 48 | + cityService.setStatus(cityId, status); | |
| 49 | + return Result.success(); | |
| 50 | + } | |
| 51 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/PlatformCityFeePlanController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/PlatformCityFeePlanController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.DeliveryFeePlanPreviewDTO; | |
| 5 | +import com.diligrp.rider.dto.DeliveryFeePlanSaveDTO; | |
| 6 | +import com.diligrp.rider.service.DeliveryFeePlanService; | |
| 7 | +import com.diligrp.rider.vo.DeliveryFeePlanDetailVO; | |
| 8 | +import com.diligrp.rider.vo.DeliveryFeePlanVO; | |
| 9 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import org.springframework.web.bind.annotation.*; | |
| 12 | + | |
| 13 | +import java.util.List; | |
| 14 | + | |
| 15 | +@RestController | |
| 16 | +@RequestMapping("/api/platform/city") | |
| 17 | +@RequiredArgsConstructor | |
| 18 | +public class PlatformCityFeePlanController { | |
| 19 | + | |
| 20 | + private final DeliveryFeePlanService deliveryFeePlanService; | |
| 21 | + | |
| 22 | + @GetMapping("/{cityId}/fee-plans") | |
| 23 | + public Result<List<DeliveryFeePlanVO>> listPlans(@PathVariable Long cityId) { | |
| 24 | + return Result.success(deliveryFeePlanService.listPlans(cityId)); | |
| 25 | + } | |
| 26 | + | |
| 27 | + @GetMapping("/{cityId}/fee-plans/{planId}") | |
| 28 | + public Result<DeliveryFeePlanDetailVO> getPlanDetail(@PathVariable Long cityId, @PathVariable Long planId) { | |
| 29 | + return Result.success(deliveryFeePlanService.getPlanDetail(cityId, planId)); | |
| 30 | + } | |
| 31 | + | |
| 32 | + @PostMapping("/{cityId}/fee-plans") | |
| 33 | + public Result<Long> createPlan(@PathVariable Long cityId, @RequestBody DeliveryFeePlanSaveDTO dto) { | |
| 34 | + return Result.success(deliveryFeePlanService.createPlan(cityId, dto)); | |
| 35 | + } | |
| 36 | + | |
| 37 | + @PostMapping("/{cityId}/fee-plans/init-default") | |
| 38 | + public Result<Long> initializeDefaultPlan(@PathVariable Long cityId) { | |
| 39 | + return Result.success(deliveryFeePlanService.initializeDefaultPlan(cityId)); | |
| 40 | + } | |
| 41 | + | |
| 42 | + @PutMapping("/{cityId}/fee-plans/{planId}") | |
| 43 | + public Result<Void> updatePlan(@PathVariable Long cityId, | |
| 44 | + @PathVariable Long planId, | |
| 45 | + @RequestBody DeliveryFeePlanSaveDTO dto) { | |
| 46 | + deliveryFeePlanService.updatePlan(cityId, planId, dto); | |
| 47 | + return Result.success(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @PostMapping("/{cityId}/fee-plans/{planId}/copy") | |
| 51 | + public Result<Long> copyPlan(@PathVariable Long cityId, @PathVariable Long planId) { | |
| 52 | + return Result.success(deliveryFeePlanService.copyPlan(cityId, planId)); | |
| 53 | + } | |
| 54 | + | |
| 55 | + @PostMapping("/{cityId}/fee-plans/{planId}/default") | |
| 56 | + public Result<Void> setDefaultPlan(@PathVariable Long cityId, @PathVariable Long planId) { | |
| 57 | + deliveryFeePlanService.setDefaultPlan(cityId, planId); | |
| 58 | + return Result.success(); | |
| 59 | + } | |
| 60 | + | |
| 61 | + @DeleteMapping("/{cityId}/fee-plans/{planId}") | |
| 62 | + public Result<Void> deletePlan(@PathVariable Long cityId, @PathVariable Long planId) { | |
| 63 | + deliveryFeePlanService.deletePlan(cityId, planId); | |
| 64 | + return Result.success(); | |
| 65 | + } | |
| 66 | + | |
| 67 | + @PostMapping("/{cityId}/fee-plans/preview") | |
| 68 | + public Result<DeliveryFeeResultVO> preview(@PathVariable Long cityId, | |
| 69 | + @RequestBody DeliveryFeePlanPreviewDTO dto) { | |
| 70 | + return Result.success(deliveryFeePlanService.preview(cityId, dto)); | |
| 71 | + } | |
| 72 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/PlatformMerchantController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/PlatformMerchantController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.MerchantStoreDTO; | |
| 5 | +import com.diligrp.rider.entity.MerchantStore; | |
| 6 | +import com.diligrp.rider.service.MerchantService; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +import java.math.BigDecimal; | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 平台后台:门店管理 | |
| 16 | + * - 外部门店通过开放平台 /api/open/store/sync 同步,统一存入 merchant_store 表 | |
| 17 | + * - 平台管理员也可直接新建门店,选填 outStoreId 与外部系统对账 | |
| 18 | + */ | |
| 19 | +@RestController | |
| 20 | +@RequestMapping("/api/platform/merchant") | |
| 21 | +@RequiredArgsConstructor | |
| 22 | +public class PlatformMerchantController { | |
| 23 | + | |
| 24 | + private final MerchantService merchantService; | |
| 25 | + | |
| 26 | + /** 门店列表(含外部同步的和平台自建的) */ | |
| 27 | + @GetMapping("/store/list") | |
| 28 | + public Result<List<MerchantStore>> storeList( | |
| 29 | + @RequestParam(required = false) Long cityId, | |
| 30 | + @RequestParam(required = false) String keyword, | |
| 31 | + @RequestParam(defaultValue = "1") int page) { | |
| 32 | + return Result.success(merchantService.storeList(cityId, keyword, page)); | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** 新增门店(平台直接建,无需审核) */ | |
| 36 | + @PostMapping("/store/add") | |
| 37 | + public Result<Long> addStore(@Valid @RequestBody MerchantStoreDTO dto) { | |
| 38 | + return Result.success(merchantService.addStore(dto)); | |
| 39 | + } | |
| 40 | + | |
| 41 | + /** 编辑门店 */ | |
| 42 | + @PutMapping("/store/edit") | |
| 43 | + public Result<Void> editStore(@Valid @RequestBody MerchantStoreDTO dto) { | |
| 44 | + merchantService.editStore(dto); | |
| 45 | + return Result.success(); | |
| 46 | + } | |
| 47 | + | |
| 48 | + /** 门店详情 */ | |
| 49 | + @GetMapping("/store/detail") | |
| 50 | + public Result<MerchantStore> storeDetail(@RequestParam Long storeId) { | |
| 51 | + return Result.success(merchantService.getStore(storeId)); | |
| 52 | + } | |
| 53 | + | |
| 54 | + /** 设置营业/打烊 */ | |
| 55 | + @PostMapping("/store/setOperatingState") | |
| 56 | + public Result<Void> setOperatingState(@RequestParam Long storeId, @RequestParam int state) { | |
| 57 | + merchantService.setOperatingState(storeId, state); | |
| 58 | + return Result.success(); | |
| 59 | + } | |
| 60 | + | |
| 61 | + /** 更新免运费和起送金额 */ | |
| 62 | + @PostMapping("/store/updateFeeConfig") | |
| 63 | + public Result<Void> updateFeeConfig( | |
| 64 | + @RequestParam Long storeId, | |
| 65 | + @RequestParam(defaultValue = "0") BigDecimal freeShipping, | |
| 66 | + @RequestParam(defaultValue = "0") BigDecimal upToSend) { | |
| 67 | + merchantService.updateFeeConfig(storeId, freeShipping, upToSend); | |
| 68 | + return Result.success(); | |
| 69 | + } | |
| 70 | + | |
| 71 | + /** 删除门店 */ | |
| 72 | + @DeleteMapping("/store/del") | |
| 73 | + public Result<Void> delStore(@RequestParam Long storeId) { | |
| 74 | + merchantService.delStore(storeId); | |
| 75 | + return Result.success(); | |
| 76 | + } | |
| 77 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/PlatformOpenAppController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/PlatformOpenAppController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.diligrp.rider.common.result.Result; | |
| 5 | +import com.diligrp.rider.dto.PlatformMockDeliveryCreateDTO; | |
| 6 | +import com.diligrp.rider.entity.OpenApp; | |
| 7 | +import com.diligrp.rider.entity.WebhookLog; | |
| 8 | +import com.diligrp.rider.mapper.OpenAppMapper; | |
| 9 | +import com.diligrp.rider.mapper.WebhookLogMapper; | |
| 10 | +import com.diligrp.rider.service.DeliveryOrderService; | |
| 11 | +import com.diligrp.rider.service.OpenAppService; | |
| 12 | +import com.diligrp.rider.service.WebhookService; | |
| 13 | +import com.diligrp.rider.vo.DeliveryOrderCreateVO; | |
| 14 | +import jakarta.validation.Valid; | |
| 15 | +import lombok.RequiredArgsConstructor; | |
| 16 | +import org.springframework.web.bind.annotation.*; | |
| 17 | + | |
| 18 | +import java.util.List; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * 平台后台:开放平台应用管理 | |
| 22 | + */ | |
| 23 | +@RestController | |
| 24 | +@RequestMapping("/api/platform/open") | |
| 25 | +@RequiredArgsConstructor | |
| 26 | +public class PlatformOpenAppController { | |
| 27 | + | |
| 28 | + private final OpenAppService openAppService; | |
| 29 | + private final WebhookService webhookService; | |
| 30 | + private final WebhookLogMapper webhookLogMapper; | |
| 31 | + private final OpenAppMapper openAppMapper; | |
| 32 | + private final DeliveryOrderService deliveryOrderService; | |
| 33 | + | |
| 34 | + /** 应用列表 */ | |
| 35 | + @GetMapping("/list") | |
| 36 | + public Result<List<OpenApp>> list(@RequestParam(defaultValue = "1") int page) { | |
| 37 | + return Result.success(openAppService.list(page)); | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** 创建应用 */ | |
| 41 | + @PostMapping("/create") | |
| 42 | + public Result<OpenApp> create(@RequestParam String appName, | |
| 43 | + @RequestParam Long cityId, | |
| 44 | + @RequestParam(required = false) Long storeId, | |
| 45 | + @RequestParam(required = false) String webhookUrl, | |
| 46 | + @RequestParam(required = false) String webhookEvents, | |
| 47 | + @RequestParam(required = false) String remark) { | |
| 48 | + return Result.success(openAppService.create(appName, cityId, storeId, webhookUrl, webhookEvents, remark)); | |
| 49 | + } | |
| 50 | + | |
| 51 | + /** 平台后台模拟推送配送单 */ | |
| 52 | + @PostMapping("/mockDelivery/create") | |
| 53 | + public Result<DeliveryOrderCreateVO> mockDeliveryCreate(@Valid @RequestBody PlatformMockDeliveryCreateDTO dto) { | |
| 54 | + OpenApp app = openAppMapper.selectById(dto.getAppId()); | |
| 55 | + if (app == null) { | |
| 56 | + return Result.error("应用不存在"); | |
| 57 | + } | |
| 58 | + return Result.success(deliveryOrderService.create(app.getAppKey(), dto)); | |
| 59 | + } | |
| 60 | + | |
| 61 | + /** 重置 AppSecret */ | |
| 62 | + @PostMapping("/resetSecret") | |
| 63 | + public Result<String> resetSecret(@RequestParam Long appId) { | |
| 64 | + return Result.success(openAppService.resetSecret(appId)); | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** 启用/禁用应用:status=0禁用 1启用 */ | |
| 68 | + @PostMapping("/setStatus") | |
| 69 | + public Result<Void> setStatus(@RequestParam Long appId, @RequestParam int status) { | |
| 70 | + openAppService.setStatus(appId, status); | |
| 71 | + return Result.success(); | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** 更新 Webhook 配置 */ | |
| 75 | + @PostMapping("/updateWebhook") | |
| 76 | + public Result<Void> updateWebhook(@RequestParam Long appId, | |
| 77 | + @RequestParam String webhookUrl, | |
| 78 | + @RequestParam String webhookEvents) { | |
| 79 | + openAppService.updateWebhook(appId, webhookUrl, webhookEvents); | |
| 80 | + return Result.success(); | |
| 81 | + } | |
| 82 | + | |
| 83 | + /** Webhook 推送日志 */ | |
| 84 | + @GetMapping("/webhook/logs") | |
| 85 | + public Result<List<WebhookLog>> webhookLogs(@RequestParam Long appId, | |
| 86 | + @RequestParam(defaultValue = "1") int page) { | |
| 87 | + int offset = (page - 1) * 20; | |
| 88 | + List<WebhookLog> logs = webhookLogMapper.selectList( | |
| 89 | + new LambdaQueryWrapper<WebhookLog>() | |
| 90 | + .eq(WebhookLog::getAppId, appId) | |
| 91 | + .orderByDesc(WebhookLog::getId) | |
| 92 | + .last("LIMIT " + offset + ",20")); | |
| 93 | + return Result.success(logs); | |
| 94 | + } | |
| 95 | + | |
| 96 | + /** 重试失败的 Webhook */ | |
| 97 | + @PostMapping("/webhook/retry") | |
| 98 | + public Result<Void> retryWebhook(@RequestParam Long logId) { | |
| 99 | + webhookService.retry(logId); | |
| 100 | + return Result.success(); | |
| 101 | + } | |
| 102 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/PlatformSubstationController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/PlatformSubstationController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.ChangePasswordDTO; | |
| 5 | +import com.diligrp.rider.entity.Substation; | |
| 6 | +import com.diligrp.rider.service.SubstationService; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +import java.util.List; | |
| 12 | + | |
| 13 | +@RestController | |
| 14 | +@RequestMapping("/api/platform/substation") | |
| 15 | +@RequiredArgsConstructor | |
| 16 | +public class PlatformSubstationController { | |
| 17 | + | |
| 18 | + private final SubstationService substationService; | |
| 19 | + | |
| 20 | + /** 分站管理员列表 */ | |
| 21 | + @GetMapping("/list") | |
| 22 | + public Result<List<Substation>> list(@RequestParam(required = false) String keyword) { | |
| 23 | + return Result.success(substationService.list(keyword)); | |
| 24 | + } | |
| 25 | + | |
| 26 | + /** 新增分站管理员 */ | |
| 27 | + @PostMapping("/add") | |
| 28 | + public Result<Void> add(@RequestBody Substation substation) { | |
| 29 | + substationService.add(substation); | |
| 30 | + return Result.success(); | |
| 31 | + } | |
| 32 | + | |
| 33 | + /** 编辑分站管理员 */ | |
| 34 | + @PutMapping("/edit") | |
| 35 | + public Result<Void> edit(@RequestBody Substation substation) { | |
| 36 | + substationService.edit(substation); | |
| 37 | + return Result.success(); | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** 禁用 */ | |
| 41 | + @PostMapping("/ban") | |
| 42 | + public Result<Void> ban(@RequestParam Long id) { | |
| 43 | + substationService.ban(id); | |
| 44 | + return Result.success(); | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** 启用 */ | |
| 48 | + @PostMapping("/cancelBan") | |
| 49 | + public Result<Void> cancelBan(@RequestParam Long id) { | |
| 50 | + substationService.cancelBan(id); | |
| 51 | + return Result.success(); | |
| 52 | + } | |
| 53 | + | |
| 54 | + /** 删除 */ | |
| 55 | + @DeleteMapping("/del") | |
| 56 | + public Result<Void> del(@RequestParam Long id) { | |
| 57 | + substationService.del(id); | |
| 58 | + return Result.success(); | |
| 59 | + } | |
| 60 | + | |
| 61 | + /** 根据城市ID查分站管理员 */ | |
| 62 | + @GetMapping("/getByCity") | |
| 63 | + public Result<Substation> getByCity(@RequestParam Long cityId) { | |
| 64 | + return Result.success(substationService.getByCityId(cityId)); | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 分站管理员修改自己的密码 | |
| 69 | + * 需要携带分站管理员 token(role=substation) | |
| 70 | + */ | |
| 71 | + @PostMapping("/changePassword") | |
| 72 | + public Result<Void> changePassword( | |
| 73 | + @Valid @RequestBody ChangePasswordDTO dto, | |
| 74 | + jakarta.servlet.http.HttpServletRequest request) { | |
| 75 | + Long adminId = (Long) request.getAttribute("adminId"); | |
| 76 | + substationService.changePassword(adminId, dto.getOldPassword(), dto.getNewPassword()); | |
| 77 | + return Result.success(); | |
| 78 | + } | |
| 79 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/RiderAuthController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/RiderAuthController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.ApplyDTO; | |
| 5 | +import com.diligrp.rider.dto.LoginDTO; | |
| 6 | +import com.diligrp.rider.service.RiderAuthService; | |
| 7 | +import com.diligrp.rider.vo.RiderVO; | |
| 8 | +import jakarta.validation.Valid; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import org.springframework.web.bind.annotation.*; | |
| 11 | + | |
| 12 | +@RestController | |
| 13 | +@RequestMapping("/api/rider") | |
| 14 | +@RequiredArgsConstructor | |
| 15 | +public class RiderAuthController { | |
| 16 | + | |
| 17 | + private final RiderAuthService authService; | |
| 18 | + | |
| 19 | + /** 骑手申请注册 */ | |
| 20 | + @PostMapping("/apply") | |
| 21 | + public Result<Void> apply(@Valid @RequestBody ApplyDTO dto) { | |
| 22 | + authService.apply(dto); | |
| 23 | + return Result.success(); | |
| 24 | + } | |
| 25 | + | |
| 26 | + /** 密码登录 */ | |
| 27 | + @PostMapping("/login/pass") | |
| 28 | + public Result<RiderVO> loginByPass(@Valid @RequestBody LoginDTO dto) { | |
| 29 | + return Result.success(authService.loginByPass(dto)); | |
| 30 | + } | |
| 31 | + | |
| 32 | + /** 获取个人信息 */ | |
| 33 | + @GetMapping("/user/info") | |
| 34 | + public Result<RiderVO> getInfo(jakarta.servlet.http.HttpServletRequest request) { | |
| 35 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 36 | + return Result.success(authService.getInfo(riderId)); | |
| 37 | + } | |
| 38 | + | |
| 39 | + /** 切换休息/接单状态 */ | |
| 40 | + @PostMapping("/user/toggleRest") | |
| 41 | + public Result<Void> toggleRest(jakarta.servlet.http.HttpServletRequest request) { | |
| 42 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 43 | + authService.toggleRest(riderId); | |
| 44 | + return Result.success(); | |
| 45 | + } | |
| 46 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/RiderBalanceController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/RiderBalanceController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.service.RiderBalanceService; | |
| 5 | +import com.diligrp.rider.vo.BalanceVO; | |
| 6 | +import jakarta.servlet.http.HttpServletRequest; | |
| 7 | +import lombok.RequiredArgsConstructor; | |
| 8 | +import org.springframework.web.bind.annotation.*; | |
| 9 | + | |
| 10 | +import java.math.BigDecimal; | |
| 11 | + | |
| 12 | +@RestController | |
| 13 | +@RequestMapping("/api/rider/balance") | |
| 14 | +@RequiredArgsConstructor | |
| 15 | +public class RiderBalanceController { | |
| 16 | + | |
| 17 | + private final RiderBalanceService balanceService; | |
| 18 | + | |
| 19 | + /** 查询余额及流水 */ | |
| 20 | + @GetMapping | |
| 21 | + public Result<BalanceVO> getBalance(@RequestParam(defaultValue = "1") int page, | |
| 22 | + HttpServletRequest request) { | |
| 23 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 24 | + return Result.success(balanceService.getBalance(riderId, page)); | |
| 25 | + } | |
| 26 | + | |
| 27 | + /** 今日收入统计 */ | |
| 28 | + @GetMapping("/today") | |
| 29 | + public Result<BigDecimal> todayIncome(HttpServletRequest request) { | |
| 30 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 31 | + return Result.success(balanceService.getTodayIncome(riderId)); | |
| 32 | + } | |
| 33 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/RiderExtController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/RiderExtController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.service.RiderEvaluateService; | |
| 5 | +import com.diligrp.rider.service.RiderOrderService; | |
| 6 | +import com.diligrp.rider.service.RefundService; | |
| 7 | +import com.diligrp.rider.entity.OrderRefundReason; | |
| 8 | +import com.diligrp.rider.vo.RiderMonthCountVO; | |
| 9 | +import com.diligrp.rider.vo.RiderTodayCountVO; | |
| 10 | +import com.diligrp.rider.vo.OrderVO; | |
| 11 | +import jakarta.servlet.http.HttpServletRequest; | |
| 12 | +import lombok.Data; | |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 14 | +import org.springframework.web.bind.annotation.*; | |
| 15 | + | |
| 16 | +import java.util.List; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * 骑手端扩展接口:转单/退款/评价/统计 | |
| 20 | + */ | |
| 21 | +@RestController | |
| 22 | +@RequestMapping("/api/rider") | |
| 23 | +@RequiredArgsConstructor | |
| 24 | +public class RiderExtController { | |
| 25 | + | |
| 26 | + private final RiderOrderService orderService; | |
| 27 | + private final RiderEvaluateService evaluateService; | |
| 28 | + private final RefundService refundService; | |
| 29 | + | |
| 30 | + @Data | |
| 31 | + static class OrderIdReq { private Long orderId; } | |
| 32 | + @Data | |
| 33 | + static class RefundReq { | |
| 34 | + private Long orderId; | |
| 35 | + private Long reasonId = 0L; | |
| 36 | + private String reason = ""; | |
| 37 | + } | |
| 38 | + | |
| 39 | + // ---- 转单 ---- | |
| 40 | + | |
| 41 | + /** 骑手申请转单(仅 status=3 可申请) */ | |
| 42 | + @PostMapping("/order/applyTrans") | |
| 43 | + public Result<Void> applyTrans(@RequestBody OrderIdReq req, HttpServletRequest request) { | |
| 44 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 45 | + orderService.applyTrans(riderId, req.getOrderId()); | |
| 46 | + return Result.success(); | |
| 47 | + } | |
| 48 | + | |
| 49 | + // ---- 统计 ---- | |
| 50 | + | |
| 51 | + /** 今日统计 */ | |
| 52 | + @GetMapping("/order/count/today") | |
| 53 | + public Result<RiderTodayCountVO> todayCount(HttpServletRequest request) { | |
| 54 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 55 | + return Result.success(orderService.getTodayCount(riderId)); | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** 月度统计(year=0表示本年) */ | |
| 59 | + @GetMapping("/order/count/month") | |
| 60 | + public Result<List<RiderMonthCountVO>> monthCount( | |
| 61 | + @RequestParam(defaultValue = "0") int year, | |
| 62 | + HttpServletRequest request) { | |
| 63 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 64 | + return Result.success(orderService.getMonthCount(riderId, year)); | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 骑手历史订单明细列表 | |
| 69 | + * type=0全部 1已完成 2已转单 | |
| 70 | + */ | |
| 71 | + @GetMapping("/order/count/list") | |
| 72 | + public Result<List<OrderVO>> countList( | |
| 73 | + @RequestParam(defaultValue = "0") int type, | |
| 74 | + @RequestParam(defaultValue = "1") int page, | |
| 75 | + HttpServletRequest request) { | |
| 76 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 77 | + return Result.success(orderService.getCountList(riderId, type, page)); | |
| 78 | + } | |
| 79 | + | |
| 80 | + // ---- 退款 ---- | |
| 81 | + | |
| 82 | + /** 退款原因列表(role=2骑手) */ | |
| 83 | + @GetMapping("/order/refund/reasons") | |
| 84 | + public Result<List<OrderRefundReason>> refundReasons() { | |
| 85 | + return Result.success(refundService.getReasons(2)); | |
| 86 | + } | |
| 87 | + | |
| 88 | + /** 骑手申请退款 */ | |
| 89 | + @PostMapping("/order/refund/apply") | |
| 90 | + public Result<Void> applyRefund(@RequestBody RefundReq req, HttpServletRequest request) { | |
| 91 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 92 | + Long reasonId = req.getReasonId() != null ? req.getReasonId() : 0L; | |
| 93 | + String reason = req.getReason() != null ? req.getReason() : ""; | |
| 94 | + refundService.applyRefund(req.getOrderId(), riderId, 2, reasonId, reason); | |
| 95 | + return Result.success(); | |
| 96 | + } | |
| 97 | + | |
| 98 | + // ---- 评价 ---- | |
| 99 | + | |
| 100 | + /** 骑手查看自己的评价列表(type=1好评 2中评 3差评 0全部) */ | |
| 101 | + @GetMapping("/evaluate/list") | |
| 102 | + public Result<List<?>> evaluateList( | |
| 103 | + @RequestParam(defaultValue = "0") int type, | |
| 104 | + @RequestParam(defaultValue = "1") int page, | |
| 105 | + HttpServletRequest request) { | |
| 106 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 107 | + return Result.success(evaluateService.getRiderEvaluates(riderId, type, page)); | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** 本月好评数 */ | |
| 111 | + @GetMapping("/evaluate/month/good") | |
| 112 | + public Result<Integer> monthGoodCount(HttpServletRequest request) { | |
| 113 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 114 | + return Result.success(evaluateService.getMonthGoodCount(riderId)); | |
| 115 | + } | |
| 116 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/RiderLocationController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/RiderLocationController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.dto.LocationDTO; | |
| 5 | +import com.diligrp.rider.service.RiderLocationService; | |
| 6 | +import com.diligrp.rider.vo.NearbyRiderVO; | |
| 7 | +import jakarta.servlet.http.HttpServletRequest; | |
| 8 | +import jakarta.validation.Valid; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import org.springframework.web.bind.annotation.*; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +@RestController | |
| 15 | +@RequestMapping("/api/rider/location") | |
| 16 | +@RequiredArgsConstructor | |
| 17 | +public class RiderLocationController { | |
| 18 | + | |
| 19 | + private final RiderLocationService locationService; | |
| 20 | + | |
| 21 | + /** 上报位置 */ | |
| 22 | + @PostMapping("/update") | |
| 23 | + public Result<Void> update(@Valid @RequestBody LocationDTO dto, HttpServletRequest request) { | |
| 24 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 25 | + locationService.updateLocation(riderId, dto); | |
| 26 | + return Result.success(); | |
| 27 | + } | |
| 28 | + | |
| 29 | + /** 查看自己位置 */ | |
| 30 | + @GetMapping | |
| 31 | + public Result<LocationDTO> get(HttpServletRequest request) { | |
| 32 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 33 | + return Result.success(locationService.getLocation(riderId)); | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 附近在线骑手列表(分站地图查看用) | |
| 38 | + * Location.getNearby() | |
| 39 | + */ | |
| 40 | + @GetMapping("/nearby") | |
| 41 | + public Result<List<NearbyRiderVO>> nearby( | |
| 42 | + @RequestParam Long cityId, | |
| 43 | + @RequestParam String lng, | |
| 44 | + @RequestParam String lat, | |
| 45 | + HttpServletRequest request) { | |
| 46 | + return Result.success(locationService.getNearby(cityId, lng, lat)); | |
| 47 | + } | |
| 48 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/RiderOrderController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/RiderOrderController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import com.diligrp.rider.service.RiderOrderService; | |
| 5 | +import com.diligrp.rider.vo.OrderVO; | |
| 6 | +import jakarta.servlet.http.HttpServletRequest; | |
| 7 | +import lombok.Data; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import org.springframework.web.bind.annotation.*; | |
| 10 | + | |
| 11 | +import java.util.List; | |
| 12 | + | |
| 13 | +@RestController | |
| 14 | +@RequestMapping("/api/rider/order") | |
| 15 | +@RequiredArgsConstructor | |
| 16 | +public class RiderOrderController { | |
| 17 | + | |
| 18 | + private final RiderOrderService orderService; | |
| 19 | + | |
| 20 | + @Data | |
| 21 | + static class OrderIdCityReq { private Long orderId; private Long cityId; } | |
| 22 | + @Data | |
| 23 | + static class OrderCompleteReq { private Long orderId; private String thumbs; } | |
| 24 | + @Data | |
| 25 | + static class OrderStartReq { private Long orderId; private String code; } | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 订单列表 | |
| 29 | + * @param type 1=待接单 2=待取货 3=待完成 | |
| 30 | + * @param cityId 城市ID | |
| 31 | + * @param page 页码 | |
| 32 | + */ | |
| 33 | + @GetMapping("/list") | |
| 34 | + public Result<List<OrderVO>> list(@RequestParam Integer type, | |
| 35 | + @RequestParam Long cityId, | |
| 36 | + @RequestParam(defaultValue = "1") int page, | |
| 37 | + HttpServletRequest request) { | |
| 38 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 39 | + return Result.success(orderService.getList(riderId, cityId, type, page)); | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** 订单详情 */ | |
| 43 | + @GetMapping("/detail") | |
| 44 | + public Result<OrderVO> detail(@RequestParam Long orderId, HttpServletRequest request) { | |
| 45 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 46 | + return Result.success(orderService.getDetail(riderId, orderId)); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** 拒单 */ | |
| 50 | + @PostMapping("/refuse") | |
| 51 | + public Result<Void> refuse(@RequestBody OrderIdCityReq req, HttpServletRequest request) { | |
| 52 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 53 | + orderService.refuse(riderId, req.getCityId(), req.getOrderId()); | |
| 54 | + return Result.success(); | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** 抢单 */ | |
| 58 | + @PostMapping("/grap") | |
| 59 | + public Result<Void> grap(@RequestBody OrderIdCityReq req, HttpServletRequest request) { | |
| 60 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 61 | + orderService.grap(riderId, req.getCityId(), req.getOrderId()); | |
| 62 | + return Result.success(); | |
| 63 | + } | |
| 64 | + | |
| 65 | + /** 开始服务(取件),输入完成码 */ | |
| 66 | + @PostMapping("/start") | |
| 67 | + public Result<Void> start(@RequestBody OrderStartReq req, HttpServletRequest request) { | |
| 68 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 69 | + orderService.start(riderId, req.getOrderId(), req.getCode()); | |
| 70 | + return Result.success(); | |
| 71 | + } | |
| 72 | + | |
| 73 | + /** 完成订单,上传照片 */ | |
| 74 | + @PostMapping("/complete") | |
| 75 | + public Result<Void> complete(@RequestBody OrderCompleteReq req, HttpServletRequest request) { | |
| 76 | + Long riderId = (Long) request.getAttribute("riderId"); | |
| 77 | + orderService.complete(riderId, req.getOrderId(), req.getThumbs()); | |
| 78 | + return Result.success(); | |
| 79 | + } | |
| 80 | +} | ... | ... |
src/main/java/com/diligrp/rider/controller/RiderUploadController.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/controller/RiderUploadController.java | |
| 1 | +package com.diligrp.rider.controller; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.result.Result; | |
| 4 | +import lombok.RequiredArgsConstructor; | |
| 5 | +import org.springframework.web.bind.annotation.*; | |
| 6 | +import org.springframework.web.multipart.MultipartFile; | |
| 7 | + | |
| 8 | +import java.util.Map; | |
| 9 | +import java.util.UUID; | |
| 10 | + | |
| 11 | +@RestController | |
| 12 | +@RequestMapping("/api/rider/upload") | |
| 13 | +@RequiredArgsConstructor | |
| 14 | +public class RiderUploadController { | |
| 15 | + | |
| 16 | + /** 文件上传(mock:返回占位图URL)TODO: 接入实际存储服务 */ | |
| 17 | + @PostMapping("") | |
| 18 | + public Result<Map<String, Object>> upload(@RequestParam("file") MultipartFile file) { | |
| 19 | + String filename = UUID.randomUUID().toString().replace("-", "") + ".jpg"; | |
| 20 | + String url = "https://diligrp.com/static/upload/" + filename; | |
| 21 | + return Result.success(Map.of("url", url)); | |
| 22 | + } | |
| 23 | + | |
| 24 | + @GetMapping("/config") | |
| 25 | + public Result<Map<String, Object>> getUploadConfig() { | |
| 26 | + return Result.success(Map.of( | |
| 27 | + "url", 0, | |
| 28 | + "domain", "", | |
| 29 | + "qiniu", Map.of(), | |
| 30 | + "ali", Map.of(), | |
| 31 | + "txcos", Map.of() | |
| 32 | + )); | |
| 33 | + } | |
| 34 | + | |
| 35 | + @GetMapping("/qiniu/token") | |
| 36 | + public Result<Map<String, Object>> getQiniuToken() { | |
| 37 | + return Result.success(Map.of("token", "")); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @GetMapping("/ali/sts") | |
| 41 | + public Result<Map<String, Object>> getAliSts() { | |
| 42 | + return Result.success(Map.of( | |
| 43 | + "accessKeyId", "", | |
| 44 | + "accessKeySecret", "", | |
| 45 | + "securityToken", "", | |
| 46 | + "endpoint", "", | |
| 47 | + "bucket", "" | |
| 48 | + )); | |
| 49 | + } | |
| 50 | + | |
| 51 | + @GetMapping("/txcos/sts") | |
| 52 | + public Result<Map<String, Object>> getTxSts() { | |
| 53 | + return Result.success(Map.of( | |
| 54 | + "tmpSecretId", "", | |
| 55 | + "tmpSecretKey", "", | |
| 56 | + "sessionToken", "", | |
| 57 | + "region", "", | |
| 58 | + "bucket", "", | |
| 59 | + "appid", "" | |
| 60 | + )); | |
| 61 | + } | |
| 62 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/AdminLoginDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/AdminLoginDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +@Data | |
| 7 | +public class AdminLoginDTO { | |
| 8 | + @NotBlank(message = "账号不能为空") | |
| 9 | + private String account; | |
| 10 | + @NotBlank(message = "密码不能为空") | |
| 11 | + private String pass; | |
| 12 | + /** 登录角色:admin=超级管理员 substation=分站管理员 */ | |
| 13 | + private String role = "substation"; | |
| 14 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/AdminRiderAddDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/AdminRiderAddDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +@Data | |
| 7 | +public class AdminRiderAddDTO { | |
| 8 | + @NotBlank(message = "昵称不能为空") | |
| 9 | + private String userNickname; | |
| 10 | + | |
| 11 | + @NotBlank(message = "手机号不能为空") | |
| 12 | + private String mobile; | |
| 13 | + | |
| 14 | + @NotBlank(message = "密码不能为空") | |
| 15 | + private String password; | |
| 16 | + | |
| 17 | + private Long cityId; | |
| 18 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/AdminRiderLevelSaveDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/AdminRiderLevelSaveDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +import java.math.BigDecimal; | |
| 8 | + | |
| 9 | +@Data | |
| 10 | +public class AdminRiderLevelSaveDTO { | |
| 11 | + private Long id; | |
| 12 | + | |
| 13 | + @NotNull(message = "城市不能为空") | |
| 14 | + private Long cityId; | |
| 15 | + | |
| 16 | + @NotNull(message = "等级编号不能为空") | |
| 17 | + private Integer levelId; | |
| 18 | + | |
| 19 | + @NotBlank(message = "等级名称不能为空") | |
| 20 | + private String name; | |
| 21 | + | |
| 22 | + @NotNull(message = "转单次数上限不能为空") | |
| 23 | + private Integer transNums; | |
| 24 | + | |
| 25 | + @NotNull(message = "收入模式不能为空") | |
| 26 | + private Integer runFeeMode; | |
| 27 | + | |
| 28 | + private BigDecimal runFixMoney; | |
| 29 | + private BigDecimal runRate; | |
| 30 | + private Integer distanceBasic; | |
| 31 | + private BigDecimal distanceBasicMoney; | |
| 32 | + private BigDecimal distanceMoreMoney; | |
| 33 | + private BigDecimal distanceMaxMoney; | |
| 34 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/ApplyDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/ApplyDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +@Data | |
| 8 | +public class ApplyDTO { | |
| 9 | + @NotBlank(message = "姓名不能为空") | |
| 10 | + private String name; | |
| 11 | + @NotBlank(message = "手机号不能为空") | |
| 12 | + private String mobile; | |
| 13 | + @NotBlank(message = "密码不能为空") | |
| 14 | + private String pass; | |
| 15 | + @NotBlank(message = "验证码不能为空") | |
| 16 | + private String code; | |
| 17 | + private String idNo; | |
| 18 | + private String thumb; | |
| 19 | + @NotNull(message = "城市不能为空") | |
| 20 | + private Long cityId; | |
| 21 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/ChangePasswordDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/ChangePasswordDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +@Data | |
| 7 | +public class ChangePasswordDTO { | |
| 8 | + @NotBlank(message = "原密码不能为空") | |
| 9 | + private String oldPassword; | |
| 10 | + @NotBlank(message = "新密码不能为空") | |
| 11 | + private String newPassword; | |
| 12 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/DeliveryFeeCalcDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/DeliveryFeeCalcDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotNull; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 配送费计算请求 DTO | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +public class DeliveryFeeCalcDTO { | |
| 13 | + | |
| 14 | + @NotNull(message = "城市ID不能为空") | |
| 15 | + private Long cityId; | |
| 16 | + | |
| 17 | + /** 订单类型:1=帮我送 2=帮我取 6=外卖配送 */ | |
| 18 | + @NotNull(message = "订单类型不能为空") | |
| 19 | + private Integer orderType; | |
| 20 | + | |
| 21 | + /** 起点经度 */ | |
| 22 | + @NotNull(message = "起点经度不能为空") | |
| 23 | + private String startLng; | |
| 24 | + | |
| 25 | + /** 起点纬度 */ | |
| 26 | + @NotNull(message = "起点纬度不能为空") | |
| 27 | + private String startLat; | |
| 28 | + | |
| 29 | + /** 终点经度 */ | |
| 30 | + @NotNull(message = "终点经度不能为空") | |
| 31 | + private String endLng; | |
| 32 | + | |
| 33 | + /** 终点纬度 */ | |
| 34 | + @NotNull(message = "终点纬度不能为空") | |
| 35 | + private String endLat; | |
| 36 | + | |
| 37 | + /** 重量(kg),不传默认0 */ | |
| 38 | + private BigDecimal weight = BigDecimal.ZERO; | |
| 39 | + | |
| 40 | + /** 件数,不传默认0 */ | |
| 41 | + private Integer pieces = 0; | |
| 42 | + | |
| 43 | + /** 服务时间戳(秒),用于时段附加费匹配,0=当前时间 */ | |
| 44 | + private Long serviceTime = 0L; | |
| 45 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/DeliveryFeePlanPreviewDTO.java
0 → 100644
src/main/java/com/diligrp/rider/dto/DeliveryFeePlanSaveDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/DeliveryFeePlanSaveDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +@Data | |
| 6 | +public class DeliveryFeePlanSaveDTO { | |
| 7 | + | |
| 8 | + private String name; | |
| 9 | + | |
| 10 | + private Integer status; | |
| 11 | + | |
| 12 | + private Integer listOrder; | |
| 13 | + | |
| 14 | + private String remark; | |
| 15 | + | |
| 16 | + private DeliveryPricingConfigDTO config; | |
| 17 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/DeliveryOrderCreateDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/DeliveryOrderCreateDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +import java.math.BigDecimal; | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 外部系统推单请求 DTO | |
| 12 | + * 对应 POST /api/open/delivery/order/create | |
| 13 | + */ | |
| 14 | +@Data | |
| 15 | +public class DeliveryOrderCreateDTO { | |
| 16 | + | |
| 17 | + /** 城市ID */ | |
| 18 | + private Long cityId; | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 接入方门店编号(对应 merchant_store.out_store_id) | |
| 22 | + * 传入后中台自动查找门店,填充名称/地址/经纬度,无需重复传 storeName/storeLng/storeLat | |
| 23 | + */ | |
| 24 | + private String outStoreId; | |
| 25 | + | |
| 26 | + /** 发货门店名称(outStoreId 为空时必填) */ | |
| 27 | + private String storeName; | |
| 28 | + | |
| 29 | + /** 发货门店地址 */ | |
| 30 | + private String storeAddr; | |
| 31 | + | |
| 32 | + /** 发货门店经度(extStoreId 为空时必填) */ | |
| 33 | + private String storeLng; | |
| 34 | + | |
| 35 | + /** 发货门店纬度(extStoreId 为空时必填) */ | |
| 36 | + private String storeLat; | |
| 37 | + | |
| 38 | + /** 收件人姓名 */ | |
| 39 | + @NotBlank(message = "收件人姓名不能为空") | |
| 40 | + private String recipName; | |
| 41 | + | |
| 42 | + /** 收件人电话 */ | |
| 43 | + @NotBlank(message = "收件人电话不能为空") | |
| 44 | + private String recipPhone; | |
| 45 | + | |
| 46 | + /** 收件人地址 */ | |
| 47 | + @NotBlank(message = "收件人地址不能为空") | |
| 48 | + private String recipAddr; | |
| 49 | + | |
| 50 | + /** 收件人经度 */ | |
| 51 | + @NotBlank(message = "收件人经度不能为空") | |
| 52 | + private String recipLng; | |
| 53 | + | |
| 54 | + /** 收件人纬度 */ | |
| 55 | + @NotBlank(message = "收件人纬度不能为空") | |
| 56 | + private String recipLat; | |
| 57 | + | |
| 58 | + /** 外部系统订单号(用于回调对账,需唯一) */ | |
| 59 | + @NotBlank(message = "外部订单号不能为空") | |
| 60 | + private String outOrderNo; | |
| 61 | + | |
| 62 | + /** 货物重量(kg),用于重量计费,0=不计重 */ | |
| 63 | + private BigDecimal weight = BigDecimal.ZERO; | |
| 64 | + | |
| 65 | + /** 期望配送时间戳(秒),0=立即配送 */ | |
| 66 | + private Long serviceTime = 0L; | |
| 67 | + | |
| 68 | + /** 状态变更回调地址(可覆盖应用级配置) */ | |
| 69 | + private String callbackUrl; | |
| 70 | + | |
| 71 | + /** 整单备注 */ | |
| 72 | + private String remark; | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * 货物清单(骑手端可见) | |
| 76 | + * 配送中台不解析具体业务含义,只做透传快照 | |
| 77 | + */ | |
| 78 | + private List<DeliveryItemDTO> items; | |
| 79 | + | |
| 80 | + /** 整单货物备注(如:放门口、不要辣等) */ | |
| 81 | + private String itemRemark; | |
| 82 | + | |
| 83 | + @Data | |
| 84 | + public static class DeliveryItemDTO { | |
| 85 | + /** 货物名称 */ | |
| 86 | + private String name; | |
| 87 | + /** 数量 */ | |
| 88 | + private Integer quantity = 1; | |
| 89 | + /** 规格/型号(如大份、500ml等) */ | |
| 90 | + private String spec; | |
| 91 | + /** 单项备注 */ | |
| 92 | + private String remark; | |
| 93 | + } | |
| 94 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/DeliveryPricingConfigDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/DeliveryPricingConfigDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 外卖配送计价方案 DTO | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +public class DeliveryPricingConfigDTO { | |
| 13 | + | |
| 14 | + private List<Integer> type; | |
| 15 | + private DeliveryPricingRuleDTO type6; | |
| 16 | + private DeliveryPricingRuleDTO type1; | |
| 17 | + private DeliveryPricingRuleDTO type2; | |
| 18 | + private BigDecimal distanceBasic; | |
| 19 | + private Integer distanceBasicTime; | |
| 20 | + private Integer distanceMoreTime; | |
| 21 | + private BigDecimal riderDistance; | |
| 22 | + private Integer riderTime; | |
| 23 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/DeliveryPricingRuleDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/DeliveryPricingRuleDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 外卖配送计价维度 DTO | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +public class DeliveryPricingRuleDTO { | |
| 13 | + | |
| 14 | + private BigDecimal minFee = BigDecimal.ZERO; | |
| 15 | + private Integer baseSwitch = 0; | |
| 16 | + private BigDecimal baseFee = BigDecimal.ZERO; | |
| 17 | + private Integer feeMode = 1; | |
| 18 | + private BigDecimal fixMoney = BigDecimal.ZERO; | |
| 19 | + private Integer distanceSwitch = 0; | |
| 20 | + private BigDecimal distanceBasic = BigDecimal.ZERO; | |
| 21 | + private BigDecimal distanceBasicMoney = BigDecimal.ZERO; | |
| 22 | + private BigDecimal distanceMoreMoney = BigDecimal.ZERO; | |
| 23 | + private Integer distanceMode = 1; | |
| 24 | + private Integer distanceType = 1; | |
| 25 | + private List<DistanceStepDTO> distanceSteps; | |
| 26 | + private Integer weightSwitch = 0; | |
| 27 | + private BigDecimal weightFirst = BigDecimal.ZERO; | |
| 28 | + private BigDecimal weightFirstFee = BigDecimal.ZERO; | |
| 29 | + private BigDecimal weightUnitFee = BigDecimal.ZERO; | |
| 30 | + private BigDecimal weightCapFee = BigDecimal.ZERO; | |
| 31 | + private BigDecimal weightBasic = BigDecimal.ZERO; | |
| 32 | + private BigDecimal weightBasicMoney = BigDecimal.ZERO; | |
| 33 | + private BigDecimal weightMoreMoney = BigDecimal.ZERO; | |
| 34 | + private Integer weightType = 1; | |
| 35 | + private Integer pieceSwitch = 0; | |
| 36 | + private List<PieceRuleDTO> pieceRules; | |
| 37 | + private List<TimePeriodDTO> times; | |
| 38 | + | |
| 39 | + @Data | |
| 40 | + public static class DistanceStepDTO { | |
| 41 | + private BigDecimal endDistance; | |
| 42 | + private BigDecimal unitDistance; | |
| 43 | + private BigDecimal unitFee; | |
| 44 | + private Integer listOrder; | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Data | |
| 48 | + public static class PieceRuleDTO { | |
| 49 | + private Integer startPiece; | |
| 50 | + private Integer endPiece; | |
| 51 | + private BigDecimal fee; | |
| 52 | + private Integer listOrder; | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Data | |
| 56 | + public static class TimePeriodDTO { | |
| 57 | + private Integer start; | |
| 58 | + private Integer end; | |
| 59 | + private Integer isOpen; | |
| 60 | + private BigDecimal money; | |
| 61 | + } | |
| 62 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/LocationDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/LocationDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotNull; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +@Data | |
| 7 | +public class LocationDTO { | |
| 8 | + @NotNull(message = "经度不能为空") | |
| 9 | + private String lng; | |
| 10 | + @NotNull(message = "纬度不能为空") | |
| 11 | + private String lat; | |
| 12 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/LoginDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/LoginDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +@Data | |
| 7 | +public class LoginDTO { | |
| 8 | + @NotBlank(message = "手机号不能为空") | |
| 9 | + private String username; | |
| 10 | + @NotBlank(message = "密码不能为空") | |
| 11 | + private String pass; | |
| 12 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/MerchantEnterDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/MerchantEnterDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +@Data | |
| 8 | +public class MerchantEnterDTO { | |
| 9 | + @NotBlank(message = "联系人姓名不能为空") | |
| 10 | + private String name; | |
| 11 | + @NotBlank(message = "手机号不能为空") | |
| 12 | + private String mobile; | |
| 13 | + @NotBlank(message = "店铺名称不能为空") | |
| 14 | + private String storeName; | |
| 15 | + @NotNull(message = "城市不能为空") | |
| 16 | + private Long cityId; | |
| 17 | + private String remark; | |
| 18 | + /** 类型:1=商家入驻 2=骑手入驻 3=商务合作 */ | |
| 19 | + private Integer type = 1; | |
| 20 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/MerchantStoreDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/MerchantStoreDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotBlank; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +import java.math.BigDecimal; | |
| 8 | + | |
| 9 | +@Data | |
| 10 | +public class MerchantStoreDTO { | |
| 11 | + private Long id; | |
| 12 | + @NotBlank(message = "店铺名称不能为空") | |
| 13 | + private String name; | |
| 14 | + @NotNull(message = "城市不能为空") | |
| 15 | + private Long cityId; | |
| 16 | + private String thumb; | |
| 17 | + private String address; | |
| 18 | + private String lng; | |
| 19 | + private String lat; | |
| 20 | + /** 营业状态:0=打烊 1=营业 */ | |
| 21 | + private Integer operatingState = 1; | |
| 22 | + /** 是否自动接单 */ | |
| 23 | + private Integer automaticOrder = 0; | |
| 24 | + /** 配送类型:1=外卖配送 2=到店自提 */ | |
| 25 | + private Integer shippingType = 1; | |
| 26 | + /** 免运费门槛 */ | |
| 27 | + private BigDecimal freeShipping = BigDecimal.ZERO; | |
| 28 | + /** 起送金额 */ | |
| 29 | + private BigDecimal upToSend = BigDecimal.ZERO; | |
| 30 | + /** 营业日期JSON */ | |
| 31 | + private String openDate; | |
| 32 | + /** 营业时间JSON */ | |
| 33 | + private String openTime; | |
| 34 | + private String about; | |
| 35 | + /** 商家账号手机号(用于创建登录账号) */ | |
| 36 | + private String accountMobile; | |
| 37 | + /** | |
| 38 | + * 接入方自己系统的门店编号(外部门店ID) | |
| 39 | + * 外部系统同步门店时必填;平台自建时选填,便于与外部系统对账 | |
| 40 | + */ | |
| 41 | + private String outStoreId; | |
| 42 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/OpenDeliveryFeeCalcDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/OpenDeliveryFeeCalcDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotNull; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 开放平台配送费计算请求 DTO | |
| 10 | + * cityId 由 X-App-Key 绑定的租户自动带出,不允许外部传入。 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +public class OpenDeliveryFeeCalcDTO { | |
| 14 | + | |
| 15 | + /** 订单类型:1=帮我送 2=帮我取 6=外卖配送 */ | |
| 16 | + @NotNull(message = "订单类型不能为空") | |
| 17 | + private Integer orderType; | |
| 18 | + | |
| 19 | + /** 起点经度 */ | |
| 20 | + @NotNull(message = "起点经度不能为空") | |
| 21 | + private String startLng; | |
| 22 | + | |
| 23 | + /** 起点纬度 */ | |
| 24 | + @NotNull(message = "起点纬度不能为空") | |
| 25 | + private String startLat; | |
| 26 | + | |
| 27 | + /** 终点经度 */ | |
| 28 | + @NotNull(message = "终点经度不能为空") | |
| 29 | + private String endLng; | |
| 30 | + | |
| 31 | + /** 终点纬度 */ | |
| 32 | + @NotNull(message = "终点纬度不能为空") | |
| 33 | + private String endLat; | |
| 34 | + | |
| 35 | + /** 重量(kg),不传默认0 */ | |
| 36 | + private BigDecimal weight = BigDecimal.ZERO; | |
| 37 | + | |
| 38 | + /** 件数,不传默认0 */ | |
| 39 | + private Integer pieces = 0; | |
| 40 | + | |
| 41 | + /** 服务时间戳(秒),用于时段附加费匹配,0=当前时间 */ | |
| 42 | + private Long serviceTime = 0L; | |
| 43 | +} | ... | ... |
src/main/java/com/diligrp/rider/dto/PlatformMockDeliveryCreateDTO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/dto/PlatformMockDeliveryCreateDTO.java | |
| 1 | +package com.diligrp.rider.dto; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotNull; | |
| 4 | +import lombok.Data; | |
| 5 | +import lombok.EqualsAndHashCode; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 平台后台模拟推单 DTO | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +@EqualsAndHashCode(callSuper = true) | |
| 12 | +public class PlatformMockDeliveryCreateDTO extends DeliveryOrderCreateDTO { | |
| 13 | + | |
| 14 | + /** 开放平台应用ID */ | |
| 15 | + @NotNull(message = "应用ID不能为空") | |
| 16 | + private Long appId; | |
| 17 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/AdminUser.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/AdminUser.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 超级管理员表 | |
| 8 | + */ | |
| 9 | +@Data | |
| 10 | +@TableName("admin_user") | |
| 11 | +public class AdminUser { | |
| 12 | + | |
| 13 | + @TableId(type = IdType.AUTO) | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + /** 登录账号 */ | |
| 17 | + private String userLogin; | |
| 18 | + | |
| 19 | + /** 密码(MD5) */ | |
| 20 | + private String userPass; | |
| 21 | + | |
| 22 | + /** 昵称 */ | |
| 23 | + private String userNickname; | |
| 24 | + | |
| 25 | + /** 状态:0=禁用 1=正常 */ | |
| 26 | + private Integer userStatus; | |
| 27 | + | |
| 28 | + private Long createTime; | |
| 29 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/City.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/City.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 城市表 | |
| 10 | + * city表,是配送中台的核心配置单元 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@TableName("city") | |
| 14 | +public class City { | |
| 15 | + | |
| 16 | + @TableId(type = IdType.AUTO) | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + /** 父级ID,0=省级 */ | |
| 20 | + private Long pid; | |
| 21 | + | |
| 22 | + /** 城市名称 */ | |
| 23 | + private String name; | |
| 24 | + | |
| 25 | + /** 行政区划码 */ | |
| 26 | + private String areaCode; | |
| 27 | + | |
| 28 | + /** 状态:0=未开通 1=已开通 */ | |
| 29 | + private Integer status; | |
| 30 | + | |
| 31 | + /** 平台抽成比例(%) */ | |
| 32 | + private BigDecimal rate; | |
| 33 | + | |
| 34 | + /** 排序 */ | |
| 35 | + private Integer listOrder; | |
| 36 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/DeliveryFeePlan.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/DeliveryFeePlan.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | |
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +@Data | |
| 11 | +@TableName("delivery_fee_plan") | |
| 12 | +public class DeliveryFeePlan { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + private Long cityId; | |
| 18 | + | |
| 19 | + private String name; | |
| 20 | + | |
| 21 | + private Integer isDefault; | |
| 22 | + | |
| 23 | + private BigDecimal minFee; | |
| 24 | + | |
| 25 | + private BigDecimal distanceBasic; | |
| 26 | + | |
| 27 | + private Integer distanceBasicTime; | |
| 28 | + | |
| 29 | + private Integer distanceMoreTime; | |
| 30 | + | |
| 31 | + private BigDecimal riderDistance; | |
| 32 | + | |
| 33 | + private Integer riderTime; | |
| 34 | + | |
| 35 | + private Integer status; | |
| 36 | + | |
| 37 | + private Integer listOrder; | |
| 38 | + | |
| 39 | + private String remark; | |
| 40 | + | |
| 41 | + private Long createTime; | |
| 42 | + | |
| 43 | + private Long updateTime; | |
| 44 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/DeliveryFeePlanDimension.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/DeliveryFeePlanDimension.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | |
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +@Data | |
| 11 | +@TableName("delivery_fee_plan_dimension") | |
| 12 | +public class DeliveryFeePlanDimension { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + private Long planId; | |
| 18 | + | |
| 19 | + private String dimensionType; | |
| 20 | + | |
| 21 | + private Integer enabled; | |
| 22 | + | |
| 23 | + private BigDecimal baseFee; | |
| 24 | + | |
| 25 | + private BigDecimal startDistance; | |
| 26 | + | |
| 27 | + private BigDecimal startFee; | |
| 28 | + | |
| 29 | + private BigDecimal firstWeight; | |
| 30 | + | |
| 31 | + private BigDecimal firstFee; | |
| 32 | + | |
| 33 | + private BigDecimal unitWeightFee; | |
| 34 | + | |
| 35 | + private BigDecimal capFee; | |
| 36 | + | |
| 37 | + private String extraJson; | |
| 38 | + | |
| 39 | + private Long createTime; | |
| 40 | + | |
| 41 | + private Long updateTime; | |
| 42 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/DeliveryFeePlanDistanceStep.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/DeliveryFeePlanDistanceStep.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | |
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +@Data | |
| 11 | +@TableName("delivery_fee_plan_distance_step") | |
| 12 | +public class DeliveryFeePlanDistanceStep { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + private Long planId; | |
| 18 | + | |
| 19 | + private BigDecimal endDistance; | |
| 20 | + | |
| 21 | + private BigDecimal unitDistance; | |
| 22 | + | |
| 23 | + private BigDecimal unitFee; | |
| 24 | + | |
| 25 | + private Integer listOrder; | |
| 26 | + | |
| 27 | + private Long createTime; | |
| 28 | + | |
| 29 | + private Long updateTime; | |
| 30 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/DeliveryFeePlanPieceRule.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/DeliveryFeePlanPieceRule.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | |
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +@Data | |
| 11 | +@TableName("delivery_fee_plan_piece_rule") | |
| 12 | +public class DeliveryFeePlanPieceRule { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + private Long planId; | |
| 18 | + | |
| 19 | + private Integer startPiece; | |
| 20 | + | |
| 21 | + private Integer endPiece; | |
| 22 | + | |
| 23 | + private BigDecimal fee; | |
| 24 | + | |
| 25 | + private Integer listOrder; | |
| 26 | + | |
| 27 | + private Long createTime; | |
| 28 | + | |
| 29 | + private Long updateTime; | |
| 30 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/DeliveryFeePlanTimeRule.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/DeliveryFeePlanTimeRule.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | |
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +@Data | |
| 11 | +@TableName("delivery_fee_plan_time_rule") | |
| 12 | +public class DeliveryFeePlanTimeRule { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + private Long planId; | |
| 18 | + | |
| 19 | + private Integer startMinute; | |
| 20 | + | |
| 21 | + private Integer endMinute; | |
| 22 | + | |
| 23 | + private BigDecimal fee; | |
| 24 | + | |
| 25 | + private Integer enabled; | |
| 26 | + | |
| 27 | + private Integer listOrder; | |
| 28 | + | |
| 29 | + private Long createTime; | |
| 30 | + | |
| 31 | + private Long updateTime; | |
| 32 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/ExtStore.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/ExtStore.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 外部门店表 | |
| 8 | + * 接入方通过 API 注册/同步自己系统的门店到配送中台 | |
| 9 | + * 与 AppKey 绑定,一个应用可有多个门店 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@TableName("ext_store") | |
| 13 | +public class ExtStore { | |
| 14 | + | |
| 15 | + @TableId(type = IdType.AUTO) | |
| 16 | + private Long id; | |
| 17 | + | |
| 18 | + /** 所属应用 AppKey */ | |
| 19 | + private String appKey; | |
| 20 | + | |
| 21 | + /** 接入方自己系统的门店ID(外部系统的原始ID,用于对账) */ | |
| 22 | + private String outStoreId; | |
| 23 | + | |
| 24 | + /** 门店名称 */ | |
| 25 | + private String name; | |
| 26 | + | |
| 27 | + /** 门店地址 */ | |
| 28 | + private String address; | |
| 29 | + | |
| 30 | + /** 经度 */ | |
| 31 | + private String lng; | |
| 32 | + | |
| 33 | + /** 纬度 */ | |
| 34 | + private String lat; | |
| 35 | + | |
| 36 | + /** 所属城市ID */ | |
| 37 | + private Long cityId; | |
| 38 | + | |
| 39 | + /** 门店联系电话 */ | |
| 40 | + private String phone; | |
| 41 | + | |
| 42 | + /** 营业状态:0=关闭 1=营业 */ | |
| 43 | + private Integer status; | |
| 44 | + | |
| 45 | + /** 备注 */ | |
| 46 | + private String remark; | |
| 47 | + | |
| 48 | + private Long createTime; | |
| 49 | + | |
| 50 | + private Long updateTime; | |
| 51 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/MerchantEnter.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/MerchantEnter.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 商家入驻申请表 | |
| 10 | + * merchant_enter表 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@TableName("merchant_enter") | |
| 14 | +public class MerchantEnter { | |
| 15 | + | |
| 16 | + @TableId(type = IdType.AUTO) | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + /** 联系人姓名 */ | |
| 20 | + private String name; | |
| 21 | + | |
| 22 | + /** 手机号 */ | |
| 23 | + private String mobile; | |
| 24 | + | |
| 25 | + /** 店铺名称 */ | |
| 26 | + private String storeName; | |
| 27 | + | |
| 28 | + /** 申请类型:1=商家入驻 2=骑手入驻 3=商务合作 */ | |
| 29 | + private Integer type; | |
| 30 | + | |
| 31 | + /** 所在城市ID */ | |
| 32 | + private Long cityId; | |
| 33 | + | |
| 34 | + /** 备注 */ | |
| 35 | + private String remark; | |
| 36 | + | |
| 37 | + /** 状态:0=未处理 1=已通过 -1=已拒绝 */ | |
| 38 | + private Integer status; | |
| 39 | + | |
| 40 | + private Long addTime; | |
| 41 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/MerchantStore.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/MerchantStore.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 商家店铺表 | |
| 10 | + * merchant_store表 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@TableName("merchant_store") | |
| 14 | +public class MerchantStore { | |
| 15 | + | |
| 16 | + @TableId(type = IdType.AUTO) | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + /** 店铺名称 */ | |
| 20 | + private String name; | |
| 21 | + | |
| 22 | + /** 店铺封面图 */ | |
| 23 | + private String thumb; | |
| 24 | + | |
| 25 | + /** 所属城市ID */ | |
| 26 | + private Long cityId; | |
| 27 | + | |
| 28 | + /** 店铺地址 */ | |
| 29 | + private String address; | |
| 30 | + | |
| 31 | + /** 经度 */ | |
| 32 | + private String lng; | |
| 33 | + | |
| 34 | + /** 纬度 */ | |
| 35 | + private String lat; | |
| 36 | + | |
| 37 | + /** 营业状态:0=打烊 1=营业 */ | |
| 38 | + private Integer operatingState; | |
| 39 | + | |
| 40 | + /** 是否自动接单:0=否 1=是 */ | |
| 41 | + private Integer automaticOrder; | |
| 42 | + | |
| 43 | + /** 配送类型:1=外卖配送 2=到店自提 */ | |
| 44 | + private Integer shippingType; | |
| 45 | + | |
| 46 | + /** 免运费门槛(订单金额满X元免运费,0=不免) */ | |
| 47 | + private BigDecimal freeShipping; | |
| 48 | + | |
| 49 | + /** 起送金额(订单金额低于此值不接单,0=不限) */ | |
| 50 | + private BigDecimal upToSend; | |
| 51 | + | |
| 52 | + /** 营业日期JSON,如[1,2,3,4,5]表示周一到周五 */ | |
| 53 | + private String openDate; | |
| 54 | + | |
| 55 | + /** 营业时间JSON,如["09:00","22:00"] */ | |
| 56 | + private String openTime; | |
| 57 | + | |
| 58 | + /** 店铺简介 */ | |
| 59 | + private String about; | |
| 60 | + | |
| 61 | + /** 关联账号ID(merchant_users表) */ | |
| 62 | + private Long accountId; | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * 关联接入方 AppKey(为空=平台自建门店) | |
| 66 | + * 外部系统同步门店时写入,用于标识门店归属哪个接入方 | |
| 67 | + */ | |
| 68 | + private String appKey; | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * 接入方自己系统的门店ID(外部门店编号) | |
| 72 | + * 推单时传此字段,中台自动匹配门店经纬度 | |
| 73 | + * 平台自建门店也可手动填入,便于与外部系统对账 | |
| 74 | + */ | |
| 75 | + private String outStoreId; | |
| 76 | + | |
| 77 | + /** 排序 */ | |
| 78 | + private Integer listOrder; | |
| 79 | + | |
| 80 | + /** 是否删除 */ | |
| 81 | + @TableLogic | |
| 82 | + private Integer isDel; | |
| 83 | + | |
| 84 | + private Long addTime; | |
| 85 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/MerchantUsers.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/MerchantUsers.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 商家账号表 | |
| 10 | + * merchant_users(商家登录账号,与店铺1:1) | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@TableName("merchant_users") | |
| 14 | +public class MerchantUsers { | |
| 15 | + | |
| 16 | + @TableId(type = IdType.AUTO) | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + /** 关联店铺ID */ | |
| 20 | + private Long storeId; | |
| 21 | + | |
| 22 | + /** 手机号(即登录账号) */ | |
| 23 | + private String mobile; | |
| 24 | + | |
| 25 | + /** 昵称 */ | |
| 26 | + private String userNickname; | |
| 27 | + | |
| 28 | + /** 账号状态:0=禁用 1=正常 */ | |
| 29 | + private Integer userStatus; | |
| 30 | + | |
| 31 | + /** 账号类型:1=商家 */ | |
| 32 | + private Integer type; | |
| 33 | + | |
| 34 | + private Long createTime; | |
| 35 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/OpenApp.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/OpenApp.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 开放平台应用表 | |
| 8 | + * 第三方系统接入时申请 AppKey/AppSecret | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +@TableName("open_app") | |
| 12 | +public class OpenApp { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + /** 应用名称 */ | |
| 18 | + private String appName; | |
| 19 | + | |
| 20 | + /** AppKey(唯一标识) */ | |
| 21 | + private String appKey; | |
| 22 | + | |
| 23 | + /** AppSecret(签名密钥,不对外展示) */ | |
| 24 | + private String appSecret; | |
| 25 | + | |
| 26 | + /** 关联商家店铺ID,0=平台级接入 */ | |
| 27 | + private Long storeId; | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 关联租户/城市ID(必填) | |
| 31 | + * 该 AppKey 只能在此城市下推单、查询、调度骑手 | |
| 32 | + */ | |
| 33 | + private Long cityId; | |
| 34 | + | |
| 35 | + /** 状态:0=禁用 1=正常 */ | |
| 36 | + private Integer status; | |
| 37 | + | |
| 38 | + /** Webhook 回调地址 */ | |
| 39 | + private String webhookUrl; | |
| 40 | + | |
| 41 | + /** 回调事件订阅(JSON数组),如["order.paid","order.completed"] */ | |
| 42 | + private String webhookEvents; | |
| 43 | + | |
| 44 | + /** 备注 */ | |
| 45 | + private String remark; | |
| 46 | + | |
| 47 | + private Long createTime; | |
| 48 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/OrderRefundReason.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/OrderRefundReason.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 退款原因配置表 | |
| 8 | + */ | |
| 9 | +@Data | |
| 10 | +@TableName("orders_refund_reason") | |
| 11 | +public class OrderRefundReason { | |
| 12 | + | |
| 13 | + @TableId(type = IdType.AUTO) | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + /** 原因描述 */ | |
| 17 | + private String name; | |
| 18 | + | |
| 19 | + /** 适用角色:1=用户申请 2=骑手申请 */ | |
| 20 | + private Integer role; | |
| 21 | + | |
| 22 | + private Integer listOrder; | |
| 23 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/OrderRefundRecord.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/OrderRefundRecord.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 退款申请记录表 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@TableName("orders_refund_record") | |
| 13 | +public class OrderRefundRecord { | |
| 14 | + | |
| 15 | + @TableId(type = IdType.AUTO) | |
| 16 | + private Long id; | |
| 17 | + | |
| 18 | + /** 订单ID */ | |
| 19 | + private Long oid; | |
| 20 | + | |
| 21 | + /** 订单号 */ | |
| 22 | + private String orderNo; | |
| 23 | + | |
| 24 | + /** 申请人ID */ | |
| 25 | + private Long uid; | |
| 26 | + | |
| 27 | + /** 申请角色:1=用户 2=骑手 */ | |
| 28 | + private Integer role; | |
| 29 | + | |
| 30 | + /** 退款原因ID */ | |
| 31 | + private Long reasonId; | |
| 32 | + | |
| 33 | + /** 退款原因描述 */ | |
| 34 | + private String reason; | |
| 35 | + | |
| 36 | + /** 退款金额 */ | |
| 37 | + private BigDecimal money; | |
| 38 | + | |
| 39 | + /** 处理状态:0=待处理 1=通过 2=拒绝 */ | |
| 40 | + private Integer status; | |
| 41 | + | |
| 42 | + /** 处理备注 */ | |
| 43 | + private String remark; | |
| 44 | + | |
| 45 | + private Long addTime; | |
| 46 | + private Long handleTime; | |
| 47 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/Orders.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/Orders.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 订单主表 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@TableName("orders") | |
| 13 | +public class Orders { | |
| 14 | + | |
| 15 | + @TableId(type = IdType.AUTO) | |
| 16 | + private Long id; | |
| 17 | + | |
| 18 | + /** 订单号 */ | |
| 19 | + private String orderNo; | |
| 20 | + | |
| 21 | + /** 用户ID */ | |
| 22 | + private Long uid; | |
| 23 | + | |
| 24 | + /** 骑手ID */ | |
| 25 | + private Long riderId; | |
| 26 | + | |
| 27 | + /** 原始骑手ID(转单前) */ | |
| 28 | + private Long oldRiderId; | |
| 29 | + | |
| 30 | + /** 城市ID */ | |
| 31 | + private Long cityId; | |
| 32 | + | |
| 33 | + /** 订单类型:6=外卖配送 */ | |
| 34 | + private Integer type; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 状态:1=待支付 2=已支付 3=已接单 4=服务中 | |
| 38 | + * 6=已完成 7=退款申请 8=退款成功 9=退款拒绝 10=已取消 | |
| 39 | + */ | |
| 40 | + private Integer status; | |
| 41 | + | |
| 42 | + /** 支付类型:1=支付宝 2=微信 */ | |
| 43 | + private Integer payType; | |
| 44 | + | |
| 45 | + /** 订单金额 */ | |
| 46 | + private BigDecimal money; | |
| 47 | + | |
| 48 | + /** 配送费 */ | |
| 49 | + private BigDecimal moneyDelivery; | |
| 50 | + | |
| 51 | + /** 总金额 */ | |
| 52 | + private BigDecimal moneyTotal; | |
| 53 | + | |
| 54 | + /** 骑手收入 */ | |
| 55 | + private BigDecimal riderIncome; | |
| 56 | + | |
| 57 | + /** 站点收入 */ | |
| 58 | + private BigDecimal substationIncome; | |
| 59 | + | |
| 60 | + /** 结算状态:0=未结算 1=待结算 2=已结算 */ | |
| 61 | + private Integer isIncome; | |
| 62 | + | |
| 63 | + /** 转单状态:0=未转单 1=通过 2=申请中 3=拒绝 */ | |
| 64 | + private Integer isTrans; | |
| 65 | + | |
| 66 | + /** 完成码(用户给骑手输入确认完成) */ | |
| 67 | + private String code; | |
| 68 | + | |
| 69 | + /** 起点名称 */ | |
| 70 | + private String fName; | |
| 71 | + | |
| 72 | + /** 起点地址 */ | |
| 73 | + private String fAddr; | |
| 74 | + | |
| 75 | + /** 起点经度 */ | |
| 76 | + private String fLng; | |
| 77 | + | |
| 78 | + /** 起点纬度 */ | |
| 79 | + private String fLat; | |
| 80 | + | |
| 81 | + /** 终点名称 */ | |
| 82 | + private String tName; | |
| 83 | + | |
| 84 | + /** 终点地址 */ | |
| 85 | + private String tAddr; | |
| 86 | + | |
| 87 | + /** 终点经度 */ | |
| 88 | + private String tLng; | |
| 89 | + | |
| 90 | + /** 终点纬度 */ | |
| 91 | + private String tLat; | |
| 92 | + | |
| 93 | + /** 收件人姓名 */ | |
| 94 | + private String recipName; | |
| 95 | + | |
| 96 | + /** 收件人电话 */ | |
| 97 | + private String recipPhone; | |
| 98 | + | |
| 99 | + /** 附加信息JSON */ | |
| 100 | + private String extra; | |
| 101 | + | |
| 102 | + /** 取件照片JSON数组 */ | |
| 103 | + private String thumbs; | |
| 104 | + | |
| 105 | + /** 关联店铺订单ID */ | |
| 106 | + private Long storeOid; | |
| 107 | + | |
| 108 | + /** 外部业务系统订单号(接入方传入,用于回调对账) */ | |
| 109 | + private String outOrderNo; | |
| 110 | + | |
| 111 | + /** 接入方 AppKey */ | |
| 112 | + private String appKey; | |
| 113 | + | |
| 114 | + /** 状态变更回调地址(接入方提供,覆盖应用级 webhookUrl) */ | |
| 115 | + private String callbackUrl; | |
| 116 | + | |
| 117 | + /** | |
| 118 | + * 货物清单快照(JSON数组,接入方推单时传入,不耦合业务商品表) | |
| 119 | + * 格式:[{"name":"宫保鸡丁","quantity":2,"spec":"大份","remark":""}] | |
| 120 | + */ | |
| 121 | + private String itemsJson; | |
| 122 | + | |
| 123 | + /** 货物整单备注(如:放门口、不要辣) */ | |
| 124 | + private String itemRemark; | |
| 125 | + | |
| 126 | + /** 关联外部门店ID(接入方在平台注册的门店) */ | |
| 127 | + private Long extStoreId; | |
| 128 | + | |
| 129 | + /** 是否删除 */ | |
| 130 | + @TableLogic | |
| 131 | + private Integer isDel; | |
| 132 | + | |
| 133 | + /** 下单时间 */ | |
| 134 | + private Long addTime; | |
| 135 | + | |
| 136 | + /** 支付时间 */ | |
| 137 | + private Long payTime; | |
| 138 | + | |
| 139 | + /** 接单时间 */ | |
| 140 | + private Long grapTime; | |
| 141 | + | |
| 142 | + /** 取件时间 */ | |
| 143 | + private Long pickTime; | |
| 144 | + | |
| 145 | + /** 完成时间 */ | |
| 146 | + private Long completeTime; | |
| 147 | + | |
| 148 | + /** 转单时间 */ | |
| 149 | + private Long transTime; | |
| 150 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/Rider.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/Rider.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | +import java.time.LocalDateTime; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 骑手信息表 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@TableName("rider") | |
| 14 | +public class Rider { | |
| 15 | + | |
| 16 | + @TableId(type = IdType.AUTO) | |
| 17 | + private Long id; | |
| 18 | + | |
| 19 | + /** 手机号 */ | |
| 20 | + private String mobile; | |
| 21 | + | |
| 22 | + /** 登录名 */ | |
| 23 | + private String userLogin; | |
| 24 | + | |
| 25 | + /** 昵称 */ | |
| 26 | + private String userNickname; | |
| 27 | + | |
| 28 | + /** 密码(加密) */ | |
| 29 | + private String userPass; | |
| 30 | + | |
| 31 | + /** 头像 */ | |
| 32 | + private String avatar; | |
| 33 | + | |
| 34 | + /** 头像缩略图 */ | |
| 35 | + private String avatarThumb; | |
| 36 | + | |
| 37 | + /** 城市ID */ | |
| 38 | + private Long cityId; | |
| 39 | + | |
| 40 | + /** 等级ID */ | |
| 41 | + private Long levelId; | |
| 42 | + | |
| 43 | + /** 等级名称(非表字段) */ | |
| 44 | + @TableField(exist = false) | |
| 45 | + private String levelName; | |
| 46 | + | |
| 47 | + /** 类型:1=兼职 2=全职 */ | |
| 48 | + private Integer type; | |
| 49 | + | |
| 50 | + /** 审核状态:0=拒绝 1=通过 2=待审核 */ | |
| 51 | + private Integer userStatus; | |
| 52 | + | |
| 53 | + /** 账号状态:0=禁用 1=正常 */ | |
| 54 | + private Integer status; | |
| 55 | + | |
| 56 | + /** 余额(兼职骑手用) */ | |
| 57 | + private BigDecimal balance; | |
| 58 | + | |
| 59 | + /** 是否休息:0=否 1=是 */ | |
| 60 | + private Integer isRest; | |
| 61 | + | |
| 62 | + /** 身份证号 */ | |
| 63 | + private String idNo; | |
| 64 | + | |
| 65 | + /** 手持身份证照片 */ | |
| 66 | + private String thumb; | |
| 67 | + | |
| 68 | + @TableField(fill = FieldFill.INSERT) | |
| 69 | + private Long createTime; | |
| 70 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/RiderBalance.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/RiderBalance.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 骑手余额流水表 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@TableName("rider_balance") | |
| 13 | +public class RiderBalance { | |
| 14 | + | |
| 15 | + @TableId(type = IdType.AUTO) | |
| 16 | + private Long id; | |
| 17 | + | |
| 18 | + /** 骑手ID */ | |
| 19 | + private Long uid; | |
| 20 | + | |
| 21 | + /** 变动类型:1=收入 2=提现 */ | |
| 22 | + private Integer type; | |
| 23 | + | |
| 24 | + /** 关联动作(如:order_complete) */ | |
| 25 | + private String action; | |
| 26 | + | |
| 27 | + /** 关联ID(如订单ID) */ | |
| 28 | + private Long actionId; | |
| 29 | + | |
| 30 | + /** 订单号 */ | |
| 31 | + private String orderNo; | |
| 32 | + | |
| 33 | + /** 变动金额 */ | |
| 34 | + private BigDecimal nums; | |
| 35 | + | |
| 36 | + /** 变动后余额 */ | |
| 37 | + private BigDecimal total; | |
| 38 | + | |
| 39 | + private Long addTime; | |
| 40 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/RiderEvaluate.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/RiderEvaluate.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 骑手评价表(用户对骑手的配送评价) | |
| 8 | + */ | |
| 9 | +@Data | |
| 10 | +@TableName("rider_evaluate") | |
| 11 | +public class RiderEvaluate { | |
| 12 | + | |
| 13 | + @TableId(type = IdType.AUTO) | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + /** 用户ID(评价人) */ | |
| 17 | + private Long uid; | |
| 18 | + | |
| 19 | + /** 订单ID */ | |
| 20 | + private Long oid; | |
| 21 | + | |
| 22 | + /** 骑手ID */ | |
| 23 | + private Long rid; | |
| 24 | + | |
| 25 | + /** 评价内容 */ | |
| 26 | + private String content; | |
| 27 | + | |
| 28 | + /** 星级:1-5 */ | |
| 29 | + private Integer star; | |
| 30 | + | |
| 31 | + /** 城市ID */ | |
| 32 | + private Long cityId; | |
| 33 | + | |
| 34 | + private Long addTime; | |
| 35 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/RiderLevel.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/RiderLevel.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 骑手等级配置表 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@TableName("rider_level") | |
| 13 | +public class RiderLevel { | |
| 14 | + | |
| 15 | + @TableId(type = IdType.AUTO) | |
| 16 | + private Long id; | |
| 17 | + | |
| 18 | + /** 城市ID */ | |
| 19 | + private Long cityId; | |
| 20 | + | |
| 21 | + /** 等级编号 */ | |
| 22 | + private Integer levelId; | |
| 23 | + | |
| 24 | + /** 等级名称 */ | |
| 25 | + private String name; | |
| 26 | + | |
| 27 | + /** 是否默认等级 */ | |
| 28 | + private Integer isDefault; | |
| 29 | + | |
| 30 | + /** 转单次数上限 */ | |
| 31 | + private Integer transNums; | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 跑腿类收入模式:1=固定金额 2=按比例 3=按距离 | |
| 35 | + */ | |
| 36 | + private Integer runFeeMode; | |
| 37 | + | |
| 38 | + /** 跑腿固定金额(mode=1) */ | |
| 39 | + private BigDecimal runFixMoney; | |
| 40 | + | |
| 41 | + /** 跑腿比例(mode=2,百分比) */ | |
| 42 | + private BigDecimal runRate; | |
| 43 | + | |
| 44 | + /** 起始距离(mode=3,米) */ | |
| 45 | + private Integer distanceBasic; | |
| 46 | + | |
| 47 | + /** 基础配送费(mode=3) */ | |
| 48 | + private BigDecimal distanceBasicMoney; | |
| 49 | + | |
| 50 | + /** 超出每公里费用(mode=3) */ | |
| 51 | + private BigDecimal distanceMoreMoney; | |
| 52 | + | |
| 53 | + /** 最高配送费上限(mode=3) */ | |
| 54 | + private BigDecimal distanceMaxMoney; | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 办事类收入模式:1=固定金额 2=按比例 | |
| 58 | + */ | |
| 59 | + private Integer workFeeMode; | |
| 60 | + | |
| 61 | + /** 办事固定金额(mode=1) */ | |
| 62 | + private BigDecimal workFixMoney; | |
| 63 | + | |
| 64 | + /** 办事比例(mode=2,百分比) */ | |
| 65 | + private BigDecimal workRate; | |
| 66 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/RiderLocation.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/RiderLocation.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 骑手位置表 | |
| 8 | + */ | |
| 9 | +@Data | |
| 10 | +@TableName("rider_location") | |
| 11 | +public class RiderLocation { | |
| 12 | + | |
| 13 | + @TableId(type = IdType.AUTO) | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + /** 骑手ID */ | |
| 17 | + private Long uid; | |
| 18 | + | |
| 19 | + /** 经度 */ | |
| 20 | + private String lng; | |
| 21 | + | |
| 22 | + /** 纬度 */ | |
| 23 | + private String lat; | |
| 24 | + | |
| 25 | + /** 更新时间 */ | |
| 26 | + private Long updateTime; | |
| 27 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/RiderOrderCount.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/RiderOrderCount.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 骑手订单统计表 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@TableName("rider_order_count") | |
| 13 | +public class RiderOrderCount { | |
| 14 | + | |
| 15 | + @TableId(type = IdType.AUTO) | |
| 16 | + private Long id; | |
| 17 | + | |
| 18 | + /** 骑手ID */ | |
| 19 | + private Long uid; | |
| 20 | + | |
| 21 | + /** 统计日期(yyyyMMdd) */ | |
| 22 | + private Integer countDate; | |
| 23 | + | |
| 24 | + /** 完成订单数 */ | |
| 25 | + private Integer orders; | |
| 26 | + | |
| 27 | + /** 转单数 */ | |
| 28 | + private Integer transfers; | |
| 29 | + | |
| 30 | + /** 配送距离(米) */ | |
| 31 | + private Long distance; | |
| 32 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/RiderOrderRefuse.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/RiderOrderRefuse.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 骑手拒单记录表 | |
| 8 | + */ | |
| 9 | +@Data | |
| 10 | +@TableName("rider_orders_refuse") | |
| 11 | +public class RiderOrderRefuse { | |
| 12 | + | |
| 13 | + @TableId(type = IdType.AUTO) | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + /** 骑手ID */ | |
| 17 | + private Long riderId; | |
| 18 | + | |
| 19 | + /** 订单ID */ | |
| 20 | + private Long oid; | |
| 21 | + | |
| 22 | + /** 拒单时间 */ | |
| 23 | + private Long addTime; | |
| 24 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/Substation.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/Substation.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 分站管理员表 | |
| 8 | + * 每个城市对应一个分站管理员,负责该城市骑手/订单日常运营 | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +@TableName("substation") | |
| 12 | +public class Substation { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + /** 管理的城市ID */ | |
| 18 | + private Long cityId; | |
| 19 | + | |
| 20 | + /** 登录账号 */ | |
| 21 | + private String userLogin; | |
| 22 | + | |
| 23 | + /** 昵称 */ | |
| 24 | + private String userNickname; | |
| 25 | + | |
| 26 | + /** 密码(MD5) */ | |
| 27 | + private String userPass; | |
| 28 | + | |
| 29 | + /** 手机号 */ | |
| 30 | + private String mobile; | |
| 31 | + | |
| 32 | + /** 头像 */ | |
| 33 | + private String avatar; | |
| 34 | + | |
| 35 | + /** 状态:0=禁用 1=正常 */ | |
| 36 | + private Integer userStatus; | |
| 37 | + | |
| 38 | + private Long createTime; | |
| 39 | +} | ... | ... |
src/main/java/com/diligrp/rider/entity/WebhookLog.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/entity/WebhookLog.java | |
| 1 | +package com.diligrp.rider.entity; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.annotation.*; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * Webhook 推送日志表 | |
| 8 | + * 记录每次回调的发送结果,便于排查问题和重试 | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +@TableName("webhook_log") | |
| 12 | +public class WebhookLog { | |
| 13 | + | |
| 14 | + @TableId(type = IdType.AUTO) | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + /** 应用ID */ | |
| 18 | + private Long appId; | |
| 19 | + | |
| 20 | + /** 事件类型,如 order.completed */ | |
| 21 | + private String event; | |
| 22 | + | |
| 23 | + /** 推送的业务ID(如订单ID) */ | |
| 24 | + private Long bizId; | |
| 25 | + | |
| 26 | + /** 推送URL */ | |
| 27 | + private String url; | |
| 28 | + | |
| 29 | + /** 推送内容(JSON) */ | |
| 30 | + private String payload; | |
| 31 | + | |
| 32 | + /** HTTP响应码 */ | |
| 33 | + private Integer responseCode; | |
| 34 | + | |
| 35 | + /** 响应内容(截取前500字) */ | |
| 36 | + private String responseBody; | |
| 37 | + | |
| 38 | + /** 状态:0=失败 1=成功 */ | |
| 39 | + private Integer status; | |
| 40 | + | |
| 41 | + /** 重试次数 */ | |
| 42 | + private Integer retryCount; | |
| 43 | + | |
| 44 | + private Long createTime; | |
| 45 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/AdminUserMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/AdminUserMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.AdminUser; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface AdminUserMapper extends BaseMapper<AdminUser> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/CityMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/CityMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.City; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface CityMapper extends BaseMapper<City> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanDimensionMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanDimensionMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.DeliveryFeePlanDimension; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface DeliveryFeePlanDimensionMapper extends BaseMapper<DeliveryFeePlanDimension> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanDistanceStepMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanDistanceStepMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.DeliveryFeePlanDistanceStep; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface DeliveryFeePlanDistanceStepMapper extends BaseMapper<DeliveryFeePlanDistanceStep> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.DeliveryFeePlan; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface DeliveryFeePlanMapper extends BaseMapper<DeliveryFeePlan> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanPieceRuleMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanPieceRuleMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.DeliveryFeePlanPieceRule; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface DeliveryFeePlanPieceRuleMapper extends BaseMapper<DeliveryFeePlanPieceRule> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanTimeRuleMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/DeliveryFeePlanTimeRuleMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.DeliveryFeePlanTimeRule; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface DeliveryFeePlanTimeRuleMapper extends BaseMapper<DeliveryFeePlanTimeRule> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/ExtStoreMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/ExtStoreMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.ExtStore; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface ExtStoreMapper extends BaseMapper<ExtStore> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/MerchantEnterMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/MerchantEnterMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.MerchantEnter; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface MerchantEnterMapper extends BaseMapper<MerchantEnter> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/MerchantStoreMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/MerchantStoreMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.MerchantStore; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface MerchantStoreMapper extends BaseMapper<MerchantStore> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/MerchantUsersMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/MerchantUsersMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.MerchantUsers; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface MerchantUsersMapper extends BaseMapper<MerchantUsers> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/OpenAppMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/OpenAppMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.OpenApp; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface OpenAppMapper extends BaseMapper<OpenApp> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/OrderRefundReasonMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/OrderRefundReasonMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.OrderRefundReason; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface OrderRefundReasonMapper extends BaseMapper<OrderRefundReason> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/OrderRefundRecordMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/OrderRefundRecordMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.OrderRefundRecord; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface OrderRefundRecordMapper extends BaseMapper<OrderRefundRecord> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/OrdersMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/OrdersMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.Orders; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | +import org.apache.ibatis.annotations.Param; | |
| 7 | + | |
| 8 | +@Mapper | |
| 9 | +public interface OrdersMapper extends BaseMapper<Orders> { | |
| 10 | + | |
| 11 | + /** 查询骑手今日转单次数 */ | |
| 12 | + int countTodayTrans(@Param("riderId") Long riderId, @Param("todayStart") long todayStart); | |
| 13 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderBalanceMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderBalanceMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.RiderBalance; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface RiderBalanceMapper extends BaseMapper<RiderBalance> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderEvaluateMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderEvaluateMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.RiderEvaluate; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface RiderEvaluateMapper extends BaseMapper<RiderEvaluate> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderLevelMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderLevelMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.RiderLevel; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface RiderLevelMapper extends BaseMapper<RiderLevel> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderLocationMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderLocationMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.RiderLocation; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface RiderLocationMapper extends BaseMapper<RiderLocation> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.Rider; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface RiderMapper extends BaseMapper<Rider> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderOrderCountMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderOrderCountMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.RiderOrderCount; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | +import org.apache.ibatis.annotations.Param; | |
| 7 | + | |
| 8 | +@Mapper | |
| 9 | +public interface RiderOrderCountMapper extends BaseMapper<RiderOrderCount> { | |
| 10 | + | |
| 11 | + /** 累加骑手订单统计 */ | |
| 12 | + void upsertCount(@Param("uid") Long uid, @Param("countDate") int countDate, | |
| 13 | + @Param("orders") int orders, @Param("distance") long distance, | |
| 14 | + @Param("transfers") int transfers); | |
| 15 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/RiderOrderRefuseMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/RiderOrderRefuseMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.RiderOrderRefuse; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | +import org.apache.ibatis.annotations.Param; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +@Mapper | |
| 11 | +public interface RiderOrderRefuseMapper extends BaseMapper<RiderOrderRefuse> { | |
| 12 | + | |
| 13 | + /** 查询骑手已拒绝的订单ID列表 */ | |
| 14 | + List<Long> selectRefuseOrderIds(@Param("riderId") Long riderId); | |
| 15 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/SubstationMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/SubstationMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.Substation; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface SubstationMapper extends BaseMapper<Substation> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/mapper/WebhookLogMapper.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/mapper/WebhookLogMapper.java | |
| 1 | +package com.diligrp.rider.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 4 | +import com.diligrp.rider.entity.WebhookLog; | |
| 5 | +import org.apache.ibatis.annotations.Mapper; | |
| 6 | + | |
| 7 | +@Mapper | |
| 8 | +public interface WebhookLogMapper extends BaseMapper<WebhookLog> { | |
| 9 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/AdminRiderLevelService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/AdminRiderLevelService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.AdminRiderLevelSaveDTO; | |
| 4 | +import com.diligrp.rider.entity.RiderLevel; | |
| 5 | + | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +public interface AdminRiderLevelService { | |
| 9 | + List<RiderLevel> list(Long cityId); | |
| 10 | + | |
| 11 | + void add(AdminRiderLevelSaveDTO dto, Long cityId); | |
| 12 | + | |
| 13 | + void edit(AdminRiderLevelSaveDTO dto, Long cityId); | |
| 14 | + | |
| 15 | + void setDefault(Long id, Long cityId); | |
| 16 | + | |
| 17 | + void delete(Long id, Long cityId); | |
| 18 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/AdminRiderService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/AdminRiderService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.AdminRiderAddDTO; | |
| 4 | +import com.diligrp.rider.entity.Rider; | |
| 5 | + | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +/** 后台管理:骑手管理 */ | |
| 9 | +public interface AdminRiderService { | |
| 10 | + /** 新增骑手 */ | |
| 11 | + void add(AdminRiderAddDTO dto, Long cityId); | |
| 12 | + /** 骑手列表 */ | |
| 13 | + List<Rider> list(String keyword, Integer userStatus, Long cityId); | |
| 14 | + /** 指派候选骑手列表 */ | |
| 15 | + List<Rider> designateCandidates(Long orderId, Long cityId); | |
| 16 | + /** 审核骑手(通过/拒绝) */ | |
| 17 | + void setStatus(Long riderId, int status); | |
| 18 | + /** 设置骑手等级,为空则使用默认等级 */ | |
| 19 | + void setLevel(Long riderId, Long levelId, Long cityId); | |
| 20 | + /** 启用/禁用骑手账号 */ | |
| 21 | + void setEnableStatus(Long riderId, int status); | |
| 22 | + /** 切换全职/兼职 */ | |
| 23 | + void setType(Long riderId, int type); | |
| 24 | + /** 指派骑手接单 */ | |
| 25 | + void designate(Long orderId, Long riderId); | |
| 26 | + /** 处理转单申请(1=通过 3=拒绝) */ | |
| 27 | + void setTrans(Long orderId, int trans); | |
| 28 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/CityService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/CityService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 4 | +import com.diligrp.rider.entity.City; | |
| 5 | +import com.diligrp.rider.vo.CityVO; | |
| 6 | + | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | +public interface CityService { | |
| 10 | + /** 获取两级城市树(省→市) */ | |
| 11 | + List<CityVO> getTree(); | |
| 12 | + /** 获取已开通城市列表(市级) */ | |
| 13 | + List<CityVO> getOpenList(); | |
| 14 | + /** 新增城市 */ | |
| 15 | + void add(City city); | |
| 16 | + /** 编辑城市基础信息 */ | |
| 17 | + void edit(City city); | |
| 18 | + /** 设置开通/关闭状态 */ | |
| 19 | + void setStatus(Long cityId, int status); | |
| 20 | + /** 获取某城市配送费配置 */ | |
| 21 | + DeliveryPricingConfigDTO getConfig(Long cityId); | |
| 22 | + /** 根据ID获取城市 */ | |
| 23 | + City getById(Long cityId); | |
| 24 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/DeliveryFeePlanService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/DeliveryFeePlanService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.DeliveryFeePlanPreviewDTO; | |
| 4 | +import com.diligrp.rider.dto.DeliveryFeePlanSaveDTO; | |
| 5 | +import com.diligrp.rider.vo.DeliveryFeePlanDetailVO; | |
| 6 | +import com.diligrp.rider.vo.DeliveryFeePlanVO; | |
| 7 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 8 | + | |
| 9 | +import java.util.List; | |
| 10 | + | |
| 11 | +public interface DeliveryFeePlanService { | |
| 12 | + | |
| 13 | + List<DeliveryFeePlanVO> listPlans(Long cityId); | |
| 14 | + | |
| 15 | + DeliveryFeePlanDetailVO getPlanDetail(Long cityId, Long planId); | |
| 16 | + | |
| 17 | + Long createPlan(Long cityId, DeliveryFeePlanSaveDTO dto); | |
| 18 | + | |
| 19 | + Long initializeDefaultPlan(Long cityId); | |
| 20 | + | |
| 21 | + void updatePlan(Long cityId, Long planId, DeliveryFeePlanSaveDTO dto); | |
| 22 | + | |
| 23 | + Long copyPlan(Long cityId, Long planId); | |
| 24 | + | |
| 25 | + void deletePlan(Long cityId, Long planId); | |
| 26 | + | |
| 27 | + void setDefaultPlan(Long cityId, Long planId); | |
| 28 | + | |
| 29 | + DeliveryFeeResultVO preview(Long cityId, DeliveryFeePlanPreviewDTO dto); | |
| 30 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/DeliveryFeeService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/DeliveryFeeService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 4 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 5 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 6 | + | |
| 7 | +public interface DeliveryFeeService { | |
| 8 | + /** | |
| 9 | + * 计算配送费(对内中台核心接口) | |
| 10 | + * Helpsend.computed() + City.checkTime() + City.getLength() | |
| 11 | + */ | |
| 12 | + DeliveryFeeResultVO calcFee(DeliveryFeeCalcDTO dto); | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 使用指定配置试算配送费 | |
| 16 | + */ | |
| 17 | + DeliveryFeeResultVO calcFeeByConfig(DeliveryPricingConfigDTO pricingConfig, DeliveryFeeCalcDTO dto); | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 检查指定城市是否开通某类型服务 | |
| 21 | + */ | |
| 22 | + boolean isServiceEnabled(Long cityId, int orderType); | |
| 23 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/DeliveryOrderService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/DeliveryOrderService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.DeliveryOrderCreateDTO; | |
| 4 | +import com.diligrp.rider.vo.DeliveryOrderCreateVO; | |
| 5 | + | |
| 6 | +public interface DeliveryOrderService { | |
| 7 | + /** | |
| 8 | + * 外部系统推单(核心接口) | |
| 9 | + * 1. 校验城市是否开通服务 | |
| 10 | + * 2. 计算配送费 | |
| 11 | + * 3. 创建配送订单(status=2待接单) | |
| 12 | + * 4. 返回订单信息供接入方展示 | |
| 13 | + */ | |
| 14 | + DeliveryOrderCreateVO create(String appKey, DeliveryOrderCreateDTO dto); | |
| 15 | + | |
| 16 | + /** 查询订单状态(供接入方轮询) */ | |
| 17 | + DeliveryOrderCreateVO queryByOutOrderNo(String appKey, String outOrderNo); | |
| 18 | + | |
| 19 | + /** 取消订单(仅 status=2 可取消) */ | |
| 20 | + void cancel(String appKey, String outOrderNo); | |
| 21 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/ExtStoreService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/ExtStoreService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.entity.ExtStore; | |
| 4 | + | |
| 5 | +import java.util.List; | |
| 6 | + | |
| 7 | +public interface ExtStoreService { | |
| 8 | + /** 同步门店(新增或更新,以 appKey+outStoreId 为唯一键) */ | |
| 9 | + ExtStore syncStore(String appKey, ExtStore store); | |
| 10 | + /** 查询某应用下的门店列表 */ | |
| 11 | + List<ExtStore> listByApp(String appKey); | |
| 12 | + /** 获取单个门店(appKey为null时不校验归属) */ | |
| 13 | + ExtStore getById(Long id, String appKey); | |
| 14 | + /** 设置门店状态 */ | |
| 15 | + void setStatus(Long id, String appKey, int status); | |
| 16 | + /** 删除门店 */ | |
| 17 | + void delete(Long id, String appKey); | |
| 18 | + /** 平台管理端:查看所有门店(可按 appKey/cityId 过滤) */ | |
| 19 | + List<ExtStore> listAll(String appKey, Long cityId, int page); | |
| 20 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/MerchantService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/MerchantService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.MerchantStoreDTO; | |
| 4 | +import com.diligrp.rider.entity.MerchantStore; | |
| 5 | + | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +public interface MerchantService { | |
| 9 | + // ---- 店铺管理 ---- | |
| 10 | + /** 新增店铺(平台后台直接新建,无需审核) */ | |
| 11 | + Long addStore(MerchantStoreDTO dto); | |
| 12 | + /** 编辑店铺 */ | |
| 13 | + void editStore(MerchantStoreDTO dto); | |
| 14 | + /** 店铺列表 */ | |
| 15 | + List<MerchantStore> storeList(Long cityId, String keyword, int page); | |
| 16 | + /** 获取店铺详情 */ | |
| 17 | + MerchantStore getStore(Long storeId); | |
| 18 | + /** 设置营业/打烊 */ | |
| 19 | + void setOperatingState(Long storeId, int state); | |
| 20 | + /** 设置自动接单 */ | |
| 21 | + void setAutoOrder(Long storeId, int auto); | |
| 22 | + /** 更新免运费和起送金额 */ | |
| 23 | + void updateFeeConfig(Long storeId, java.math.BigDecimal freeShipping, java.math.BigDecimal upToSend); | |
| 24 | + /** 删除店铺 */ | |
| 25 | + void delStore(Long storeId); | |
| 26 | + /** | |
| 27 | + * 外部系统同步门店(新增或更新,以 appKey+outStoreId 为唯一键) | |
| 28 | + */ | |
| 29 | + MerchantStore syncStore(String appKey, MerchantStoreDTO dto); | |
| 30 | + /** | |
| 31 | + * 根据 appKey + outStoreId 查询门店 | |
| 32 | + */ | |
| 33 | + MerchantStore getByOutStoreId(String appKey, String outStoreId); | |
| 34 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/OpenAppService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/OpenAppService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.entity.OpenApp; | |
| 4 | + | |
| 5 | +import java.util.List; | |
| 6 | + | |
| 7 | +public interface OpenAppService { | |
| 8 | + /** 创建应用,自动生成 AppKey/AppSecret,cityId 为必填(租户隔离) */ | |
| 9 | + OpenApp create(String appName, Long cityId, Long storeId, String webhookUrl, String webhookEvents, String remark); | |
| 10 | + /** 列表 */ | |
| 11 | + List<OpenApp> list(int page); | |
| 12 | + /** 重置 AppSecret */ | |
| 13 | + String resetSecret(Long appId); | |
| 14 | + /** 启用/禁用 */ | |
| 15 | + void setStatus(Long appId, int status); | |
| 16 | + /** 更新 Webhook 配置 */ | |
| 17 | + void updateWebhook(Long appId, String webhookUrl, String webhookEvents); | |
| 18 | + /** 根据 AppKey 获取应用(用于签名验证) */ | |
| 19 | + OpenApp getByAppKey(String appKey); | |
| 20 | + /** 验证签名 */ | |
| 21 | + boolean verifySign(String appKey, String timestamp, String nonce, String sign); | |
| 22 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RefundService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RefundService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.entity.OrderRefundReason; | |
| 4 | +import com.diligrp.rider.entity.OrderRefundRecord; | |
| 5 | + | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +public interface RefundService { | |
| 9 | + /** 退款原因列表 */ | |
| 10 | + List<OrderRefundReason> getReasons(int role); | |
| 11 | + /** 用户/骑手申请退款 */ | |
| 12 | + void applyRefund(Long orderId, Long uid, int role, Long reasonId, String reason); | |
| 13 | + /** 分站审核退款:status=1通过 2拒绝 */ | |
| 14 | + void handleRefund(Long recordId, int status, String remark); | |
| 15 | + /** 查询订单退款记录 */ | |
| 16 | + OrderRefundRecord getByOrderId(Long orderId); | |
| 17 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RiderAuthService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RiderAuthService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.ApplyDTO; | |
| 4 | +import com.diligrp.rider.dto.LoginDTO; | |
| 5 | +import com.diligrp.rider.vo.RiderVO; | |
| 6 | + | |
| 7 | +public interface RiderAuthService { | |
| 8 | + /** 骑手申请注册 */ | |
| 9 | + void apply(ApplyDTO dto); | |
| 10 | + /** 密码登录 */ | |
| 11 | + RiderVO loginByPass(LoginDTO dto); | |
| 12 | + /** 获取骑手信息 */ | |
| 13 | + RiderVO getInfo(Long riderId); | |
| 14 | + /** 切换休息状态 */ | |
| 15 | + void toggleRest(Long riderId); | |
| 16 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RiderBalanceService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RiderBalanceService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.vo.BalanceVO; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | + | |
| 7 | +public interface RiderBalanceService { | |
| 8 | + /** 查询余额和流水 */ | |
| 9 | + BalanceVO getBalance(Long riderId, int page); | |
| 10 | + /** 今日收入统计(配送收入总和) */ | |
| 11 | + BigDecimal getTodayIncome(Long riderId); | |
| 12 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RiderEvaluateService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RiderEvaluateService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.entity.RiderEvaluate; | |
| 4 | + | |
| 5 | +import java.util.List; | |
| 6 | + | |
| 7 | +public interface RiderEvaluateService { | |
| 8 | + /** 用户对骑手评价(订单完成后) */ | |
| 9 | + void evaluate(Long uid, Long orderId, int star, String content); | |
| 10 | + /** 骑手评价列表 */ | |
| 11 | + List<RiderEvaluate> getRiderEvaluates(Long riderId, Integer type, int page); | |
| 12 | + /** 本月好评数 */ | |
| 13 | + int getMonthGoodCount(Long riderId); | |
| 14 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RiderLevelService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RiderLevelService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.entity.RiderLevel; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | + | |
| 7 | +public interface RiderLevelService { | |
| 8 | + /** 根据骑手获取等级配置 */ | |
| 9 | + RiderLevel getLevelByRider(Long riderId); | |
| 10 | + /** | |
| 11 | + * 计算骑手收入 | |
| 12 | + * @param riderId 骑手ID | |
| 13 | + * @param orderType 订单类型 | |
| 14 | + * @param deliveryFee 配送费 | |
| 15 | + * @param distance 距离(米) | |
| 16 | + */ | |
| 17 | + BigDecimal calcIncome(Long riderId, int orderType, BigDecimal deliveryFee, long distance); | |
| 18 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RiderLocationService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RiderLocationService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.LocationDTO; | |
| 4 | +import com.diligrp.rider.vo.NearbyRiderVO; | |
| 5 | + | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +public interface RiderLocationService { | |
| 9 | + /** 上报骑手位置 */ | |
| 10 | + void updateLocation(Long riderId, LocationDTO dto); | |
| 11 | + /** 获取骑手位置 */ | |
| 12 | + LocationDTO getLocation(Long riderId); | |
| 13 | + /** | |
| 14 | + * 获取附近在线骑手列表 | |
| 15 | + * Location.getNearby() | |
| 16 | + * @param cityId 城市ID | |
| 17 | + * @param lng 查询点经度 | |
| 18 | + * @param lat 查询点纬度 | |
| 19 | + */ | |
| 20 | + List<NearbyRiderVO> getNearby(Long cityId, String lng, String lat); | |
| 21 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/RiderOrderService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/RiderOrderService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.vo.OrderVO; | |
| 4 | +import com.diligrp.rider.vo.RiderMonthCountVO; | |
| 5 | +import com.diligrp.rider.vo.RiderTodayCountVO; | |
| 6 | + | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | +public interface RiderOrderService { | |
| 10 | + /** 订单列表:type=1待接单 2待取货 3待完成 */ | |
| 11 | + List<OrderVO> getList(Long riderId, Long cityId, Integer type, int page); | |
| 12 | + /** 订单详情 */ | |
| 13 | + OrderVO getDetail(Long riderId, Long orderId); | |
| 14 | + /** 拒单 */ | |
| 15 | + void refuse(Long riderId, Long cityId, Long orderId); | |
| 16 | + /** 抢单 */ | |
| 17 | + void grap(Long riderId, Long cityId, Long orderId); | |
| 18 | + /** 开始服务(取件),输入完成码 */ | |
| 19 | + void start(Long riderId, Long orderId, String code); | |
| 20 | + /** 完成订单,上传照片 */ | |
| 21 | + void complete(Long riderId, Long orderId, String thumbsJson); | |
| 22 | + /** 骑手申请转单 */ | |
| 23 | + void applyTrans(Long riderId, Long orderId); | |
| 24 | + /** 今日统计 */ | |
| 25 | + RiderTodayCountVO getTodayCount(Long riderId); | |
| 26 | + /** 月度统计 */ | |
| 27 | + List<RiderMonthCountVO> getMonthCount(Long riderId, int year); | |
| 28 | + /** | |
| 29 | + * 骑手订单明细列表(历史) | |
| 30 | + * type=0全部 1已完成 2已转单 | |
| 31 | + */ | |
| 32 | + List<OrderVO> getCountList(Long riderId, int type, int page); | |
| 33 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/SubstationService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/SubstationService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.entity.Substation; | |
| 4 | + | |
| 5 | +import java.util.List; | |
| 6 | + | |
| 7 | +public interface SubstationService { | |
| 8 | + /** 列表 */ | |
| 9 | + List<Substation> list(String keyword); | |
| 10 | + /** 新增分站管理员 */ | |
| 11 | + void add(Substation substation); | |
| 12 | + /** 编辑 */ | |
| 13 | + void edit(Substation substation); | |
| 14 | + /** 禁用 */ | |
| 15 | + void ban(Long id); | |
| 16 | + /** 启用 */ | |
| 17 | + void cancelBan(Long id); | |
| 18 | + /** 删除 */ | |
| 19 | + void del(Long id); | |
| 20 | + /** 根据城市ID获取分站管理员 */ | |
| 21 | + Substation getByCityId(Long cityId); | |
| 22 | + /** 分站管理员修改自己的密码 */ | |
| 23 | + void changePassword(Long substationId, String oldPassword, String newPassword); | |
| 24 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/WebhookService.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/WebhookService.java | |
| 1 | +package com.diligrp.rider.service; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Webhook 推送服务 | |
| 5 | + * 订单状态变更时调用此服务通知接入方 | |
| 6 | + */ | |
| 7 | +public interface WebhookService { | |
| 8 | + /** | |
| 9 | + * 发送 Webhook 通知 | |
| 10 | + * @param event 事件名称,如 order.paid / order.completed / order.cancelled | |
| 11 | + * @param bizId 业务ID(订单ID等) | |
| 12 | + * @param payload 推送内容(JSON字符串) | |
| 13 | + */ | |
| 14 | + void send(String event, Long bizId, String payload); | |
| 15 | + | |
| 16 | + /** 重试失败的 Webhook */ | |
| 17 | + void retry(Long logId); | |
| 18 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/AdminAuthServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/AdminAuthServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.diligrp.rider.common.exception.BizException; | |
| 5 | +import com.diligrp.rider.config.JwtUtil; | |
| 6 | +import com.diligrp.rider.dto.AdminLoginDTO; | |
| 7 | +import com.diligrp.rider.entity.AdminUser; | |
| 8 | +import com.diligrp.rider.entity.Substation; | |
| 9 | +import com.diligrp.rider.mapper.AdminUserMapper; | |
| 10 | +import com.diligrp.rider.mapper.SubstationMapper; | |
| 11 | +import com.diligrp.rider.vo.AdminLoginVO; | |
| 12 | +import lombok.RequiredArgsConstructor; | |
| 13 | +import org.springframework.stereotype.Service; | |
| 14 | +import org.springframework.util.DigestUtils; | |
| 15 | + | |
| 16 | +import java.nio.charset.StandardCharsets; | |
| 17 | + | |
| 18 | +@Service | |
| 19 | +@RequiredArgsConstructor | |
| 20 | +public class AdminAuthServiceImpl { | |
| 21 | + | |
| 22 | + private final AdminUserMapper adminUserMapper; | |
| 23 | + private final SubstationMapper substationMapper; | |
| 24 | + private final JwtUtil jwtUtil; | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 统一登录入口 | |
| 28 | + * role=admin:超级管理员登录(admin_user表) | |
| 29 | + * role=substation:分站管理员登录(substation表) | |
| 30 | + */ | |
| 31 | + public AdminLoginVO login(AdminLoginDTO dto) { | |
| 32 | + if ("admin".equals(dto.getRole())) { | |
| 33 | + return loginAdmin(dto.getAccount(), dto.getPass()); | |
| 34 | + } | |
| 35 | + return loginSubstation(dto.getAccount(), dto.getPass()); | |
| 36 | + } | |
| 37 | + | |
| 38 | + private AdminLoginVO loginAdmin(String account, String pass) { | |
| 39 | + AdminUser user = adminUserMapper.selectOne(new LambdaQueryWrapper<AdminUser>() | |
| 40 | + .eq(AdminUser::getUserLogin, account).last("LIMIT 1")); | |
| 41 | + if (user == null) throw new BizException("账号不存在"); | |
| 42 | + if (!encryptPass(pass).equals(user.getUserPass())) throw new BizException("密码错误"); | |
| 43 | + if (user.getUserStatus() == null || user.getUserStatus() == 0) throw new BizException("账号已被禁用"); | |
| 44 | + | |
| 45 | + AdminLoginVO vo = new AdminLoginVO(); | |
| 46 | + vo.setId(user.getId()); | |
| 47 | + vo.setUserLogin(user.getUserLogin()); | |
| 48 | + vo.setUserNickname(user.getUserNickname()); | |
| 49 | + vo.setRole("admin"); | |
| 50 | + vo.setToken(jwtUtil.generateAdminToken(user.getId(), "admin")); | |
| 51 | + return vo; | |
| 52 | + } | |
| 53 | + | |
| 54 | + private AdminLoginVO loginSubstation(String account, String pass) { | |
| 55 | + Substation sub = substationMapper.selectOne(new LambdaQueryWrapper<Substation>() | |
| 56 | + .eq(Substation::getUserLogin, account).last("LIMIT 1")); | |
| 57 | + if (sub == null) throw new BizException("账号不存在"); | |
| 58 | + if (!encryptPass(pass).equals(sub.getUserPass())) throw new BizException("密码错误"); | |
| 59 | + if (sub.getUserStatus() == null || sub.getUserStatus() == 0) throw new BizException("账号已被禁用"); | |
| 60 | + | |
| 61 | + AdminLoginVO vo = new AdminLoginVO(); | |
| 62 | + vo.setId(sub.getId()); | |
| 63 | + vo.setUserLogin(sub.getUserLogin()); | |
| 64 | + vo.setUserNickname(sub.getUserNickname()); | |
| 65 | + vo.setRole("substation"); | |
| 66 | + vo.setCityId(sub.getCityId()); | |
| 67 | + vo.setToken(jwtUtil.generateAdminToken(sub.getId(), "substation")); | |
| 68 | + return vo; | |
| 69 | + } | |
| 70 | + | |
| 71 | + private String encryptPass(String pass) { | |
| 72 | + return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8)); | |
| 73 | + } | |
| 74 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/AdminRiderLevelServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/AdminRiderLevelServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.dto.AdminRiderLevelSaveDTO; | |
| 7 | +import com.diligrp.rider.entity.Rider; | |
| 8 | +import com.diligrp.rider.entity.RiderLevel; | |
| 9 | +import com.diligrp.rider.mapper.RiderLevelMapper; | |
| 10 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 11 | +import com.diligrp.rider.service.AdminRiderLevelService; | |
| 12 | +import lombok.RequiredArgsConstructor; | |
| 13 | +import org.springframework.stereotype.Service; | |
| 14 | + | |
| 15 | +import java.math.BigDecimal; | |
| 16 | +import java.util.List; | |
| 17 | + | |
| 18 | +@Service | |
| 19 | +@RequiredArgsConstructor | |
| 20 | +public class AdminRiderLevelServiceImpl implements AdminRiderLevelService { | |
| 21 | + | |
| 22 | + private final RiderLevelMapper riderLevelMapper; | |
| 23 | + private final RiderMapper riderMapper; | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public List<RiderLevel> list(Long cityId) { | |
| 27 | + return riderLevelMapper.selectList(new LambdaQueryWrapper<RiderLevel>() | |
| 28 | + .eq(RiderLevel::getCityId, cityId) | |
| 29 | + .orderByAsc(RiderLevel::getLevelId) | |
| 30 | + .orderByDesc(RiderLevel::getIsDefault)); | |
| 31 | + } | |
| 32 | + | |
| 33 | + @Override | |
| 34 | + public void add(AdminRiderLevelSaveDTO dto, Long cityId) { | |
| 35 | + validateCity(cityId); | |
| 36 | + ensureLevelIdUnique(cityId, dto.getLevelId(), null); | |
| 37 | + | |
| 38 | + RiderLevel level = new RiderLevel(); | |
| 39 | + fillLevel(level, dto, cityId); | |
| 40 | + level.setIsDefault(hasDefault(cityId) ? 0 : 1); | |
| 41 | + riderLevelMapper.insert(level); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public void edit(AdminRiderLevelSaveDTO dto, Long cityId) { | |
| 46 | + validateCity(cityId); | |
| 47 | + RiderLevel existing = getOwnedLevel(dto.getId(), cityId); | |
| 48 | + ensureLevelIdUnique(cityId, dto.getLevelId(), existing.getId()); | |
| 49 | + fillLevel(existing, dto, cityId); | |
| 50 | + riderLevelMapper.updateById(existing); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public void setDefault(Long id, Long cityId) { | |
| 55 | + validateCity(cityId); | |
| 56 | + RiderLevel existing = getOwnedLevel(id, cityId); | |
| 57 | + riderLevelMapper.update(null, new LambdaUpdateWrapper<RiderLevel>() | |
| 58 | + .eq(RiderLevel::getCityId, cityId) | |
| 59 | + .set(RiderLevel::getIsDefault, 0)); | |
| 60 | + riderLevelMapper.update(null, new LambdaUpdateWrapper<RiderLevel>() | |
| 61 | + .eq(RiderLevel::getId, existing.getId()) | |
| 62 | + .set(RiderLevel::getIsDefault, 1)); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Override | |
| 66 | + public void delete(Long id, Long cityId) { | |
| 67 | + validateCity(cityId); | |
| 68 | + RiderLevel existing = getOwnedLevel(id, cityId); | |
| 69 | + if (existing.getIsDefault() != null && existing.getIsDefault() == 1) { | |
| 70 | + throw new BizException("默认等级不能删除"); | |
| 71 | + } | |
| 72 | + Long riderCount = riderMapper.selectCount(new LambdaQueryWrapper<Rider>() | |
| 73 | + .eq(Rider::getLevelId, existing.getId())); | |
| 74 | + if (riderCount != null && riderCount > 0) { | |
| 75 | + throw new BizException("该等级已有骑手使用,不能删除"); | |
| 76 | + } | |
| 77 | + riderLevelMapper.deleteById(existing.getId()); | |
| 78 | + } | |
| 79 | + | |
| 80 | + private void fillLevel(RiderLevel level, AdminRiderLevelSaveDTO dto, Long cityId) { | |
| 81 | + level.setCityId(cityId); | |
| 82 | + level.setLevelId(dto.getLevelId()); | |
| 83 | + level.setName(dto.getName()); | |
| 84 | + level.setTransNums(dto.getTransNums()); | |
| 85 | + level.setRunFeeMode(dto.getRunFeeMode()); | |
| 86 | + level.setRunFixMoney(nvl(dto.getRunFixMoney())); | |
| 87 | + level.setRunRate(nvl(dto.getRunRate())); | |
| 88 | + level.setDistanceBasic(dto.getDistanceBasic() == null ? 0 : dto.getDistanceBasic()); | |
| 89 | + level.setDistanceBasicMoney(nvl(dto.getDistanceBasicMoney())); | |
| 90 | + level.setDistanceMoreMoney(nvl(dto.getDistanceMoreMoney())); | |
| 91 | + level.setDistanceMaxMoney(nvl(dto.getDistanceMaxMoney())); | |
| 92 | + level.setWorkFeeMode(level.getWorkFeeMode() == null ? 1 : level.getWorkFeeMode()); | |
| 93 | + level.setWorkFixMoney(level.getWorkFixMoney() == null ? BigDecimal.ZERO : level.getWorkFixMoney()); | |
| 94 | + level.setWorkRate(level.getWorkRate() == null ? BigDecimal.ZERO : level.getWorkRate()); | |
| 95 | + } | |
| 96 | + | |
| 97 | + private RiderLevel getOwnedLevel(Long id, Long cityId) { | |
| 98 | + RiderLevel level = riderLevelMapper.selectById(id); | |
| 99 | + if (level == null) throw new BizException("等级不存在"); | |
| 100 | + if (!cityId.equals(level.getCityId())) { | |
| 101 | + throw new BizException("等级不属于当前城市"); | |
| 102 | + } | |
| 103 | + return level; | |
| 104 | + } | |
| 105 | + | |
| 106 | + private void ensureLevelIdUnique(Long cityId, Integer levelId, Long excludeId) { | |
| 107 | + LambdaQueryWrapper<RiderLevel> wrapper = new LambdaQueryWrapper<RiderLevel>() | |
| 108 | + .eq(RiderLevel::getCityId, cityId) | |
| 109 | + .eq(RiderLevel::getLevelId, levelId); | |
| 110 | + if (excludeId != null) { | |
| 111 | + wrapper.ne(RiderLevel::getId, excludeId); | |
| 112 | + } | |
| 113 | + Long exists = riderLevelMapper.selectCount(wrapper); | |
| 114 | + if (exists != null && exists > 0) { | |
| 115 | + throw new BizException("同城市下等级编号不能重复"); | |
| 116 | + } | |
| 117 | + } | |
| 118 | + | |
| 119 | + private boolean hasDefault(Long cityId) { | |
| 120 | + Long count = riderLevelMapper.selectCount(new LambdaQueryWrapper<RiderLevel>() | |
| 121 | + .eq(RiderLevel::getCityId, cityId) | |
| 122 | + .eq(RiderLevel::getIsDefault, 1)); | |
| 123 | + return count != null && count > 0; | |
| 124 | + } | |
| 125 | + | |
| 126 | + private void validateCity(Long cityId) { | |
| 127 | + if (cityId == null || cityId < 1) { | |
| 128 | + throw new BizException("城市不能为空"); | |
| 129 | + } | |
| 130 | + } | |
| 131 | + | |
| 132 | + private BigDecimal nvl(BigDecimal value) { | |
| 133 | + return value == null ? BigDecimal.ZERO : value; | |
| 134 | + } | |
| 135 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/AdminRiderServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/AdminRiderServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.dto.AdminRiderAddDTO; | |
| 7 | +import com.diligrp.rider.entity.Orders; | |
| 8 | +import com.diligrp.rider.entity.Rider; | |
| 9 | +import com.diligrp.rider.entity.RiderLevel; | |
| 10 | +import com.diligrp.rider.mapper.OrdersMapper; | |
| 11 | +import com.diligrp.rider.mapper.RiderBalanceMapper; | |
| 12 | +import com.diligrp.rider.mapper.RiderLevelMapper; | |
| 13 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 14 | +import com.diligrp.rider.entity.*; | |
| 15 | +import com.diligrp.rider.mapper.*; | |
| 16 | +import com.diligrp.rider.service.AdminRiderService; | |
| 17 | +import lombok.RequiredArgsConstructor; | |
| 18 | +import org.springframework.stereotype.Service; | |
| 19 | +import org.springframework.transaction.annotation.Transactional; | |
| 20 | +import org.springframework.util.DigestUtils; | |
| 21 | + | |
| 22 | +import java.math.BigDecimal; | |
| 23 | +import java.nio.charset.StandardCharsets; | |
| 24 | +import java.util.HashMap; | |
| 25 | +import java.util.List; | |
| 26 | +import java.util.Map; | |
| 27 | +import java.util.Set; | |
| 28 | +import java.util.stream.Collectors; | |
| 29 | + | |
| 30 | +@Service | |
| 31 | +@RequiredArgsConstructor | |
| 32 | +public class AdminRiderServiceImpl implements AdminRiderService { | |
| 33 | + | |
| 34 | + private final RiderMapper riderMapper; | |
| 35 | + private final RiderLevelMapper riderLevelMapper; | |
| 36 | + private final OrdersMapper ordersMapper; | |
| 37 | + private final RiderBalanceMapper balanceMapper; | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public void add(AdminRiderAddDTO dto, Long cityId) { | |
| 41 | + if (cityId == null || cityId < 1) { | |
| 42 | + throw new BizException("城市不能为空"); | |
| 43 | + } | |
| 44 | + Long exists = riderMapper.selectCount(new LambdaQueryWrapper<Rider>() | |
| 45 | + .eq(Rider::getMobile, dto.getMobile())); | |
| 46 | + if (exists > 0) { | |
| 47 | + throw new BizException("该手机号已存在"); | |
| 48 | + } | |
| 49 | + | |
| 50 | + Rider rider = new Rider(); | |
| 51 | + rider.setUserNickname(dto.getUserNickname()); | |
| 52 | + rider.setMobile(dto.getMobile()); | |
| 53 | + rider.setUserPass(encryptPass(dto.getPassword())); | |
| 54 | + rider.setCityId(cityId); | |
| 55 | + rider.setUserStatus(1); | |
| 56 | + rider.setStatus(1); | |
| 57 | + rider.setType(1); | |
| 58 | + rider.setIsRest(0); | |
| 59 | + rider.setBalance(BigDecimal.ZERO); | |
| 60 | + rider.setUserLogin("phone_" + System.currentTimeMillis()); | |
| 61 | + rider.setCreateTime(System.currentTimeMillis() / 1000); | |
| 62 | + riderMapper.insert(rider); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Override | |
| 66 | + public List<Rider> list(String keyword, Integer userStatus, Long cityId) { | |
| 67 | + LambdaQueryWrapper<Rider> wrapper = new LambdaQueryWrapper<Rider>() | |
| 68 | + .orderByDesc(Rider::getId); | |
| 69 | + if (cityId != null) { | |
| 70 | + wrapper.eq(Rider::getCityId, cityId); | |
| 71 | + } | |
| 72 | + if (userStatus != null) { | |
| 73 | + wrapper.eq(Rider::getUserStatus, userStatus); | |
| 74 | + } | |
| 75 | + if (keyword != null && !keyword.isBlank()) { | |
| 76 | + wrapper.and(w -> w.like(Rider::getUserNickname, keyword) | |
| 77 | + .or() | |
| 78 | + .like(Rider::getMobile, keyword)); | |
| 79 | + } | |
| 80 | + List<Rider> riders = riderMapper.selectList(wrapper); | |
| 81 | + enrichLevelName(riders); | |
| 82 | + return riders; | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + public List<Rider> designateCandidates(Long orderId, Long cityId) { | |
| 87 | + Orders order = ordersMapper.selectById(orderId); | |
| 88 | + if (order == null) throw new BizException("订单不存在"); | |
| 89 | + if (cityId != null && !cityId.equals(order.getCityId())) { | |
| 90 | + throw new BizException("只能查看当前租户订单的骑手"); | |
| 91 | + } | |
| 92 | + | |
| 93 | + LambdaQueryWrapper<Rider> wrapper = new LambdaQueryWrapper<Rider>() | |
| 94 | + .eq(Rider::getCityId, order.getCityId()) | |
| 95 | + .eq(Rider::getUserStatus, 1) | |
| 96 | + .eq(Rider::getStatus, 1) | |
| 97 | + .orderByAsc(Rider::getIsRest) | |
| 98 | + .orderByDesc(Rider::getId); | |
| 99 | + if (order.getIsTrans() != null && order.getIsTrans() == 1 | |
| 100 | + && order.getOldRiderId() != null && order.getOldRiderId() > 0) { | |
| 101 | + wrapper.ne(Rider::getId, order.getOldRiderId()); | |
| 102 | + } | |
| 103 | + | |
| 104 | + List<Rider> riders = riderMapper.selectList(wrapper); | |
| 105 | + enrichLevelName(riders); | |
| 106 | + return riders; | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public void setStatus(Long riderId, int status) { | |
| 111 | + Rider rider = riderMapper.selectById(riderId); | |
| 112 | + if (rider == null) throw new BizException("骑手不存在"); | |
| 113 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | |
| 114 | + .eq(Rider::getId, riderId) | |
| 115 | + .set(Rider::getUserStatus, status)); | |
| 116 | + } | |
| 117 | + | |
| 118 | + @Override | |
| 119 | + public void setLevel(Long riderId, Long levelId, Long cityId) { | |
| 120 | + Rider rider = riderMapper.selectById(riderId); | |
| 121 | + if (rider == null) throw new BizException("骑手不存在"); | |
| 122 | + if (cityId != null && !cityId.equals(rider.getCityId())) { | |
| 123 | + throw new BizException("只能操作当前城市骑手"); | |
| 124 | + } | |
| 125 | + if (levelId != null && levelId > 0) { | |
| 126 | + RiderLevel level = riderLevelMapper.selectById(levelId); | |
| 127 | + if (level == null) throw new BizException("等级不存在"); | |
| 128 | + if (!rider.getCityId().equals(level.getCityId())) { | |
| 129 | + throw new BizException("等级与骑手城市不匹配"); | |
| 130 | + } | |
| 131 | + } | |
| 132 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | |
| 133 | + .eq(Rider::getId, riderId) | |
| 134 | + .set(Rider::getLevelId, levelId != null && levelId > 0 ? levelId : null)); | |
| 135 | + } | |
| 136 | + | |
| 137 | + @Override | |
| 138 | + public void setEnableStatus(Long riderId, int status) { | |
| 139 | + Rider rider = riderMapper.selectById(riderId); | |
| 140 | + if (rider == null) throw new BizException("骑手不存在"); | |
| 141 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | |
| 142 | + .eq(Rider::getId, riderId) | |
| 143 | + .set(Rider::getStatus, status)); | |
| 144 | + } | |
| 145 | + | |
| 146 | + @Override | |
| 147 | + @Transactional | |
| 148 | + public void setType(Long riderId, int type) { | |
| 149 | + Rider rider = riderMapper.selectById(riderId); | |
| 150 | + if (rider == null) throw new BizException("骑手不存在"); | |
| 151 | + if (type == 2) { | |
| 152 | + if (rider.getBalance() != null && rider.getBalance().compareTo(BigDecimal.ZERO) > 0) { | |
| 153 | + throw new BizException("变更为全职前要保证余额为0"); | |
| 154 | + } | |
| 155 | + } | |
| 156 | + LambdaUpdateWrapper<Rider> wrapper = new LambdaUpdateWrapper<Rider>() | |
| 157 | + .eq(Rider::getId, riderId) | |
| 158 | + .set(Rider::getType, type); | |
| 159 | + if (type == 1) { | |
| 160 | + wrapper.set(Rider::getBalance, BigDecimal.ZERO); | |
| 161 | + } | |
| 162 | + riderMapper.update(null, wrapper); | |
| 163 | + } | |
| 164 | + | |
| 165 | + @Override | |
| 166 | + @Transactional | |
| 167 | + public void designate(Long orderId, Long riderId) { | |
| 168 | + Orders order = ordersMapper.selectById(orderId); | |
| 169 | + if (order == null) throw new BizException("订单不存在"); | |
| 170 | + if (order.getStatus() == 1) throw new BizException("订单未支付,无法指派"); | |
| 171 | + if (order.getStatus() == 10) throw new BizException("订单已取消,无法指派"); | |
| 172 | + if (order.getStatus() != 2) throw new BizException("订单已服务中,无法指派"); | |
| 173 | + if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) { | |
| 174 | + throw new BizException("此订单为该骑手转单订单,无法指派给该骑手"); | |
| 175 | + } | |
| 176 | + | |
| 177 | + long now = System.currentTimeMillis() / 1000; | |
| 178 | + LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>() | |
| 179 | + .eq(Orders::getId, orderId) | |
| 180 | + .eq(Orders::getStatus, 2) | |
| 181 | + .eq(Orders::getRiderId, 0) | |
| 182 | + .set(Orders::getRiderId, riderId) | |
| 183 | + .set(Orders::getStatus, 3) | |
| 184 | + .set(Orders::getGrapTime, now); | |
| 185 | + if (order.getOldRiderId() == null || order.getOldRiderId() == 0) { | |
| 186 | + wrapper.set(Orders::getOldRiderId, riderId); | |
| 187 | + } | |
| 188 | + int updated = ordersMapper.update(null, wrapper); | |
| 189 | + if (updated == 0) throw new BizException("指派失败,请重试"); | |
| 190 | + } | |
| 191 | + | |
| 192 | + @Override | |
| 193 | + @Transactional | |
| 194 | + public void setTrans(Long orderId, int trans) { | |
| 195 | + Orders order = ordersMapper.selectById(orderId); | |
| 196 | + if (order == null) throw new BizException("订单不存在"); | |
| 197 | + if (order.getStatus() != 4) throw new BizException("订单状态错误,无法操作"); | |
| 198 | + if (order.getIsTrans() != 2) throw new BizException("订单未申请转单,无法操作"); | |
| 199 | + | |
| 200 | + LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>() | |
| 201 | + .eq(Orders::getId, orderId) | |
| 202 | + .eq(Orders::getIsTrans, 2) | |
| 203 | + .set(Orders::getIsTrans, trans); | |
| 204 | + | |
| 205 | + if (trans == 1) { | |
| 206 | + wrapper.set(Orders::getStatus, 2) | |
| 207 | + .set(Orders::getGrapTime, 0L) | |
| 208 | + .set(Orders::getPickTime, 0L) | |
| 209 | + .set(Orders::getRiderId, 0L) | |
| 210 | + .set(Orders::getIsIncome, 0) | |
| 211 | + .set(Orders::getRiderIncome, BigDecimal.ZERO) | |
| 212 | + .set(Orders::getSubstationIncome, BigDecimal.ZERO); | |
| 213 | + } | |
| 214 | + int updated = ordersMapper.update(null, wrapper); | |
| 215 | + if (updated == 0) throw new BizException("操作失败,请重试"); | |
| 216 | + } | |
| 217 | + | |
| 218 | + private String encryptPass(String pass) { | |
| 219 | + return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8)); | |
| 220 | + } | |
| 221 | + | |
| 222 | + private void enrichLevelName(List<Rider> riders) { | |
| 223 | + if (riders == null || riders.isEmpty()) return; | |
| 224 | + | |
| 225 | + Set<Long> cityIds = riders.stream() | |
| 226 | + .map(Rider::getCityId) | |
| 227 | + .filter(id -> id != null && id > 0) | |
| 228 | + .collect(Collectors.toSet()); | |
| 229 | + if (cityIds.isEmpty()) return; | |
| 230 | + | |
| 231 | + List<RiderLevel> levels = riderLevelMapper.selectList(new LambdaQueryWrapper<RiderLevel>() | |
| 232 | + .in(RiderLevel::getCityId, cityIds)); | |
| 233 | + Map<Long, String> levelNameMap = new HashMap<>(); | |
| 234 | + Map<Long, String> defaultLevelNameMap = new HashMap<>(); | |
| 235 | + for (RiderLevel level : levels) { | |
| 236 | + levelNameMap.put(level.getId(), level.getName()); | |
| 237 | + if (level.getIsDefault() != null && level.getIsDefault() == 1) { | |
| 238 | + defaultLevelNameMap.put(level.getCityId(), level.getName()); | |
| 239 | + } | |
| 240 | + } | |
| 241 | + | |
| 242 | + for (Rider rider : riders) { | |
| 243 | + if (rider.getLevelId() != null && rider.getLevelId() > 0) { | |
| 244 | + rider.setLevelName(levelNameMap.getOrDefault(rider.getLevelId(), "未知等级")); | |
| 245 | + } else { | |
| 246 | + rider.setLevelName(defaultLevelNameMap.getOrDefault(rider.getCityId(), "默认等级")); | |
| 247 | + } | |
| 248 | + } | |
| 249 | + } | |
| 250 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/CityServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/CityServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 7 | +import com.diligrp.rider.dto.DeliveryPricingRuleDTO; | |
| 8 | +import com.diligrp.rider.entity.City; | |
| 9 | +import com.diligrp.rider.entity.DeliveryFeePlan; | |
| 10 | +import com.diligrp.rider.entity.DeliveryFeePlanDimension; | |
| 11 | +import com.diligrp.rider.entity.DeliveryFeePlanDistanceStep; | |
| 12 | +import com.diligrp.rider.entity.DeliveryFeePlanPieceRule; | |
| 13 | +import com.diligrp.rider.entity.DeliveryFeePlanTimeRule; | |
| 14 | +import com.diligrp.rider.mapper.CityMapper; | |
| 15 | +import com.diligrp.rider.mapper.DeliveryFeePlanDimensionMapper; | |
| 16 | +import com.diligrp.rider.mapper.DeliveryFeePlanDistanceStepMapper; | |
| 17 | +import com.diligrp.rider.mapper.DeliveryFeePlanMapper; | |
| 18 | +import com.diligrp.rider.mapper.DeliveryFeePlanPieceRuleMapper; | |
| 19 | +import com.diligrp.rider.mapper.DeliveryFeePlanTimeRuleMapper; | |
| 20 | +import com.diligrp.rider.service.CityService; | |
| 21 | +import com.diligrp.rider.vo.CityVO; | |
| 22 | +import lombok.RequiredArgsConstructor; | |
| 23 | +import org.springframework.stereotype.Service; | |
| 24 | + | |
| 25 | +import java.math.BigDecimal; | |
| 26 | +import java.util.Collections; | |
| 27 | +import java.util.HashMap; | |
| 28 | +import java.util.List; | |
| 29 | +import java.util.Map; | |
| 30 | +import java.util.stream.Collectors; | |
| 31 | + | |
| 32 | +@Service | |
| 33 | +@RequiredArgsConstructor | |
| 34 | +public class CityServiceImpl implements CityService { | |
| 35 | + | |
| 36 | + private final CityMapper cityMapper; | |
| 37 | + private final DeliveryFeePlanMapper deliveryFeePlanMapper; | |
| 38 | + private final DeliveryFeePlanDimensionMapper deliveryFeePlanDimensionMapper; | |
| 39 | + private final DeliveryFeePlanDistanceStepMapper deliveryFeePlanDistanceStepMapper; | |
| 40 | + private final DeliveryFeePlanPieceRuleMapper deliveryFeePlanPieceRuleMapper; | |
| 41 | + private final DeliveryFeePlanTimeRuleMapper deliveryFeePlanTimeRuleMapper; | |
| 42 | + @Override | |
| 43 | + public List<CityVO> getTree() { | |
| 44 | + List<City> all = cityMapper.selectList(new LambdaQueryWrapper<City>().orderByAsc(City::getListOrder)); | |
| 45 | + return all.stream().map(this::toVO).collect(Collectors.toList()); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public List<CityVO> getOpenList() { | |
| 50 | + return cityMapper.selectList(new LambdaQueryWrapper<City>() | |
| 51 | + .eq(City::getStatus, 1) | |
| 52 | + .orderByAsc(City::getListOrder)) | |
| 53 | + .stream().map(this::toVO).collect(Collectors.toList()); | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void add(City city) { | |
| 58 | + if (city.getAreaCode() != null && !city.getAreaCode().isBlank()) { | |
| 59 | + Long exists = cityMapper.selectCount(new LambdaQueryWrapper<City>() | |
| 60 | + .eq(City::getAreaCode, city.getAreaCode())); | |
| 61 | + if (exists > 0) { | |
| 62 | + throw new BizException("地区编号已存在"); | |
| 63 | + } | |
| 64 | + } | |
| 65 | + cityMapper.insert(city); | |
| 66 | + initializeDefaultPlan(city.getId()); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public void edit(City city) { | |
| 71 | + City existing = cityMapper.selectById(city.getId()); | |
| 72 | + if (existing == null) { | |
| 73 | + throw new BizException("城市不存在"); | |
| 74 | + } | |
| 75 | + cityMapper.updateById(city); | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public void setStatus(Long cityId, int status) { | |
| 80 | + cityMapper.update(null, new LambdaUpdateWrapper<City>() | |
| 81 | + .eq(City::getId, cityId) | |
| 82 | + .set(City::getStatus, status)); | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + public DeliveryPricingConfigDTO getConfig(Long cityId) { | |
| 87 | + City city = cityMapper.selectById(cityId); | |
| 88 | + if (city == null) { | |
| 89 | + throw new BizException("城市不存在"); | |
| 90 | + } | |
| 91 | + | |
| 92 | + DeliveryFeePlan plan = deliveryFeePlanMapper.selectOne(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 93 | + .eq(DeliveryFeePlan::getCityId, cityId) | |
| 94 | + .eq(DeliveryFeePlan::getIsDefault, 1) | |
| 95 | + .last("LIMIT 1")); | |
| 96 | + if (plan == null) { | |
| 97 | + plan = deliveryFeePlanMapper.selectOne(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 98 | + .eq(DeliveryFeePlan::getCityId, cityId) | |
| 99 | + .orderByAsc(DeliveryFeePlan::getListOrder) | |
| 100 | + .orderByAsc(DeliveryFeePlan::getId) | |
| 101 | + .last("LIMIT 1")); | |
| 102 | + } | |
| 103 | + if (plan == null) { | |
| 104 | + return createDefaultConfig(); | |
| 105 | + } | |
| 106 | + | |
| 107 | + List<DeliveryFeePlanDimension> dimensions = deliveryFeePlanDimensionMapper.selectList( | |
| 108 | + new LambdaQueryWrapper<DeliveryFeePlanDimension>() | |
| 109 | + .eq(DeliveryFeePlanDimension::getPlanId, plan.getId())); | |
| 110 | + Map<String, DeliveryFeePlanDimension> dimensionMap = new HashMap<>(); | |
| 111 | + for (DeliveryFeePlanDimension item : dimensions) { | |
| 112 | + dimensionMap.put(item.getDimensionType(), item); | |
| 113 | + } | |
| 114 | + | |
| 115 | + List<DeliveryFeePlanDistanceStep> distanceSteps = deliveryFeePlanDistanceStepMapper.selectList( | |
| 116 | + new LambdaQueryWrapper<DeliveryFeePlanDistanceStep>() | |
| 117 | + .eq(DeliveryFeePlanDistanceStep::getPlanId, plan.getId()) | |
| 118 | + .orderByAsc(DeliveryFeePlanDistanceStep::getListOrder) | |
| 119 | + .orderByAsc(DeliveryFeePlanDistanceStep::getId)); | |
| 120 | + List<DeliveryFeePlanPieceRule> pieceRules = deliveryFeePlanPieceRuleMapper.selectList( | |
| 121 | + new LambdaQueryWrapper<DeliveryFeePlanPieceRule>() | |
| 122 | + .eq(DeliveryFeePlanPieceRule::getPlanId, plan.getId()) | |
| 123 | + .orderByAsc(DeliveryFeePlanPieceRule::getListOrder) | |
| 124 | + .orderByAsc(DeliveryFeePlanPieceRule::getId)); | |
| 125 | + List<DeliveryFeePlanTimeRule> timeRules = deliveryFeePlanTimeRuleMapper.selectList( | |
| 126 | + new LambdaQueryWrapper<DeliveryFeePlanTimeRule>() | |
| 127 | + .eq(DeliveryFeePlanTimeRule::getPlanId, plan.getId()) | |
| 128 | + .orderByAsc(DeliveryFeePlanTimeRule::getListOrder) | |
| 129 | + .orderByAsc(DeliveryFeePlanTimeRule::getId)); | |
| 130 | + | |
| 131 | + DeliveryPricingRuleDTO type6 = new DeliveryPricingRuleDTO(); | |
| 132 | + type6.setMinFee(nvl(plan.getMinFee())); | |
| 133 | + type6.setFeeMode(2); | |
| 134 | + | |
| 135 | + DeliveryFeePlanDimension base = dimensionMap.get("base"); | |
| 136 | + if (base != null) { | |
| 137 | + type6.setBaseSwitch(on(base.getEnabled())); | |
| 138 | + type6.setBaseFee(nvl(base.getBaseFee())); | |
| 139 | + } | |
| 140 | + | |
| 141 | + DeliveryFeePlanDimension distance = dimensionMap.get("distance"); | |
| 142 | + if (distance != null) { | |
| 143 | + type6.setDistanceSwitch(on(distance.getEnabled())); | |
| 144 | + type6.setDistanceBasic(nvl(distance.getStartDistance())); | |
| 145 | + type6.setDistanceBasicMoney(nvl(distance.getStartFee())); | |
| 146 | + type6.setDistanceType(2); | |
| 147 | + type6.setDistanceSteps(distanceSteps.stream().map(step -> { | |
| 148 | + DeliveryPricingRuleDTO.DistanceStepDTO dto = new DeliveryPricingRuleDTO.DistanceStepDTO(); | |
| 149 | + dto.setEndDistance(nvl(step.getEndDistance())); | |
| 150 | + dto.setUnitDistance(nvl(step.getUnitDistance())); | |
| 151 | + dto.setUnitFee(nvl(step.getUnitFee())); | |
| 152 | + dto.setListOrder(step.getListOrder()); | |
| 153 | + return dto; | |
| 154 | + }).collect(Collectors.toList())); | |
| 155 | + if (!distanceSteps.isEmpty()) { | |
| 156 | + type6.setDistanceMoreMoney(nvl(distanceSteps.get(distanceSteps.size() - 1).getUnitFee())); | |
| 157 | + } | |
| 158 | + } | |
| 159 | + | |
| 160 | + DeliveryFeePlanDimension weight = dimensionMap.get("weight"); | |
| 161 | + if (weight != null) { | |
| 162 | + type6.setWeightSwitch(on(weight.getEnabled())); | |
| 163 | + type6.setWeightFirst(nvl(weight.getFirstWeight())); | |
| 164 | + type6.setWeightFirstFee(nvl(weight.getFirstFee())); | |
| 165 | + type6.setWeightUnitFee(nvl(weight.getUnitWeightFee())); | |
| 166 | + type6.setWeightCapFee(nvl(weight.getCapFee())); | |
| 167 | + type6.setWeightBasic(nvl(weight.getFirstWeight())); | |
| 168 | + type6.setWeightBasicMoney(nvl(weight.getFirstFee())); | |
| 169 | + type6.setWeightMoreMoney(nvl(weight.getUnitWeightFee())); | |
| 170 | + } | |
| 171 | + | |
| 172 | + DeliveryFeePlanDimension piece = dimensionMap.get("piece"); | |
| 173 | + if (piece != null) { | |
| 174 | + type6.setPieceSwitch(on(piece.getEnabled())); | |
| 175 | + type6.setPieceRules(pieceRules.stream().map(rule -> { | |
| 176 | + DeliveryPricingRuleDTO.PieceRuleDTO dto = new DeliveryPricingRuleDTO.PieceRuleDTO(); | |
| 177 | + dto.setStartPiece(rule.getStartPiece()); | |
| 178 | + dto.setEndPiece(rule.getEndPiece()); | |
| 179 | + dto.setFee(nvl(rule.getFee())); | |
| 180 | + dto.setListOrder(rule.getListOrder()); | |
| 181 | + return dto; | |
| 182 | + }).collect(Collectors.toList())); | |
| 183 | + } | |
| 184 | + | |
| 185 | + DeliveryFeePlanDimension time = dimensionMap.get("time"); | |
| 186 | + if (time != null) { | |
| 187 | + type6.setTimes(timeRules.stream().map(rule -> { | |
| 188 | + DeliveryPricingRuleDTO.TimePeriodDTO dto = new DeliveryPricingRuleDTO.TimePeriodDTO(); | |
| 189 | + dto.setStart(rule.getStartMinute()); | |
| 190 | + dto.setEnd(rule.getEndMinute()); | |
| 191 | + dto.setMoney(nvl(rule.getFee())); | |
| 192 | + dto.setIsOpen(on(rule.getEnabled())); | |
| 193 | + return dto; | |
| 194 | + }).collect(Collectors.toList())); | |
| 195 | + } | |
| 196 | + | |
| 197 | + DeliveryPricingConfigDTO config = createDefaultConfig(); | |
| 198 | + config.setType6(type6); | |
| 199 | + config.setDistanceBasic(nvl(plan.getDistanceBasic())); | |
| 200 | + config.setDistanceBasicTime(plan.getDistanceBasicTime() != null ? plan.getDistanceBasicTime() : 30); | |
| 201 | + config.setDistanceMoreTime(plan.getDistanceMoreTime() != null ? plan.getDistanceMoreTime() : 10); | |
| 202 | + config.setRiderDistance(nvl(plan.getRiderDistance())); | |
| 203 | + config.setRiderTime(plan.getRiderTime()); | |
| 204 | + return config; | |
| 205 | + } | |
| 206 | + | |
| 207 | + @Override | |
| 208 | + public City getById(Long cityId) { | |
| 209 | + return cityMapper.selectById(cityId); | |
| 210 | + } | |
| 211 | + | |
| 212 | + private CityVO toVO(City c) { | |
| 213 | + CityVO vo = new CityVO(); | |
| 214 | + vo.setId(c.getId()); | |
| 215 | + vo.setPid(c.getPid()); | |
| 216 | + vo.setName(c.getName()); | |
| 217 | + vo.setAreaCode(c.getAreaCode()); | |
| 218 | + vo.setStatus(c.getStatus()); | |
| 219 | + vo.setStatusName(c.getStatus() != null && c.getStatus() == 1 ? "已开通" : "未开通"); | |
| 220 | + vo.setRate(c.getRate()); | |
| 221 | + vo.setListOrder(c.getListOrder()); | |
| 222 | + return vo; | |
| 223 | + } | |
| 224 | + | |
| 225 | + private DeliveryPricingConfigDTO createDefaultConfig() { | |
| 226 | + DeliveryPricingConfigDTO config = new DeliveryPricingConfigDTO(); | |
| 227 | + config.setType(Collections.emptyList()); | |
| 228 | + config.setType6(new DeliveryPricingRuleDTO()); | |
| 229 | + config.setDistanceBasic(BigDecimal.valueOf(3)); | |
| 230 | + config.setDistanceBasicTime(30); | |
| 231 | + config.setDistanceMoreTime(10); | |
| 232 | + config.setRiderDistance(BigDecimal.valueOf(3)); | |
| 233 | + return config; | |
| 234 | + } | |
| 235 | + | |
| 236 | + private void initializeDefaultPlan(Long cityId) { | |
| 237 | + long now = System.currentTimeMillis() / 1000; | |
| 238 | + DeliveryFeePlan plan = new DeliveryFeePlan(); | |
| 239 | + plan.setCityId(cityId); | |
| 240 | + plan.setName("默认方案"); | |
| 241 | + plan.setIsDefault(1); | |
| 242 | + plan.setStatus(1); | |
| 243 | + plan.setListOrder(0); | |
| 244 | + plan.setRemark("系统初始化"); | |
| 245 | + plan.setMinFee(BigDecimal.ZERO); | |
| 246 | + plan.setDistanceBasic(BigDecimal.valueOf(3)); | |
| 247 | + plan.setDistanceBasicTime(30); | |
| 248 | + plan.setDistanceMoreTime(10); | |
| 249 | + plan.setRiderDistance(BigDecimal.valueOf(3)); | |
| 250 | + plan.setRiderTime(0); | |
| 251 | + plan.setCreateTime(now); | |
| 252 | + plan.setUpdateTime(now); | |
| 253 | + deliveryFeePlanMapper.insert(plan); | |
| 254 | + | |
| 255 | + upsertDimension(plan.getId(), "base", 0, BigDecimal.ZERO, null, null, null, null, null, null, now); | |
| 256 | + upsertDimension(plan.getId(), "distance", 1, null, BigDecimal.valueOf(3), BigDecimal.valueOf(4), null, null, null, null, now); | |
| 257 | + upsertDimension(plan.getId(), "weight", 1, null, null, null, BigDecimal.valueOf(5), BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.valueOf(30), now); | |
| 258 | + upsertDimension(plan.getId(), "piece", 0, null, null, null, null, null, null, null, now); | |
| 259 | + upsertDimension(plan.getId(), "time", 0, null, null, null, null, null, null, null, now); | |
| 260 | + } | |
| 261 | + | |
| 262 | + private void upsertDimension(Long planId, String type, Integer enabled, BigDecimal baseFee, | |
| 263 | + BigDecimal startDistance, BigDecimal startFee, BigDecimal firstWeight, | |
| 264 | + BigDecimal firstFee, BigDecimal unitWeightFee, BigDecimal capFee, long now) { | |
| 265 | + DeliveryFeePlanDimension dimension = new DeliveryFeePlanDimension(); | |
| 266 | + dimension.setPlanId(planId); | |
| 267 | + dimension.setDimensionType(type); | |
| 268 | + dimension.setEnabled(on(enabled)); | |
| 269 | + dimension.setBaseFee(nvl(baseFee)); | |
| 270 | + dimension.setStartDistance(nvl(startDistance)); | |
| 271 | + dimension.setStartFee(nvl(startFee)); | |
| 272 | + dimension.setFirstWeight(nvl(firstWeight)); | |
| 273 | + dimension.setFirstFee(nvl(firstFee)); | |
| 274 | + dimension.setUnitWeightFee(nvl(unitWeightFee)); | |
| 275 | + dimension.setCapFee(nvl(capFee)); | |
| 276 | + dimension.setCreateTime(now); | |
| 277 | + dimension.setUpdateTime(now); | |
| 278 | + deliveryFeePlanDimensionMapper.insert(dimension); | |
| 279 | + } | |
| 280 | + | |
| 281 | + private BigDecimal nvl(BigDecimal value) { | |
| 282 | + return value != null ? value : BigDecimal.ZERO; | |
| 283 | + } | |
| 284 | + | |
| 285 | + private Integer on(Integer value) { | |
| 286 | + return value != null && value == 1 ? 1 : 0; | |
| 287 | + } | |
| 288 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/DeliveryFeePlanServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/DeliveryFeePlanServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.diligrp.rider.common.exception.BizException; | |
| 5 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 6 | +import com.diligrp.rider.dto.DeliveryPricingRuleDTO; | |
| 7 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 8 | +import com.diligrp.rider.dto.DeliveryFeePlanPreviewDTO; | |
| 9 | +import com.diligrp.rider.dto.DeliveryFeePlanSaveDTO; | |
| 10 | +import com.diligrp.rider.entity.City; | |
| 11 | +import com.diligrp.rider.entity.DeliveryFeePlan; | |
| 12 | +import com.diligrp.rider.entity.DeliveryFeePlanDimension; | |
| 13 | +import com.diligrp.rider.entity.DeliveryFeePlanDistanceStep; | |
| 14 | +import com.diligrp.rider.entity.DeliveryFeePlanPieceRule; | |
| 15 | +import com.diligrp.rider.entity.DeliveryFeePlanTimeRule; | |
| 16 | +import com.diligrp.rider.mapper.CityMapper; | |
| 17 | +import com.diligrp.rider.mapper.DeliveryFeePlanDimensionMapper; | |
| 18 | +import com.diligrp.rider.mapper.DeliveryFeePlanDistanceStepMapper; | |
| 19 | +import com.diligrp.rider.mapper.DeliveryFeePlanMapper; | |
| 20 | +import com.diligrp.rider.mapper.DeliveryFeePlanPieceRuleMapper; | |
| 21 | +import com.diligrp.rider.mapper.DeliveryFeePlanTimeRuleMapper; | |
| 22 | +import com.diligrp.rider.service.DeliveryFeePlanService; | |
| 23 | +import com.diligrp.rider.service.DeliveryFeeService; | |
| 24 | +import com.diligrp.rider.vo.DeliveryFeePlanDetailVO; | |
| 25 | +import com.diligrp.rider.vo.DeliveryFeePlanVO; | |
| 26 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 27 | +import lombok.RequiredArgsConstructor; | |
| 28 | +import lombok.extern.slf4j.Slf4j; | |
| 29 | +import org.springframework.stereotype.Service; | |
| 30 | +import org.springframework.transaction.annotation.Transactional; | |
| 31 | + | |
| 32 | +import java.math.BigDecimal; | |
| 33 | +import java.util.ArrayList; | |
| 34 | +import java.util.Arrays; | |
| 35 | +import java.util.HashMap; | |
| 36 | +import java.util.List; | |
| 37 | +import java.util.Map; | |
| 38 | +import java.util.stream.Collectors; | |
| 39 | + | |
| 40 | +@Slf4j | |
| 41 | +@Service | |
| 42 | +@RequiredArgsConstructor | |
| 43 | +public class DeliveryFeePlanServiceImpl implements DeliveryFeePlanService { | |
| 44 | + | |
| 45 | + private final CityMapper cityMapper; | |
| 46 | + private final DeliveryFeeService deliveryFeeService; | |
| 47 | + private final DeliveryFeePlanMapper deliveryFeePlanMapper; | |
| 48 | + private final DeliveryFeePlanDimensionMapper deliveryFeePlanDimensionMapper; | |
| 49 | + private final DeliveryFeePlanDistanceStepMapper deliveryFeePlanDistanceStepMapper; | |
| 50 | + private final DeliveryFeePlanPieceRuleMapper deliveryFeePlanPieceRuleMapper; | |
| 51 | + private final DeliveryFeePlanTimeRuleMapper deliveryFeePlanTimeRuleMapper; | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public List<DeliveryFeePlanVO> listPlans(Long cityId) { | |
| 55 | + ensurePlanTablesReady(); | |
| 56 | + requireCity(cityId); | |
| 57 | + return deliveryFeePlanMapper.selectList(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 58 | + .eq(DeliveryFeePlan::getCityId, cityId) | |
| 59 | + .orderByDesc(DeliveryFeePlan::getIsDefault) | |
| 60 | + .orderByAsc(DeliveryFeePlan::getListOrder) | |
| 61 | + .orderByAsc(DeliveryFeePlan::getId)) | |
| 62 | + .stream() | |
| 63 | + .map(this::toPlanVO) | |
| 64 | + .collect(Collectors.toList()); | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public DeliveryFeePlanDetailVO getPlanDetail(Long cityId, Long planId) { | |
| 69 | + ensurePlanTablesReady(); | |
| 70 | + requireCity(cityId); | |
| 71 | + DeliveryFeePlan plan = requirePlan(cityId, planId); | |
| 72 | + DeliveryFeePlanDetailVO vo = new DeliveryFeePlanDetailVO(); | |
| 73 | + copyPlanMeta(plan, vo); | |
| 74 | + vo.setConfig(buildPlanConfig(plan)); | |
| 75 | + return vo; | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + @Transactional | |
| 80 | + public Long createPlan(Long cityId, DeliveryFeePlanSaveDTO dto) { | |
| 81 | + ensurePlanTablesReady(); | |
| 82 | + requireCity(cityId); | |
| 83 | + long now = now(); | |
| 84 | + | |
| 85 | + DeliveryFeePlan plan = new DeliveryFeePlan(); | |
| 86 | + plan.setCityId(cityId); | |
| 87 | + plan.setName(safeName(dto != null ? dto.getName() : null, "新方案")); | |
| 88 | + plan.setIsDefault(0); | |
| 89 | + plan.setStatus(dto != null && dto.getStatus() != null ? dto.getStatus() : 1); | |
| 90 | + plan.setListOrder(dto != null && dto.getListOrder() != null ? dto.getListOrder() : nextListOrder(cityId)); | |
| 91 | + plan.setRemark(dto != null && dto.getRemark() != null ? dto.getRemark() : ""); | |
| 92 | + plan.setMinFee(BigDecimal.ZERO); | |
| 93 | + plan.setCreateTime(now); | |
| 94 | + plan.setUpdateTime(now); | |
| 95 | + deliveryFeePlanMapper.insert(plan); | |
| 96 | + | |
| 97 | + DeliveryPricingConfigDTO config = normalizeConfig(dto != null ? dto.getConfig() : null); | |
| 98 | + savePlanConfig(plan, config, now); | |
| 99 | + return plan.getId(); | |
| 100 | + } | |
| 101 | + | |
| 102 | + @Override | |
| 103 | + @Transactional | |
| 104 | + public Long initializeDefaultPlan(Long cityId) { | |
| 105 | + ensurePlanTablesReady(); | |
| 106 | + requireCity(cityId); | |
| 107 | + DeliveryFeePlan existed = deliveryFeePlanMapper.selectOne(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 108 | + .eq(DeliveryFeePlan::getCityId, cityId) | |
| 109 | + .eq(DeliveryFeePlan::getIsDefault, 1) | |
| 110 | + .last("LIMIT 1")); | |
| 111 | + if (existed != null) { | |
| 112 | + return existed.getId(); | |
| 113 | + } | |
| 114 | + long now = now(); | |
| 115 | + DeliveryFeePlan plan = new DeliveryFeePlan(); | |
| 116 | + plan.setCityId(cityId); | |
| 117 | + plan.setName("默认方案"); | |
| 118 | + plan.setIsDefault(1); | |
| 119 | + plan.setStatus(1); | |
| 120 | + plan.setListOrder(0); | |
| 121 | + plan.setRemark("系统初始化"); | |
| 122 | + plan.setMinFee(BigDecimal.ZERO); | |
| 123 | + plan.setCreateTime(now); | |
| 124 | + plan.setUpdateTime(now); | |
| 125 | + deliveryFeePlanMapper.insert(plan); | |
| 126 | + savePlanConfig(plan, createDefaultConfig(), now); | |
| 127 | + return plan.getId(); | |
| 128 | + } | |
| 129 | + | |
| 130 | + @Override | |
| 131 | + @Transactional | |
| 132 | + public void updatePlan(Long cityId, Long planId, DeliveryFeePlanSaveDTO dto) { | |
| 133 | + ensurePlanTablesReady(); | |
| 134 | + requireCity(cityId); | |
| 135 | + DeliveryFeePlan plan = requirePlan(cityId, planId); | |
| 136 | + long now = now(); | |
| 137 | + | |
| 138 | + plan.setName(safeName(dto != null ? dto.getName() : null, plan.getName())); | |
| 139 | + plan.setStatus(dto != null && dto.getStatus() != null ? dto.getStatus() : plan.getStatus()); | |
| 140 | + plan.setListOrder(dto != null && dto.getListOrder() != null ? dto.getListOrder() : plan.getListOrder()); | |
| 141 | + plan.setRemark(dto != null && dto.getRemark() != null ? dto.getRemark() : ""); | |
| 142 | + plan.setUpdateTime(now); | |
| 143 | + deliveryFeePlanMapper.updateById(plan); | |
| 144 | + | |
| 145 | + DeliveryPricingConfigDTO config = normalizeConfig(dto != null ? dto.getConfig() : null); | |
| 146 | + savePlanConfig(plan, config, now); | |
| 147 | + } | |
| 148 | + | |
| 149 | + @Override | |
| 150 | + @Transactional | |
| 151 | + public Long copyPlan(Long cityId, Long planId) { | |
| 152 | + ensurePlanTablesReady(); | |
| 153 | + requireCity(cityId); | |
| 154 | + DeliveryFeePlan source = requirePlan(cityId, planId); | |
| 155 | + DeliveryPricingConfigDTO sourceConfig = buildPlanConfig(source); | |
| 156 | + long now = now(); | |
| 157 | + | |
| 158 | + DeliveryFeePlan target = new DeliveryFeePlan(); | |
| 159 | + target.setCityId(cityId); | |
| 160 | + target.setName(safeName(source.getName(), "方案") + "副本"); | |
| 161 | + target.setIsDefault(0); | |
| 162 | + target.setStatus(source.getStatus() != null ? source.getStatus() : 1); | |
| 163 | + target.setListOrder(nextListOrder(cityId)); | |
| 164 | + target.setRemark(source.getRemark() != null ? source.getRemark() : ""); | |
| 165 | + target.setMinFee(source.getMinFee() != null ? source.getMinFee() : BigDecimal.ZERO); | |
| 166 | + target.setCreateTime(now); | |
| 167 | + target.setUpdateTime(now); | |
| 168 | + deliveryFeePlanMapper.insert(target); | |
| 169 | + | |
| 170 | + savePlanConfig(target, sourceConfig, now); | |
| 171 | + return target.getId(); | |
| 172 | + } | |
| 173 | + | |
| 174 | + @Override | |
| 175 | + @Transactional | |
| 176 | + public void deletePlan(Long cityId, Long planId) { | |
| 177 | + ensurePlanTablesReady(); | |
| 178 | + requireCity(cityId); | |
| 179 | + DeliveryFeePlan plan = requirePlan(cityId, planId); | |
| 180 | + if (plan.getIsDefault() != null && plan.getIsDefault() == 1) { | |
| 181 | + throw new BizException("默认方案不允许删除"); | |
| 182 | + } | |
| 183 | + deletePlanChildren(planId); | |
| 184 | + deliveryFeePlanMapper.deleteById(planId); | |
| 185 | + } | |
| 186 | + | |
| 187 | + @Override | |
| 188 | + @Transactional | |
| 189 | + public void setDefaultPlan(Long cityId, Long planId) { | |
| 190 | + ensurePlanTablesReady(); | |
| 191 | + requireCity(cityId); | |
| 192 | + DeliveryFeePlan target = requirePlan(cityId, planId); | |
| 193 | + if (target.getStatus() == null || target.getStatus() != 1) { | |
| 194 | + throw new BizException("禁用方案不能设为默认"); | |
| 195 | + } | |
| 196 | + long now = now(); | |
| 197 | + List<DeliveryFeePlan> plans = deliveryFeePlanMapper.selectList(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 198 | + .eq(DeliveryFeePlan::getCityId, cityId)); | |
| 199 | + for (DeliveryFeePlan item : plans) { | |
| 200 | + item.setIsDefault(item.getId().equals(planId) ? 1 : 0); | |
| 201 | + item.setUpdateTime(now); | |
| 202 | + deliveryFeePlanMapper.updateById(item); | |
| 203 | + } | |
| 204 | + } | |
| 205 | + | |
| 206 | + @Override | |
| 207 | + public DeliveryFeeResultVO preview(Long cityId, DeliveryFeePlanPreviewDTO dto) { | |
| 208 | + ensurePlanTablesReady(); | |
| 209 | + requireCity(cityId); | |
| 210 | + DeliveryFeeCalcDTO calc = dto != null ? dto.getCalc() : null; | |
| 211 | + if (calc == null) { | |
| 212 | + throw new BizException("请填写试算参数"); | |
| 213 | + } | |
| 214 | + calc.setCityId(cityId); | |
| 215 | + calc.setOrderType(6); | |
| 216 | + DeliveryPricingConfigDTO config = normalizeConfig(dto != null ? dto.getConfig() : null); | |
| 217 | + return deliveryFeeService.calcFeeByConfig(config, calc); | |
| 218 | + } | |
| 219 | + | |
| 220 | + private void ensurePlanTablesReady() { | |
| 221 | + try { | |
| 222 | + deliveryFeePlanMapper.selectCount(new LambdaQueryWrapper<DeliveryFeePlan>().last("LIMIT 1")); | |
| 223 | + } catch (Exception e) { | |
| 224 | + log.warn("配送费方案表不可用", e); | |
| 225 | + throw new BizException("配送费方案表未初始化,请先执行数据库建表脚本"); | |
| 226 | + } | |
| 227 | + } | |
| 228 | + | |
| 229 | + private City requireCity(Long cityId) { | |
| 230 | + City city = cityMapper.selectById(cityId); | |
| 231 | + if (city == null) { | |
| 232 | + throw new BizException("租户不存在"); | |
| 233 | + } | |
| 234 | + return city; | |
| 235 | + } | |
| 236 | + | |
| 237 | + private DeliveryFeePlan requirePlan(Long cityId, Long planId) { | |
| 238 | + DeliveryFeePlan plan = deliveryFeePlanMapper.selectOne(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 239 | + .eq(DeliveryFeePlan::getId, planId) | |
| 240 | + .eq(DeliveryFeePlan::getCityId, cityId) | |
| 241 | + .last("LIMIT 1")); | |
| 242 | + if (plan == null) { | |
| 243 | + throw new BizException("配送费方案不存在"); | |
| 244 | + } | |
| 245 | + return plan; | |
| 246 | + } | |
| 247 | + | |
| 248 | + private int nextListOrder(Long cityId) { | |
| 249 | + List<DeliveryFeePlan> plans = deliveryFeePlanMapper.selectList(new LambdaQueryWrapper<DeliveryFeePlan>() | |
| 250 | + .eq(DeliveryFeePlan::getCityId, cityId) | |
| 251 | + .orderByDesc(DeliveryFeePlan::getListOrder) | |
| 252 | + .last("LIMIT 1")); | |
| 253 | + if (plans.isEmpty() || plans.get(0).getListOrder() == null) { | |
| 254 | + return plans.size(); | |
| 255 | + } | |
| 256 | + return plans.get(0).getListOrder() + 1; | |
| 257 | + } | |
| 258 | + | |
| 259 | + private DeliveryFeePlanVO toPlanVO(DeliveryFeePlan plan) { | |
| 260 | + DeliveryFeePlanVO vo = new DeliveryFeePlanVO(); | |
| 261 | + copyPlanMeta(plan, vo); | |
| 262 | + return vo; | |
| 263 | + } | |
| 264 | + | |
| 265 | + private void copyPlanMeta(DeliveryFeePlan plan, DeliveryFeePlanVO vo) { | |
| 266 | + vo.setId(plan.getId()); | |
| 267 | + vo.setCityId(plan.getCityId()); | |
| 268 | + vo.setName(plan.getName()); | |
| 269 | + vo.setIsDefault(plan.getIsDefault()); | |
| 270 | + vo.setStatus(plan.getStatus()); | |
| 271 | + vo.setListOrder(plan.getListOrder()); | |
| 272 | + vo.setRemark(plan.getRemark()); | |
| 273 | + vo.setCreateTime(plan.getCreateTime()); | |
| 274 | + vo.setUpdateTime(plan.getUpdateTime()); | |
| 275 | + } | |
| 276 | + | |
| 277 | + private DeliveryPricingConfigDTO buildPlanConfig(DeliveryFeePlan plan) { | |
| 278 | + List<DeliveryFeePlanDimension> dimensions = deliveryFeePlanDimensionMapper.selectList( | |
| 279 | + new LambdaQueryWrapper<DeliveryFeePlanDimension>() | |
| 280 | + .eq(DeliveryFeePlanDimension::getPlanId, plan.getId())); | |
| 281 | + Map<String, DeliveryFeePlanDimension> dimensionMap = new HashMap<>(); | |
| 282 | + for (DeliveryFeePlanDimension item : dimensions) { | |
| 283 | + dimensionMap.put(item.getDimensionType(), item); | |
| 284 | + } | |
| 285 | + | |
| 286 | + List<DeliveryFeePlanDistanceStep> distanceSteps = deliveryFeePlanDistanceStepMapper.selectList( | |
| 287 | + new LambdaQueryWrapper<DeliveryFeePlanDistanceStep>() | |
| 288 | + .eq(DeliveryFeePlanDistanceStep::getPlanId, plan.getId()) | |
| 289 | + .orderByAsc(DeliveryFeePlanDistanceStep::getListOrder) | |
| 290 | + .orderByAsc(DeliveryFeePlanDistanceStep::getId)); | |
| 291 | + List<DeliveryFeePlanPieceRule> pieceRules = deliveryFeePlanPieceRuleMapper.selectList( | |
| 292 | + new LambdaQueryWrapper<DeliveryFeePlanPieceRule>() | |
| 293 | + .eq(DeliveryFeePlanPieceRule::getPlanId, plan.getId()) | |
| 294 | + .orderByAsc(DeliveryFeePlanPieceRule::getListOrder) | |
| 295 | + .orderByAsc(DeliveryFeePlanPieceRule::getId)); | |
| 296 | + List<DeliveryFeePlanTimeRule> timeRules = deliveryFeePlanTimeRuleMapper.selectList( | |
| 297 | + new LambdaQueryWrapper<DeliveryFeePlanTimeRule>() | |
| 298 | + .eq(DeliveryFeePlanTimeRule::getPlanId, plan.getId()) | |
| 299 | + .orderByAsc(DeliveryFeePlanTimeRule::getListOrder) | |
| 300 | + .orderByAsc(DeliveryFeePlanTimeRule::getId)); | |
| 301 | + | |
| 302 | + DeliveryPricingRuleDTO type6 = createDefaultType6(); | |
| 303 | + type6.setMinFee(nvl(plan.getMinFee())); | |
| 304 | + type6.setFeeMode(2); | |
| 305 | + | |
| 306 | + DeliveryFeePlanDimension base = dimensionMap.get("base"); | |
| 307 | + if (base != null) { | |
| 308 | + type6.setBaseSwitch(on(base.getEnabled())); | |
| 309 | + type6.setBaseFee(nvl(base.getBaseFee())); | |
| 310 | + } | |
| 311 | + | |
| 312 | + DeliveryFeePlanDimension distance = dimensionMap.get("distance"); | |
| 313 | + if (distance != null) { | |
| 314 | + type6.setDistanceSwitch(on(distance.getEnabled())); | |
| 315 | + type6.setDistanceBasic(nvl(distance.getStartDistance())); | |
| 316 | + type6.setDistanceBasicMoney(nvl(distance.getStartFee())); | |
| 317 | + type6.setDistanceType(2); | |
| 318 | + type6.setDistanceSteps(distanceSteps.stream().map(step -> { | |
| 319 | + DeliveryPricingRuleDTO.DistanceStepDTO item = new DeliveryPricingRuleDTO.DistanceStepDTO(); | |
| 320 | + item.setEndDistance(nvl(step.getEndDistance())); | |
| 321 | + item.setUnitDistance(nvl(step.getUnitDistance())); | |
| 322 | + item.setUnitFee(nvl(step.getUnitFee())); | |
| 323 | + item.setListOrder(step.getListOrder()); | |
| 324 | + return item; | |
| 325 | + }).collect(Collectors.toList())); | |
| 326 | + if (!distanceSteps.isEmpty()) { | |
| 327 | + type6.setDistanceMoreMoney(nvl(distanceSteps.get(distanceSteps.size() - 1).getUnitFee())); | |
| 328 | + } | |
| 329 | + } | |
| 330 | + | |
| 331 | + DeliveryFeePlanDimension weight = dimensionMap.get("weight"); | |
| 332 | + if (weight != null) { | |
| 333 | + type6.setWeightSwitch(on(weight.getEnabled())); | |
| 334 | + type6.setWeightFirst(nvl(weight.getFirstWeight())); | |
| 335 | + type6.setWeightFirstFee(nvl(weight.getFirstFee())); | |
| 336 | + type6.setWeightUnitFee(nvl(weight.getUnitWeightFee())); | |
| 337 | + type6.setWeightCapFee(nvl(weight.getCapFee())); | |
| 338 | + type6.setWeightBasic(nvl(weight.getFirstWeight())); | |
| 339 | + type6.setWeightBasicMoney(nvl(weight.getFirstFee())); | |
| 340 | + type6.setWeightMoreMoney(nvl(weight.getUnitWeightFee())); | |
| 341 | + } | |
| 342 | + | |
| 343 | + DeliveryFeePlanDimension piece = dimensionMap.get("piece"); | |
| 344 | + if (piece != null) { | |
| 345 | + type6.setPieceSwitch(on(piece.getEnabled())); | |
| 346 | + type6.setPieceRules(pieceRules.stream().map(rule -> { | |
| 347 | + DeliveryPricingRuleDTO.PieceRuleDTO item = new DeliveryPricingRuleDTO.PieceRuleDTO(); | |
| 348 | + item.setStartPiece(rule.getStartPiece()); | |
| 349 | + item.setEndPiece(rule.getEndPiece()); | |
| 350 | + item.setFee(nvl(rule.getFee())); | |
| 351 | + item.setListOrder(rule.getListOrder()); | |
| 352 | + return item; | |
| 353 | + }).collect(Collectors.toList())); | |
| 354 | + } | |
| 355 | + | |
| 356 | + DeliveryFeePlanDimension time = dimensionMap.get("time"); | |
| 357 | + if (time != null) { | |
| 358 | + type6.setTimes(timeRules.stream().map(rule -> { | |
| 359 | + DeliveryPricingRuleDTO.TimePeriodDTO item = new DeliveryPricingRuleDTO.TimePeriodDTO(); | |
| 360 | + item.setStart(rule.getStartMinute()); | |
| 361 | + item.setEnd(rule.getEndMinute()); | |
| 362 | + item.setMoney(nvl(rule.getFee())); | |
| 363 | + item.setIsOpen(on(rule.getEnabled())); | |
| 364 | + return item; | |
| 365 | + }).collect(Collectors.toList())); | |
| 366 | + } | |
| 367 | + | |
| 368 | + DeliveryPricingConfigDTO config = createDefaultConfig(); | |
| 369 | + config.setType6(type6); | |
| 370 | + config.setDistanceBasic(nvl(plan.getDistanceBasic())); | |
| 371 | + config.setDistanceBasicTime(plan.getDistanceBasicTime() != null ? plan.getDistanceBasicTime() : 30); | |
| 372 | + config.setDistanceMoreTime(plan.getDistanceMoreTime() != null ? plan.getDistanceMoreTime() : 10); | |
| 373 | + config.setRiderDistance(nvl(plan.getRiderDistance())); | |
| 374 | + config.setRiderTime(plan.getRiderTime()); | |
| 375 | + return config; | |
| 376 | + } | |
| 377 | + | |
| 378 | + private void savePlanConfig(DeliveryFeePlan plan, DeliveryPricingConfigDTO config, long now) { | |
| 379 | + DeliveryPricingConfigDTO normalized = normalizeConfig(config); | |
| 380 | + DeliveryPricingRuleDTO type6 = normalized.getType6(); | |
| 381 | + | |
| 382 | + plan.setMinFee(nvl(type6.getMinFee())); | |
| 383 | + plan.setDistanceBasic(nvl(normalized.getDistanceBasic())); | |
| 384 | + plan.setDistanceBasicTime(normalized.getDistanceBasicTime() != null ? normalized.getDistanceBasicTime() : 30); | |
| 385 | + plan.setDistanceMoreTime(normalized.getDistanceMoreTime() != null ? normalized.getDistanceMoreTime() : 10); | |
| 386 | + plan.setRiderDistance(nvl(normalized.getRiderDistance())); | |
| 387 | + plan.setRiderTime(normalized.getRiderTime() != null ? normalized.getRiderTime() : 0); | |
| 388 | + plan.setUpdateTime(now); | |
| 389 | + deliveryFeePlanMapper.updateById(plan); | |
| 390 | + | |
| 391 | + upsertDimension(plan.getId(), "base", on(type6.getBaseSwitch()), type6.getBaseFee(), | |
| 392 | + null, null, null, null, null, null, now); | |
| 393 | + upsertDimension(plan.getId(), "distance", on(type6.getDistanceSwitch()), null, | |
| 394 | + type6.getDistanceBasic(), type6.getDistanceBasicMoney(), null, null, null, null, now); | |
| 395 | + upsertDimension(plan.getId(), "weight", on(type6.getWeightSwitch()), null, | |
| 396 | + null, null, type6.getWeightFirst(), type6.getWeightFirstFee(), | |
| 397 | + type6.getWeightUnitFee(), type6.getWeightCapFee(), now); | |
| 398 | + upsertDimension(plan.getId(), "piece", on(type6.getPieceSwitch()), null, | |
| 399 | + null, null, null, null, null, null, now); | |
| 400 | + upsertDimension(plan.getId(), "time", type6.getTimes() != null && !type6.getTimes().isEmpty() ? 1 : 0, | |
| 401 | + null, null, null, null, null, null, null, now); | |
| 402 | + | |
| 403 | + deliveryFeePlanDistanceStepMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanDistanceStep>() | |
| 404 | + .eq(DeliveryFeePlanDistanceStep::getPlanId, plan.getId())); | |
| 405 | + int stepOrder = 0; | |
| 406 | + for (DeliveryPricingRuleDTO.DistanceStepDTO item : safeList(type6.getDistanceSteps())) { | |
| 407 | + DeliveryFeePlanDistanceStep step = new DeliveryFeePlanDistanceStep(); | |
| 408 | + step.setPlanId(plan.getId()); | |
| 409 | + step.setEndDistance(nvl(item.getEndDistance())); | |
| 410 | + step.setUnitDistance(nvl(item.getUnitDistance())); | |
| 411 | + step.setUnitFee(nvl(item.getUnitFee())); | |
| 412 | + step.setListOrder(item.getListOrder() != null ? item.getListOrder() : stepOrder++); | |
| 413 | + step.setCreateTime(now); | |
| 414 | + step.setUpdateTime(now); | |
| 415 | + deliveryFeePlanDistanceStepMapper.insert(step); | |
| 416 | + } | |
| 417 | + | |
| 418 | + deliveryFeePlanPieceRuleMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanPieceRule>() | |
| 419 | + .eq(DeliveryFeePlanPieceRule::getPlanId, plan.getId())); | |
| 420 | + int pieceOrder = 0; | |
| 421 | + for (DeliveryPricingRuleDTO.PieceRuleDTO item : safeList(type6.getPieceRules())) { | |
| 422 | + DeliveryFeePlanPieceRule rule = new DeliveryFeePlanPieceRule(); | |
| 423 | + rule.setPlanId(plan.getId()); | |
| 424 | + rule.setStartPiece(item.getStartPiece() != null ? item.getStartPiece() : 0); | |
| 425 | + rule.setEndPiece(item.getEndPiece() != null ? item.getEndPiece() : 0); | |
| 426 | + rule.setFee(nvl(item.getFee())); | |
| 427 | + rule.setListOrder(item.getListOrder() != null ? item.getListOrder() : pieceOrder++); | |
| 428 | + rule.setCreateTime(now); | |
| 429 | + rule.setUpdateTime(now); | |
| 430 | + deliveryFeePlanPieceRuleMapper.insert(rule); | |
| 431 | + } | |
| 432 | + | |
| 433 | + deliveryFeePlanTimeRuleMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanTimeRule>() | |
| 434 | + .eq(DeliveryFeePlanTimeRule::getPlanId, plan.getId())); | |
| 435 | + int timeOrder = 0; | |
| 436 | + for (DeliveryPricingRuleDTO.TimePeriodDTO item : safeList(type6.getTimes())) { | |
| 437 | + DeliveryFeePlanTimeRule rule = new DeliveryFeePlanTimeRule(); | |
| 438 | + rule.setPlanId(plan.getId()); | |
| 439 | + rule.setStartMinute(item.getStart() != null ? item.getStart() : 0); | |
| 440 | + rule.setEndMinute(item.getEnd() != null ? item.getEnd() : 0); | |
| 441 | + rule.setFee(nvl(item.getMoney())); | |
| 442 | + rule.setEnabled(on(item.getIsOpen())); | |
| 443 | + rule.setListOrder(timeOrder++); | |
| 444 | + rule.setCreateTime(now); | |
| 445 | + rule.setUpdateTime(now); | |
| 446 | + deliveryFeePlanTimeRuleMapper.insert(rule); | |
| 447 | + } | |
| 448 | + } | |
| 449 | + | |
| 450 | + private void deletePlanChildren(Long planId) { | |
| 451 | + deliveryFeePlanDimensionMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanDimension>() | |
| 452 | + .eq(DeliveryFeePlanDimension::getPlanId, planId)); | |
| 453 | + deliveryFeePlanDistanceStepMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanDistanceStep>() | |
| 454 | + .eq(DeliveryFeePlanDistanceStep::getPlanId, planId)); | |
| 455 | + deliveryFeePlanPieceRuleMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanPieceRule>() | |
| 456 | + .eq(DeliveryFeePlanPieceRule::getPlanId, planId)); | |
| 457 | + deliveryFeePlanTimeRuleMapper.delete(new LambdaQueryWrapper<DeliveryFeePlanTimeRule>() | |
| 458 | + .eq(DeliveryFeePlanTimeRule::getPlanId, planId)); | |
| 459 | + } | |
| 460 | + | |
| 461 | + private void upsertDimension(Long planId, String type, Integer enabled, BigDecimal baseFee, | |
| 462 | + BigDecimal startDistance, BigDecimal startFee, BigDecimal firstWeight, | |
| 463 | + BigDecimal firstFee, BigDecimal unitWeightFee, BigDecimal capFee, long now) { | |
| 464 | + DeliveryFeePlanDimension dimension = deliveryFeePlanDimensionMapper.selectOne( | |
| 465 | + new LambdaQueryWrapper<DeliveryFeePlanDimension>() | |
| 466 | + .eq(DeliveryFeePlanDimension::getPlanId, planId) | |
| 467 | + .eq(DeliveryFeePlanDimension::getDimensionType, type) | |
| 468 | + .last("LIMIT 1")); | |
| 469 | + if (dimension == null) { | |
| 470 | + dimension = new DeliveryFeePlanDimension(); | |
| 471 | + dimension.setPlanId(planId); | |
| 472 | + dimension.setDimensionType(type); | |
| 473 | + dimension.setCreateTime(now); | |
| 474 | + } | |
| 475 | + dimension.setEnabled(on(enabled)); | |
| 476 | + dimension.setBaseFee(nvl(baseFee)); | |
| 477 | + dimension.setStartDistance(nvl(startDistance)); | |
| 478 | + dimension.setStartFee(nvl(startFee)); | |
| 479 | + dimension.setFirstWeight(nvl(firstWeight)); | |
| 480 | + dimension.setFirstFee(nvl(firstFee)); | |
| 481 | + dimension.setUnitWeightFee(nvl(unitWeightFee)); | |
| 482 | + dimension.setCapFee(nvl(capFee)); | |
| 483 | + dimension.setUpdateTime(now); | |
| 484 | + if (dimension.getId() == null) { | |
| 485 | + deliveryFeePlanDimensionMapper.insert(dimension); | |
| 486 | + } else { | |
| 487 | + deliveryFeePlanDimensionMapper.updateById(dimension); | |
| 488 | + } | |
| 489 | + } | |
| 490 | + | |
| 491 | + private DeliveryPricingConfigDTO normalizeConfig(DeliveryPricingConfigDTO input) { | |
| 492 | + DeliveryPricingConfigDTO config = input != null ? input : createDefaultConfig(); | |
| 493 | + DeliveryPricingRuleDTO type6 = config.getType6() != null ? config.getType6() : createDefaultType6(); | |
| 494 | + | |
| 495 | + if (type6.getDistanceSteps() == null) { | |
| 496 | + type6.setDistanceSteps(new ArrayList<>()); | |
| 497 | + } | |
| 498 | + if (type6.getPieceRules() == null) { | |
| 499 | + type6.setPieceRules(new ArrayList<>()); | |
| 500 | + } | |
| 501 | + if (type6.getTimes() == null) { | |
| 502 | + type6.setTimes(new ArrayList<>()); | |
| 503 | + } | |
| 504 | + | |
| 505 | + config.setType(Arrays.asList(6)); | |
| 506 | + config.setType6(type6); | |
| 507 | + config.setDistanceBasic(config.getDistanceBasic() != null ? config.getDistanceBasic() : BigDecimal.valueOf(3)); | |
| 508 | + config.setDistanceBasicTime(config.getDistanceBasicTime() != null ? config.getDistanceBasicTime() : 30); | |
| 509 | + config.setDistanceMoreTime(config.getDistanceMoreTime() != null ? config.getDistanceMoreTime() : 10); | |
| 510 | + config.setRiderDistance(config.getRiderDistance() != null ? config.getRiderDistance() : BigDecimal.valueOf(3)); | |
| 511 | + config.setRiderTime(config.getRiderTime() != null ? config.getRiderTime() : 0); | |
| 512 | + return config; | |
| 513 | + } | |
| 514 | + | |
| 515 | + private DeliveryPricingConfigDTO createDefaultConfig() { | |
| 516 | + DeliveryPricingConfigDTO config = new DeliveryPricingConfigDTO(); | |
| 517 | + config.setType(Arrays.asList(6)); | |
| 518 | + config.setType6(createDefaultType6()); | |
| 519 | + config.setDistanceBasic(BigDecimal.valueOf(3)); | |
| 520 | + config.setDistanceBasicTime(30); | |
| 521 | + config.setDistanceMoreTime(10); | |
| 522 | + config.setRiderDistance(BigDecimal.valueOf(3)); | |
| 523 | + return config; | |
| 524 | + } | |
| 525 | + | |
| 526 | + private DeliveryPricingRuleDTO createDefaultType6() { | |
| 527 | + DeliveryPricingRuleDTO type6 = new DeliveryPricingRuleDTO(); | |
| 528 | + type6.setMinFee(BigDecimal.ZERO); | |
| 529 | + type6.setBaseSwitch(0); | |
| 530 | + type6.setBaseFee(BigDecimal.ZERO); | |
| 531 | + type6.setFeeMode(2); | |
| 532 | + type6.setFixMoney(BigDecimal.ZERO); | |
| 533 | + type6.setDistanceSwitch(1); | |
| 534 | + type6.setDistanceBasic(BigDecimal.valueOf(3)); | |
| 535 | + type6.setDistanceBasicMoney(BigDecimal.valueOf(4)); | |
| 536 | + type6.setDistanceMoreMoney(BigDecimal.valueOf(1.5)); | |
| 537 | + type6.setDistanceMode(1); | |
| 538 | + type6.setDistanceType(1); | |
| 539 | + type6.setDistanceSteps(new ArrayList<>()); | |
| 540 | + type6.setWeightSwitch(1); | |
| 541 | + type6.setWeightFirst(BigDecimal.valueOf(5)); | |
| 542 | + type6.setWeightFirstFee(BigDecimal.ZERO); | |
| 543 | + type6.setWeightUnitFee(BigDecimal.ONE); | |
| 544 | + type6.setWeightCapFee(BigDecimal.valueOf(30)); | |
| 545 | + type6.setWeightBasic(BigDecimal.ZERO); | |
| 546 | + type6.setWeightBasicMoney(BigDecimal.ZERO); | |
| 547 | + type6.setWeightMoreMoney(BigDecimal.ZERO); | |
| 548 | + type6.setWeightType(1); | |
| 549 | + type6.setPieceSwitch(0); | |
| 550 | + type6.setPieceRules(new ArrayList<>()); | |
| 551 | + type6.setTimes(new ArrayList<>()); | |
| 552 | + return type6; | |
| 553 | + } | |
| 554 | + | |
| 555 | + private String safeName(String value, String fallback) { | |
| 556 | + if (value == null || value.isBlank()) { | |
| 557 | + return fallback; | |
| 558 | + } | |
| 559 | + return value.trim(); | |
| 560 | + } | |
| 561 | + | |
| 562 | + private long now() { | |
| 563 | + return System.currentTimeMillis() / 1000; | |
| 564 | + } | |
| 565 | + | |
| 566 | + private BigDecimal nvl(BigDecimal value) { | |
| 567 | + return value != null ? value : BigDecimal.ZERO; | |
| 568 | + } | |
| 569 | + | |
| 570 | + private Integer on(Integer value) { | |
| 571 | + return value != null && value == 1 ? 1 : 0; | |
| 572 | + } | |
| 573 | + | |
| 574 | + private <T> List<T> safeList(List<T> list) { | |
| 575 | + return list != null ? list : new ArrayList<>(); | |
| 576 | + } | |
| 577 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/DeliveryFeeServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/DeliveryFeeServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.common.exception.BizException; | |
| 4 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 5 | +import com.diligrp.rider.dto.DeliveryPricingRuleDTO; | |
| 6 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 7 | +import com.diligrp.rider.service.CityService; | |
| 8 | +import com.diligrp.rider.service.DeliveryFeeService; | |
| 9 | +import com.diligrp.rider.util.GeoUtil; | |
| 10 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 11 | +import lombok.RequiredArgsConstructor; | |
| 12 | +import lombok.extern.slf4j.Slf4j; | |
| 13 | +import org.springframework.stereotype.Service; | |
| 14 | + | |
| 15 | +import java.math.BigDecimal; | |
| 16 | +import java.math.RoundingMode; | |
| 17 | +import java.time.Instant; | |
| 18 | +import java.time.ZoneId; | |
| 19 | +import java.time.ZonedDateTime; | |
| 20 | +import java.util.ArrayList; | |
| 21 | +import java.util.Comparator; | |
| 22 | +import java.util.List; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * 配送费计算引擎 | |
| 26 | + * Helpsend.computed() + City.checkTime() + City.getLength() | |
| 27 | + */ | |
| 28 | +@Slf4j | |
| 29 | +@Service | |
| 30 | +@RequiredArgsConstructor | |
| 31 | +public class DeliveryFeeServiceImpl implements DeliveryFeeService { | |
| 32 | + | |
| 33 | + private final CityService cityService; | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public DeliveryFeeResultVO calcFee(DeliveryFeeCalcDTO dto) { | |
| 37 | + DeliveryPricingConfigDTO pricingConfig = cityService.getConfig(dto.getCityId()); | |
| 38 | + return calcFeeByConfig(pricingConfig, dto); | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public DeliveryFeeResultVO calcFeeByConfig(DeliveryPricingConfigDTO pricingConfig, DeliveryFeeCalcDTO dto) { | |
| 43 | + if (pricingConfig == null || pricingConfig.getType() == null) { | |
| 44 | + throw new BizException("当前城市未开通服务"); | |
| 45 | + } | |
| 46 | + | |
| 47 | + int orderType = dto.getOrderType(); | |
| 48 | + if (!pricingConfig.getType().contains(orderType)) { | |
| 49 | + throw new BizException("当前城市未开通该类型服务"); | |
| 50 | + } | |
| 51 | + | |
| 52 | + // 获取该订单类型的配送费配置 | |
| 53 | + DeliveryPricingRuleDTO typeConfig = getTypeConfig(pricingConfig, orderType); | |
| 54 | + if (typeConfig == null) { | |
| 55 | + throw new BizException("该服务类型未配置收费规则"); | |
| 56 | + } | |
| 57 | + | |
| 58 | + DeliveryFeeResultVO result = new DeliveryFeeResultVO(); | |
| 59 | + result.setMoneyBasic(BigDecimal.ZERO); | |
| 60 | + result.setMoneyBasicTxt(""); | |
| 61 | + result.setMoneyDistance(BigDecimal.ZERO); | |
| 62 | + result.setMoneyDistanceTxt(""); | |
| 63 | + result.setMoneyWeight(BigDecimal.ZERO); | |
| 64 | + result.setMoneyWeightTxt(""); | |
| 65 | + result.setMoneyPiece(BigDecimal.ZERO); | |
| 66 | + result.setMoneyPieceTxt(""); | |
| 67 | + result.setMoneyTime(BigDecimal.ZERO); | |
| 68 | + result.setDistance(BigDecimal.ZERO); | |
| 69 | + result.setWeight(dto.getWeight()); | |
| 70 | + result.setPieces(dto.getPieces()); | |
| 71 | + result.setMinFee(nvl(typeConfig.getMinFee())); | |
| 72 | + result.setMinFeeApplied(0); | |
| 73 | + | |
| 74 | + // --- fee_mode=1:固定费用 --- | |
| 75 | + if (typeConfig.getFeeMode() != null && typeConfig.getFeeMode() == 1) { | |
| 76 | + BigDecimal fix = typeConfig.getFixMoney() != null ? typeConfig.getFixMoney() : BigDecimal.ZERO; | |
| 77 | + result.setMoneyBasic(fix); | |
| 78 | + result.setTotalFee(applyMinFee(fix, typeConfig, result)); | |
| 79 | + result.setEstimatedMinutes(calcEstimatedMinutes(pricingConfig, BigDecimal.ZERO)); | |
| 80 | + return result; | |
| 81 | + } | |
| 82 | + | |
| 83 | + // --- fee_mode=2:按距离/重量计费 --- | |
| 84 | + BigDecimal moneyBasic = BigDecimal.ZERO; | |
| 85 | + BigDecimal moneyDistance = BigDecimal.ZERO; | |
| 86 | + BigDecimal moneyWeight = BigDecimal.ZERO; | |
| 87 | + BigDecimal moneyPiece = BigDecimal.ZERO; | |
| 88 | + String moneyBasicTxt = ""; | |
| 89 | + String moneyDistanceTxt = ""; | |
| 90 | + String moneyWeightTxt = ""; | |
| 91 | + String moneyPieceTxt = ""; | |
| 92 | + BigDecimal distanceKm = BigDecimal.ZERO; | |
| 93 | + | |
| 94 | + // 基础费 | |
| 95 | + if (typeConfig.getBaseSwitch() != null && typeConfig.getBaseSwitch() == 1) { | |
| 96 | + moneyBasic = moneyBasic.add(nvl(typeConfig.getBaseFee())); | |
| 97 | + } | |
| 98 | + | |
| 99 | + // 距离计费 | |
| 100 | + if (typeConfig.getDistanceSwitch() != null && typeConfig.getDistanceSwitch() == 1) { | |
| 101 | + BigDecimal distanceBasicMoney = nvl(typeConfig.getDistanceBasicMoney()); | |
| 102 | + moneyBasic = moneyBasic.add(distanceBasicMoney); | |
| 103 | + BigDecimal basicKm = nvl(typeConfig.getDistanceBasic()); | |
| 104 | + moneyBasicTxt = "(" + basicKm.stripTrailingZeros().toPlainString() + "km)"; | |
| 105 | + | |
| 106 | + // 计算实际距离 | |
| 107 | + distanceKm = calcDistance(typeConfig, dto); | |
| 108 | + | |
| 109 | + // 超出距离费 | |
| 110 | + if (distanceKm.compareTo(basicKm) > 0) { | |
| 111 | + BigDecimal moreKm = distanceKm.subtract(basicKm); | |
| 112 | + if (typeConfig.getDistanceSteps() != null && !typeConfig.getDistanceSteps().isEmpty()) { | |
| 113 | + moneyDistance = calcDistanceStepFee(basicKm, distanceKm, typeConfig.getDistanceSteps()); | |
| 114 | + } else { | |
| 115 | + BigDecimal moreMoneyPerKm = nvl(typeConfig.getDistanceMoreMoney()); | |
| 116 | + BigDecimal moreKmCeil = moreKm.setScale(0, RoundingMode.CEILING); | |
| 117 | + moneyDistance = moreKmCeil.multiply(moreMoneyPerKm).setScale(2, RoundingMode.HALF_UP); | |
| 118 | + } | |
| 119 | + moneyDistanceTxt = "(" + moreKm.setScale(1, RoundingMode.HALF_UP).toPlainString() + "km)"; | |
| 120 | + } | |
| 121 | + } | |
| 122 | + | |
| 123 | + // 重量计费 | |
| 124 | + if (typeConfig.getWeightSwitch() != null && typeConfig.getWeightSwitch() == 1) { | |
| 125 | + BigDecimal weightBasicMoney = nvl(typeConfig.getWeightFirstFee().compareTo(BigDecimal.ZERO) > 0 | |
| 126 | + ? typeConfig.getWeightFirstFee() : typeConfig.getWeightBasicMoney()); | |
| 127 | + moneyBasic = moneyBasic.add(weightBasicMoney); | |
| 128 | + | |
| 129 | + BigDecimal weight = dto.getWeight() != null ? dto.getWeight() : BigDecimal.ZERO; | |
| 130 | + BigDecimal weightAdj = adjustValue(weight, typeConfig.getWeightType()); | |
| 131 | + BigDecimal basicWeight = nvl(typeConfig.getWeightFirst().compareTo(BigDecimal.ZERO) > 0 | |
| 132 | + ? typeConfig.getWeightFirst() : typeConfig.getWeightBasic()); | |
| 133 | + BigDecimal moreMoneyPerKg = nvl(typeConfig.getWeightUnitFee().compareTo(BigDecimal.ZERO) > 0 | |
| 134 | + ? typeConfig.getWeightUnitFee() : typeConfig.getWeightMoreMoney()); | |
| 135 | + BigDecimal capFee = nvl(typeConfig.getWeightCapFee()); | |
| 136 | + | |
| 137 | + if (weightAdj.compareTo(basicWeight) > 0) { | |
| 138 | + BigDecimal moreWeight = weightAdj.subtract(basicWeight); | |
| 139 | + moneyWeight = moreWeight.multiply(moreMoneyPerKg).setScale(2, RoundingMode.HALF_UP); | |
| 140 | + if (capFee.compareTo(BigDecimal.ZERO) > 0) { | |
| 141 | + BigDecimal weightTotal = weightBasicMoney.add(moneyWeight); | |
| 142 | + if (weightTotal.compareTo(capFee) > 0) { | |
| 143 | + moneyWeight = capFee.subtract(weightBasicMoney).max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP); | |
| 144 | + } | |
| 145 | + } | |
| 146 | + moneyWeightTxt = "(" + moreWeight.toPlainString() + "kg)"; | |
| 147 | + } | |
| 148 | + } | |
| 149 | + | |
| 150 | + // 件数计费 | |
| 151 | + if (typeConfig.getPieceSwitch() != null && typeConfig.getPieceSwitch() == 1 | |
| 152 | + && typeConfig.getPieceRules() != null && !typeConfig.getPieceRules().isEmpty()) { | |
| 153 | + int pieces = dto.getPieces() != null ? dto.getPieces() : 0; | |
| 154 | + List<DeliveryPricingRuleDTO.PieceRuleDTO> rules = new ArrayList<>(typeConfig.getPieceRules()); | |
| 155 | + rules.sort(Comparator.comparing(rule -> rule.getListOrder() == null ? 0 : rule.getListOrder())); | |
| 156 | + for (DeliveryPricingRuleDTO.PieceRuleDTO rule : rules) { | |
| 157 | + int start = rule.getStartPiece() != null ? rule.getStartPiece() : 0; | |
| 158 | + int end = rule.getEndPiece() != null ? rule.getEndPiece() : Integer.MAX_VALUE; | |
| 159 | + if (pieces >= start && pieces <= end) { | |
| 160 | + moneyPiece = nvl(rule.getFee()).setScale(2, RoundingMode.HALF_UP); | |
| 161 | + moneyPieceTxt = "(" + start + "-" + end + "件)"; | |
| 162 | + break; | |
| 163 | + } | |
| 164 | + } | |
| 165 | + } | |
| 166 | + | |
| 167 | + // 时段附加费 | |
| 168 | + long serviceTime = dto.getServiceTime() != null && dto.getServiceTime() > 0 | |
| 169 | + ? dto.getServiceTime() : System.currentTimeMillis() / 1000; | |
| 170 | + BigDecimal moneyTime = calcTimeMoney(typeConfig, serviceTime); | |
| 171 | + | |
| 172 | + result.setMoneyBasic(moneyBasic); | |
| 173 | + result.setMoneyBasicTxt(moneyBasicTxt); | |
| 174 | + result.setMoneyDistance(moneyDistance); | |
| 175 | + result.setMoneyDistanceTxt(moneyDistanceTxt); | |
| 176 | + result.setMoneyWeight(moneyWeight); | |
| 177 | + result.setMoneyWeightTxt(moneyWeightTxt); | |
| 178 | + result.setMoneyPiece(moneyPiece); | |
| 179 | + result.setMoneyPieceTxt(moneyPieceTxt); | |
| 180 | + result.setMoneyTime(moneyTime); | |
| 181 | + result.setDistance(distanceKm); | |
| 182 | + | |
| 183 | + BigDecimal total = moneyBasic.add(moneyDistance).add(moneyWeight).add(moneyPiece).add(moneyTime); | |
| 184 | + result.setTotalFee(applyMinFee(total, typeConfig, result)); | |
| 185 | + result.setEstimatedMinutes(calcEstimatedMinutes(pricingConfig, distanceKm)); | |
| 186 | + | |
| 187 | + return result; | |
| 188 | + } | |
| 189 | + | |
| 190 | + @Override | |
| 191 | + public boolean isServiceEnabled(Long cityId, int orderType) { | |
| 192 | + DeliveryPricingConfigDTO config = cityService.getConfig(cityId); | |
| 193 | + return config != null && config.getType() != null && config.getType().contains(orderType); | |
| 194 | + } | |
| 195 | + | |
| 196 | + // ---- 私有方法 ---- | |
| 197 | + | |
| 198 | + /** | |
| 199 | + * 计算实际距离(km), getDistance() + distance_type取整 | |
| 200 | + */ | |
| 201 | + private BigDecimal calcDistance(DeliveryPricingRuleDTO typeConfig, DeliveryFeeCalcDTO dto) { | |
| 202 | + double disKm; | |
| 203 | + if (typeConfig.getDistanceMode() != null && typeConfig.getDistanceMode() == 2) { | |
| 204 | + // 直接使用传入距离(米转km) | |
| 205 | + disKm = 0; // 传入距离模式下 dto 中应有 distance 字段,此处简化为0 | |
| 206 | + } else { | |
| 207 | + // 经纬度计算 | |
| 208 | + try { | |
| 209 | + disKm = GeoUtil.calcDistanceKm( | |
| 210 | + Double.parseDouble(dto.getStartLat()), | |
| 211 | + Double.parseDouble(dto.getStartLng()), | |
| 212 | + Double.parseDouble(dto.getEndLat()), | |
| 213 | + Double.parseDouble(dto.getEndLng())); | |
| 214 | + } catch (Exception e) { | |
| 215 | + throw new BizException("地点经纬度信息错误"); | |
| 216 | + } | |
| 217 | + } | |
| 218 | + return adjustValue(BigDecimal.valueOf(disKm), typeConfig.getDistanceType()); | |
| 219 | + } | |
| 220 | + | |
| 221 | + /** | |
| 222 | + * 根据取整方式调整数值 | |
| 223 | + * distanceType: 1=四舍五入(保留1位) 2=向上取整 3=向下取整 | |
| 224 | + */ | |
| 225 | + private BigDecimal adjustValue(BigDecimal value, Integer type) { | |
| 226 | + if (type == null || type == 1) { | |
| 227 | + return value.setScale(1, RoundingMode.HALF_UP); | |
| 228 | + } else if (type == 2) { | |
| 229 | + return value.setScale(0, RoundingMode.CEILING).setScale(1); | |
| 230 | + } else { | |
| 231 | + return value.setScale(0, RoundingMode.FLOOR).setScale(1); | |
| 232 | + } | |
| 233 | + } | |
| 234 | + | |
| 235 | + /** | |
| 236 | + * 计算时段附加费 | |
| 237 | + * City.checkTime() | |
| 238 | + */ | |
| 239 | + private BigDecimal calcTimeMoney(DeliveryPricingRuleDTO typeConfig, long serviceTime) { | |
| 240 | + if (typeConfig.getTimes() == null || typeConfig.getTimes().isEmpty()) { | |
| 241 | + return BigDecimal.ZERO; | |
| 242 | + } | |
| 243 | + ZonedDateTime zdt = Instant.ofEpochSecond(serviceTime).atZone(ZoneId.of("Asia/Shanghai")); | |
| 244 | + int minuteOfDay = zdt.getHour() * 60 + zdt.getMinute(); | |
| 245 | + | |
| 246 | + BigDecimal total = BigDecimal.ZERO; | |
| 247 | + for (DeliveryPricingRuleDTO.TimePeriodDTO period : typeConfig.getTimes()) { | |
| 248 | + if (period.getIsOpen() == null || period.getIsOpen() != 1) continue; | |
| 249 | + if (period.getStart() == null || period.getEnd() == null) continue; | |
| 250 | + if (period.getStart() <= period.getEnd()) { | |
| 251 | + if (minuteOfDay >= period.getStart() && minuteOfDay < period.getEnd()) { | |
| 252 | + total = total.add(period.getMoney() != null ? period.getMoney() : BigDecimal.ZERO); | |
| 253 | + } | |
| 254 | + } else { | |
| 255 | + if (minuteOfDay >= period.getStart() || minuteOfDay < period.getEnd()) { | |
| 256 | + total = total.add(period.getMoney() != null ? period.getMoney() : BigDecimal.ZERO); | |
| 257 | + } | |
| 258 | + } | |
| 259 | + } | |
| 260 | + return total; | |
| 261 | + } | |
| 262 | + | |
| 263 | + /** | |
| 264 | + * 计算预计送达分钟数 | |
| 265 | + * City.getLength() | |
| 266 | + */ | |
| 267 | + private int calcEstimatedMinutes(DeliveryPricingConfigDTO pricingConfig, BigDecimal distanceKm) { | |
| 268 | + if (pricingConfig.getDistanceBasicTime() == null) return 0; | |
| 269 | + int basicTime = pricingConfig.getDistanceBasicTime(); | |
| 270 | + BigDecimal basicKm = pricingConfig.getDistanceBasic() != null ? pricingConfig.getDistanceBasic() : BigDecimal.ZERO; | |
| 271 | + int moreTime = pricingConfig.getDistanceMoreTime() != null ? pricingConfig.getDistanceMoreTime() : 0; | |
| 272 | + | |
| 273 | + if (distanceKm.compareTo(basicKm) <= 0) return basicTime; | |
| 274 | + BigDecimal moreKm = distanceKm.subtract(basicKm); | |
| 275 | + return (int) (basicTime + moreKm.doubleValue() * moreTime); | |
| 276 | + } | |
| 277 | + | |
| 278 | + private DeliveryPricingRuleDTO getTypeConfig(DeliveryPricingConfigDTO config, int orderType) { | |
| 279 | + return switch (orderType) { | |
| 280 | + case 1 -> config.getType1(); | |
| 281 | + case 2 -> config.getType2(); | |
| 282 | + case 6 -> config.getType6(); | |
| 283 | + default -> null; | |
| 284 | + }; | |
| 285 | + } | |
| 286 | + | |
| 287 | + private BigDecimal nvl(BigDecimal v) { | |
| 288 | + return v != null ? v : BigDecimal.ZERO; | |
| 289 | + } | |
| 290 | + | |
| 291 | + private BigDecimal calcDistanceStepFee(BigDecimal basicKm, BigDecimal distanceKm, | |
| 292 | + List<DeliveryPricingRuleDTO.DistanceStepDTO> steps) { | |
| 293 | + List<DeliveryPricingRuleDTO.DistanceStepDTO> sorted = new ArrayList<>(steps); | |
| 294 | + sorted.sort(Comparator.comparing(step -> step.getListOrder() == null ? 0 : step.getListOrder())); | |
| 295 | + | |
| 296 | + BigDecimal fee = BigDecimal.ZERO; | |
| 297 | + BigDecimal prevThreshold = basicKm; | |
| 298 | + for (DeliveryPricingRuleDTO.DistanceStepDTO step : sorted) { | |
| 299 | + BigDecimal endDistance = nvl(step.getEndDistance()); | |
| 300 | + BigDecimal unitDistance = nvl(step.getUnitDistance()); | |
| 301 | + BigDecimal unitFee = nvl(step.getUnitFee()); | |
| 302 | + if (unitDistance.compareTo(BigDecimal.ZERO) <= 0 || unitFee.compareTo(BigDecimal.ZERO) < 0 | |
| 303 | + || endDistance.compareTo(prevThreshold) <= 0) { | |
| 304 | + continue; | |
| 305 | + } | |
| 306 | + | |
| 307 | + BigDecimal capped = distanceKm.min(endDistance); | |
| 308 | + if (capped.compareTo(prevThreshold) > 0) { | |
| 309 | + BigDecimal segments = capped.subtract(prevThreshold) | |
| 310 | + .divide(unitDistance, 0, RoundingMode.CEILING); | |
| 311 | + fee = fee.add(segments.multiply(unitFee)); | |
| 312 | + prevThreshold = endDistance; | |
| 313 | + } | |
| 314 | + if (distanceKm.compareTo(endDistance) <= 0) { | |
| 315 | + return fee.setScale(2, RoundingMode.HALF_UP); | |
| 316 | + } | |
| 317 | + } | |
| 318 | + | |
| 319 | + if (!sorted.isEmpty() && distanceKm.compareTo(prevThreshold) > 0) { | |
| 320 | + DeliveryPricingRuleDTO.DistanceStepDTO last = sorted.get(sorted.size() - 1); | |
| 321 | + BigDecimal unitDistance = nvl(last.getUnitDistance()); | |
| 322 | + BigDecimal unitFee = nvl(last.getUnitFee()); | |
| 323 | + if (unitDistance.compareTo(BigDecimal.ZERO) > 0) { | |
| 324 | + BigDecimal segments = distanceKm.subtract(prevThreshold) | |
| 325 | + .divide(unitDistance, 0, RoundingMode.CEILING); | |
| 326 | + fee = fee.add(segments.multiply(unitFee)); | |
| 327 | + } | |
| 328 | + } | |
| 329 | + return fee.setScale(2, RoundingMode.HALF_UP); | |
| 330 | + } | |
| 331 | + | |
| 332 | + private BigDecimal applyMinFee(BigDecimal total, DeliveryPricingRuleDTO typeConfig, DeliveryFeeResultVO result) { | |
| 333 | + BigDecimal finalTotal = total.setScale(2, RoundingMode.HALF_UP); | |
| 334 | + BigDecimal minFee = nvl(typeConfig.getMinFee()).setScale(2, RoundingMode.HALF_UP); | |
| 335 | + result.setMinFee(minFee); | |
| 336 | + if (minFee.compareTo(BigDecimal.ZERO) > 0 && finalTotal.compareTo(minFee) < 0) { | |
| 337 | + result.setMinFeeApplied(1); | |
| 338 | + return minFee; | |
| 339 | + } | |
| 340 | + result.setMinFeeApplied(0); | |
| 341 | + return finalTotal; | |
| 342 | + } | |
| 343 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/DeliveryOrderServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/DeliveryOrderServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.entity.MerchantStore; | |
| 6 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 7 | +import com.diligrp.rider.common.exception.BizException; | |
| 8 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 9 | +import com.diligrp.rider.dto.DeliveryOrderCreateDTO; | |
| 10 | +import com.diligrp.rider.entity.OpenApp; | |
| 11 | +import com.diligrp.rider.entity.Orders; | |
| 12 | +import com.diligrp.rider.mapper.OpenAppMapper; | |
| 13 | +import com.diligrp.rider.mapper.OrdersMapper; | |
| 14 | +import com.diligrp.rider.service.DeliveryFeeService; | |
| 15 | +import com.diligrp.rider.service.DeliveryOrderService; | |
| 16 | +import com.diligrp.rider.service.MerchantService; | |
| 17 | +import com.diligrp.rider.service.WebhookService; | |
| 18 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 19 | +import com.diligrp.rider.vo.DeliveryOrderCreateVO; | |
| 20 | +import lombok.RequiredArgsConstructor; | |
| 21 | +import lombok.extern.slf4j.Slf4j; | |
| 22 | +import org.springframework.stereotype.Service; | |
| 23 | +import org.springframework.transaction.annotation.Transactional; | |
| 24 | + | |
| 25 | +import java.math.BigDecimal; | |
| 26 | +import java.time.LocalDate; | |
| 27 | +import java.time.format.DateTimeFormatter; | |
| 28 | +import java.util.List; | |
| 29 | +import java.util.HashMap; | |
| 30 | +import java.util.Map; | |
| 31 | +import java.util.UUID; | |
| 32 | + | |
| 33 | +@Slf4j | |
| 34 | +@Service | |
| 35 | +@RequiredArgsConstructor | |
| 36 | +public class DeliveryOrderServiceImpl implements DeliveryOrderService { | |
| 37 | + | |
| 38 | + private final OrdersMapper ordersMapper; | |
| 39 | + private final OpenAppMapper openAppMapper; | |
| 40 | + private final MerchantService merchantService; | |
| 41 | + private final DeliveryFeeService deliveryFeeService; | |
| 42 | + private final WebhookService webhookService; | |
| 43 | + private final ObjectMapper objectMapper; | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + @Transactional | |
| 47 | + public DeliveryOrderCreateVO create(String appKey, DeliveryOrderCreateDTO dto) { | |
| 48 | + // 1. 校验应用,从 AppKey 获取租户 cityId(不信任调用方传入的 cityId) | |
| 49 | + OpenApp app = getApp(appKey); | |
| 50 | + if (app.getCityId() == null || app.getCityId() < 1) { | |
| 51 | + throw new BizException("该应用未绑定城市/租户,请联系平台管理员"); | |
| 52 | + } | |
| 53 | + // 强制使用 AppKey 绑定的 cityId,覆盖调用方传入的值 | |
| 54 | + dto.setCityId(app.getCityId()); | |
| 55 | + | |
| 56 | + // 2. 检查外部订单号唯一性 | |
| 57 | + Long exists = ordersMapper.selectCount(new LambdaQueryWrapper<Orders>() | |
| 58 | + .eq(Orders::getAppKey, appKey) | |
| 59 | + .eq(Orders::getOutOrderNo, dto.getOutOrderNo())); | |
| 60 | + if (exists > 0) throw new BizException("外部订单号已存在:" + dto.getOutOrderNo()); | |
| 61 | + | |
| 62 | + // 3. 若传了 outStoreId,用 merchant_store 里的门店信息补全发货方 | |
| 63 | + if (dto.getOutStoreId() != null && !dto.getOutStoreId().isBlank()) { | |
| 64 | + MerchantStore ms = merchantService.getByOutStoreId(appKey, dto.getOutStoreId()); | |
| 65 | + if (ms != null) { | |
| 66 | + if (dto.getStoreName() == null || dto.getStoreName().isBlank()) | |
| 67 | + dto.setStoreName(ms.getName()); | |
| 68 | + if (dto.getStoreAddr() == null || dto.getStoreAddr().isBlank()) | |
| 69 | + dto.setStoreAddr(ms.getAddress()); | |
| 70 | + if (dto.getStoreLng() == null || dto.getStoreLng().isBlank()) | |
| 71 | + dto.setStoreLng(ms.getLng()); | |
| 72 | + if (dto.getStoreLat() == null || dto.getStoreLat().isBlank()) | |
| 73 | + dto.setStoreLat(ms.getLat()); | |
| 74 | + } | |
| 75 | + } | |
| 76 | + // 校验发货方坐标 | |
| 77 | + if (dto.getStoreLng() == null || dto.getStoreLng().isBlank() | |
| 78 | + || dto.getStoreLat() == null || dto.getStoreLat().isBlank()) { | |
| 79 | + throw new BizException("发货方经纬度不能为空(请传 storeLng/storeLat 或有效的 outStoreId)"); | |
| 80 | + } | |
| 81 | + | |
| 82 | + // 4. 计算配送费 | |
| 83 | + DeliveryFeeCalcDTO feeDTO = new DeliveryFeeCalcDTO(); | |
| 84 | + feeDTO.setCityId(dto.getCityId()); | |
| 85 | + feeDTO.setOrderType(6); // 外卖配送 | |
| 86 | + feeDTO.setStartLng(dto.getStoreLng()); | |
| 87 | + feeDTO.setStartLat(dto.getStoreLat()); | |
| 88 | + feeDTO.setEndLng(dto.getRecipLng()); | |
| 89 | + feeDTO.setEndLat(dto.getRecipLat()); | |
| 90 | + feeDTO.setWeight(dto.getWeight()); | |
| 91 | + feeDTO.setPieces(calcPieces(dto.getItems())); | |
| 92 | + feeDTO.setServiceTime(dto.getServiceTime()); | |
| 93 | + DeliveryFeeResultVO fee = deliveryFeeService.calcFee(feeDTO); | |
| 94 | + | |
| 95 | + // 4. 生成订单号 | |
| 96 | + String orderNo = generateOrderNo(); | |
| 97 | + // 5. 生成完成码(6位数字) | |
| 98 | + String code = String.valueOf((int) (Math.random() * 900000 + 100000)); | |
| 99 | + | |
| 100 | + // 6. 构建 extra 信息 | |
| 101 | + Map<String, Object> extra = new HashMap<>(); | |
| 102 | + extra.put("distance", fee.getDistance()); | |
| 103 | + extra.put("weight", dto.getWeight()); | |
| 104 | + extra.put("pieces", feeDTO.getPieces()); | |
| 105 | + extra.put("remark", dto.getRemark()); | |
| 106 | + extra.put("computed", Map.of( | |
| 107 | + "money_basic", fee.getMoneyBasic(), | |
| 108 | + "money_distance", fee.getMoneyDistance(), | |
| 109 | + "money_piece", fee.getMoneyPiece(), | |
| 110 | + "money_time", fee.getMoneyTime() | |
| 111 | + )); | |
| 112 | + String extraJson; | |
| 113 | + try { | |
| 114 | + extraJson = objectMapper.writeValueAsString(extra); | |
| 115 | + } catch (Exception e) { | |
| 116 | + extraJson = "{}"; | |
| 117 | + } | |
| 118 | + | |
| 119 | + // 7. 回调地址优先用订单级,其次用应用级 | |
| 120 | + String callbackUrl = dto.getCallbackUrl(); | |
| 121 | + if (callbackUrl == null || callbackUrl.isBlank()) { | |
| 122 | + callbackUrl = app.getWebhookUrl(); | |
| 123 | + } | |
| 124 | + | |
| 125 | + // 8. 创建订单 | |
| 126 | + Orders order = new Orders(); | |
| 127 | + order.setOrderNo(orderNo); | |
| 128 | + order.setOutOrderNo(dto.getOutOrderNo()); | |
| 129 | + order.setAppKey(appKey); | |
| 130 | + order.setCityId(dto.getCityId()); | |
| 131 | + order.setType(6); | |
| 132 | + order.setStatus(2); // 直接到待接单(外部系统已完成支付) | |
| 133 | + order.setMoneyDelivery(fee.getTotalFee()); | |
| 134 | + order.setMoney(fee.getTotalFee()); | |
| 135 | + order.setMoneyTotal(fee.getTotalFee()); | |
| 136 | + order.setFName(dto.getStoreName()); | |
| 137 | + order.setFAddr(dto.getStoreAddr() != null ? dto.getStoreAddr() : ""); | |
| 138 | + order.setFLng(dto.getStoreLng()); | |
| 139 | + order.setFLat(dto.getStoreLat()); | |
| 140 | + order.setTName(dto.getRecipAddr()); | |
| 141 | + order.setTAddr(dto.getRecipAddr()); | |
| 142 | + order.setTLng(dto.getRecipLng()); | |
| 143 | + order.setTLat(dto.getRecipLat()); | |
| 144 | + order.setRecipName(dto.getRecipName()); | |
| 145 | + order.setRecipPhone(dto.getRecipPhone()); | |
| 146 | + order.setCode(code); | |
| 147 | + order.setExtra(extraJson); | |
| 148 | + order.setCallbackUrl(callbackUrl); | |
| 149 | + order.setItemsJson(null); | |
| 150 | + // 货物快照 | |
| 151 | + if (dto.getItems() != null && !dto.getItems().isEmpty()) { | |
| 152 | + try { | |
| 153 | + order.setItemsJson(objectMapper.writeValueAsString(dto.getItems())); | |
| 154 | + } catch (Exception ignored) {} | |
| 155 | + } | |
| 156 | + order.setItemRemark(dto.getItemRemark()); | |
| 157 | + order.setExtStoreId(null); // 改为通过 outStoreId 关联,不存具体ID | |
| 158 | + order.setRiderId(0L); | |
| 159 | + order.setOldRiderId(0L); | |
| 160 | + order.setIsTrans(0); | |
| 161 | + order.setIsIncome(0); | |
| 162 | + order.setIsDel(0); | |
| 163 | + order.setAddTime(System.currentTimeMillis() / 1000); | |
| 164 | + order.setPayTime(System.currentTimeMillis() / 1000); | |
| 165 | + ordersMapper.insert(order); | |
| 166 | + | |
| 167 | + // 9. 回调接入方:订单已创建 | |
| 168 | + notifyCallback(order, "order.created"); | |
| 169 | + | |
| 170 | + return toCreateVO(order, fee); | |
| 171 | + } | |
| 172 | + | |
| 173 | + @Override | |
| 174 | + public DeliveryOrderCreateVO queryByOutOrderNo(String appKey, String outOrderNo) { | |
| 175 | + getApp(appKey); | |
| 176 | + Orders order = getOrderByOutNo(appKey, outOrderNo); | |
| 177 | + DeliveryFeeResultVO feeVO = new DeliveryFeeResultVO(); | |
| 178 | + feeVO.setMoneyBasic(order.getMoneyDelivery()); | |
| 179 | + feeVO.setMoneyDistance(BigDecimal.ZERO); | |
| 180 | + feeVO.setMoneyTime(BigDecimal.ZERO); | |
| 181 | + feeVO.setTotalFee(order.getMoneyDelivery()); | |
| 182 | + return toCreateVO(order, feeVO); | |
| 183 | + } | |
| 184 | + | |
| 185 | + @Override | |
| 186 | + @Transactional | |
| 187 | + public void cancel(String appKey, String outOrderNo) { | |
| 188 | + getApp(appKey); | |
| 189 | + Orders order = getOrderByOutNo(appKey, outOrderNo); | |
| 190 | + if (order.getStatus() != 2) { | |
| 191 | + throw new BizException("订单已接单或已完成,无法取消"); | |
| 192 | + } | |
| 193 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 194 | + .eq(Orders::getId, order.getId()) | |
| 195 | + .set(Orders::getStatus, 10)); | |
| 196 | + order.setStatus(10); | |
| 197 | + notifyCallback(order, "order.cancelled"); | |
| 198 | + } | |
| 199 | + | |
| 200 | + // ---- 私有方法 ---- | |
| 201 | + | |
| 202 | + private OpenApp getApp(String appKey) { | |
| 203 | + OpenApp app = openAppMapper.selectOne(new LambdaQueryWrapper<OpenApp>() | |
| 204 | + .eq(OpenApp::getAppKey, appKey).last("LIMIT 1")); | |
| 205 | + if (app == null || app.getStatus() != 1) throw new BizException("应用不存在或已禁用"); | |
| 206 | + return app; | |
| 207 | + } | |
| 208 | + | |
| 209 | + private Orders getOrderByOutNo(String appKey, String outOrderNo) { | |
| 210 | + Orders order = ordersMapper.selectOne(new LambdaQueryWrapper<Orders>() | |
| 211 | + .eq(Orders::getAppKey, appKey) | |
| 212 | + .eq(Orders::getOutOrderNo, outOrderNo) | |
| 213 | + .last("LIMIT 1")); | |
| 214 | + if (order == null) throw new BizException("订单不存在"); | |
| 215 | + return order; | |
| 216 | + } | |
| 217 | + | |
| 218 | + private void notifyCallback(Orders order, String event) { | |
| 219 | + try { | |
| 220 | + Map<String, Object> payload = new HashMap<>(); | |
| 221 | + payload.put("event", event); | |
| 222 | + payload.put("outOrderNo", order.getOutOrderNo()); | |
| 223 | + payload.put("deliveryOrderId", order.getId()); | |
| 224 | + payload.put("orderNo", order.getOrderNo()); | |
| 225 | + payload.put("status", order.getStatus()); | |
| 226 | + payload.put("timestamp", System.currentTimeMillis() / 1000); | |
| 227 | + String json = objectMapper.writeValueAsString(payload); | |
| 228 | + webhookService.send(event, order.getId(), json); | |
| 229 | + } catch (Exception e) { | |
| 230 | + log.warn("通知回调失败 orderId={}", order.getId(), e); | |
| 231 | + } | |
| 232 | + } | |
| 233 | + | |
| 234 | + private String generateOrderNo() { | |
| 235 | + String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); | |
| 236 | + return "DL" + date + UUID.randomUUID().toString().replace("-", "").substring(0, 10).toUpperCase(); | |
| 237 | + } | |
| 238 | + | |
| 239 | + private Integer calcPieces(List<DeliveryOrderCreateDTO.DeliveryItemDTO> items) { | |
| 240 | + if (items == null || items.isEmpty()) { | |
| 241 | + return 0; | |
| 242 | + } | |
| 243 | + int pieces = 0; | |
| 244 | + for (DeliveryOrderCreateDTO.DeliveryItemDTO item : items) { | |
| 245 | + pieces += item.getQuantity() != null && item.getQuantity() > 0 ? item.getQuantity() : 1; | |
| 246 | + } | |
| 247 | + return pieces; | |
| 248 | + } | |
| 249 | + | |
| 250 | + private DeliveryOrderCreateVO toCreateVO(Orders order, DeliveryFeeResultVO fee) { | |
| 251 | + DeliveryOrderCreateVO vo = new DeliveryOrderCreateVO(); | |
| 252 | + vo.setDeliveryOrderId(order.getId()); | |
| 253 | + vo.setOrderNo(order.getOrderNo()); | |
| 254 | + vo.setOutOrderNo(order.getOutOrderNo()); | |
| 255 | + vo.setMoneyBasic(fee.getMoneyBasic()); | |
| 256 | + vo.setMoneyDistance(fee.getMoneyDistance()); | |
| 257 | + vo.setMoneyTime(fee.getMoneyTime()); | |
| 258 | + vo.setTotalFee(fee.getTotalFee()); | |
| 259 | + vo.setDistance(fee.getDistance()); | |
| 260 | + vo.setEstimatedMinutes(fee.getEstimatedMinutes()); | |
| 261 | + vo.setStatus(order.getStatus()); | |
| 262 | + return vo; | |
| 263 | + } | |
| 264 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/ExtStoreServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/ExtStoreServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.entity.ExtStore; | |
| 7 | +import com.diligrp.rider.mapper.ExtStoreMapper; | |
| 8 | +import com.diligrp.rider.service.ExtStoreService; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import org.springframework.stereotype.Service; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +@Service | |
| 15 | +@RequiredArgsConstructor | |
| 16 | +public class ExtStoreServiceImpl implements ExtStoreService { | |
| 17 | + | |
| 18 | + private final ExtStoreMapper extStoreMapper; | |
| 19 | + | |
| 20 | + @Override | |
| 21 | + public ExtStore syncStore(String appKey, ExtStore store) { | |
| 22 | + store.setAppKey(appKey); | |
| 23 | + long now = System.currentTimeMillis() / 1000; | |
| 24 | + | |
| 25 | + // 根据 appKey + outStoreId 判断是新增还是更新 | |
| 26 | + ExtStore existing = extStoreMapper.selectOne(new LambdaQueryWrapper<ExtStore>() | |
| 27 | + .eq(ExtStore::getAppKey, appKey) | |
| 28 | + .eq(ExtStore::getOutStoreId, store.getOutStoreId()) | |
| 29 | + .last("LIMIT 1")); | |
| 30 | + | |
| 31 | + if (existing == null) { | |
| 32 | + store.setStatus(store.getStatus() != null ? store.getStatus() : 1); | |
| 33 | + store.setCreateTime(now); | |
| 34 | + store.setUpdateTime(now); | |
| 35 | + extStoreMapper.insert(store); | |
| 36 | + } else { | |
| 37 | + store.setId(existing.getId()); | |
| 38 | + store.setUpdateTime(now); | |
| 39 | + extStoreMapper.updateById(store); | |
| 40 | + } | |
| 41 | + return extStoreMapper.selectById(store.getId()); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + public List<ExtStore> listByApp(String appKey) { | |
| 46 | + return extStoreMapper.selectList(new LambdaQueryWrapper<ExtStore>() | |
| 47 | + .eq(ExtStore::getAppKey, appKey) | |
| 48 | + .eq(ExtStore::getStatus, 1) | |
| 49 | + .orderByDesc(ExtStore::getId)); | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Override | |
| 53 | + public ExtStore getById(Long id, String appKey) { | |
| 54 | + ExtStore store = extStoreMapper.selectById(id); | |
| 55 | + if (store == null) throw new BizException("门店不存在"); | |
| 56 | + if (appKey != null && !appKey.equals(store.getAppKey())) throw new BizException("无权访问该门店"); | |
| 57 | + return store; | |
| 58 | + } | |
| 59 | + | |
| 60 | + @Override | |
| 61 | + public void setStatus(Long id, String appKey, int status) { | |
| 62 | + ExtStore store = getById(id, appKey); | |
| 63 | + extStoreMapper.update(null, new LambdaUpdateWrapper<ExtStore>() | |
| 64 | + .eq(ExtStore::getId, id) | |
| 65 | + .set(ExtStore::getStatus, status) | |
| 66 | + .set(ExtStore::getUpdateTime, System.currentTimeMillis() / 1000)); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public void delete(Long id, String appKey) { | |
| 71 | + getById(id, appKey); | |
| 72 | + extStoreMapper.deleteById(id); | |
| 73 | + } | |
| 74 | + | |
| 75 | + // 平台管理端:不限 appKey 查看所有门店 | |
| 76 | + @Override | |
| 77 | + public List<ExtStore> listAll(String appKey, Long cityId, int page) { | |
| 78 | + LambdaQueryWrapper<ExtStore> wrapper = new LambdaQueryWrapper<ExtStore>() | |
| 79 | + .orderByDesc(ExtStore::getId); | |
| 80 | + if (appKey != null && !appKey.isBlank()) wrapper.eq(ExtStore::getAppKey, appKey); | |
| 81 | + if (cityId != null && cityId > 0) wrapper.eq(ExtStore::getCityId, cityId); | |
| 82 | + int offset = (page - 1) * 20; | |
| 83 | + wrapper.last("LIMIT " + offset + ",20"); | |
| 84 | + return extStoreMapper.selectList(wrapper); | |
| 85 | + } | |
| 86 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/MerchantServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/MerchantServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.dto.MerchantStoreDTO; | |
| 7 | +import com.diligrp.rider.entity.MerchantStore; | |
| 8 | +import com.diligrp.rider.entity.MerchantUsers; | |
| 9 | +import com.diligrp.rider.mapper.MerchantStoreMapper; | |
| 10 | +import com.diligrp.rider.mapper.MerchantUsersMapper; | |
| 11 | +import com.diligrp.rider.service.CityService; | |
| 12 | +import com.diligrp.rider.service.MerchantService; | |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 14 | +import org.springframework.stereotype.Service; | |
| 15 | +import org.springframework.transaction.annotation.Transactional; | |
| 16 | + | |
| 17 | +import java.math.BigDecimal; | |
| 18 | +import java.util.List; | |
| 19 | + | |
| 20 | +@Service | |
| 21 | +@RequiredArgsConstructor | |
| 22 | +public class MerchantServiceImpl implements MerchantService { | |
| 23 | + | |
| 24 | + private final MerchantStoreMapper storeMapper; | |
| 25 | + private final MerchantUsersMapper usersMapper; | |
| 26 | + private final CityService cityService; | |
| 27 | + | |
| 28 | + private static final int PAGE_SIZE = 20; | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + @Transactional | |
| 32 | + public Long addStore(MerchantStoreDTO dto) { | |
| 33 | + var city = cityService.getById(dto.getCityId()); | |
| 34 | + if (city == null) throw new BizException("城市不存在"); | |
| 35 | + | |
| 36 | + MerchantStore store = toEntity(dto); | |
| 37 | + store.setAddTime(System.currentTimeMillis() / 1000); | |
| 38 | + store.setIsDel(0); | |
| 39 | + storeMapper.insert(store); | |
| 40 | + | |
| 41 | + if (dto.getAccountMobile() != null && !dto.getAccountMobile().isBlank()) { | |
| 42 | + Long exists = usersMapper.selectCount(new LambdaQueryWrapper<MerchantUsers>() | |
| 43 | + .eq(MerchantUsers::getMobile, dto.getAccountMobile())); | |
| 44 | + if (exists > 0) throw new BizException("该手机号已有账号"); | |
| 45 | + MerchantUsers user = new MerchantUsers(); | |
| 46 | + user.setStoreId(store.getId()); | |
| 47 | + user.setMobile(dto.getAccountMobile()); | |
| 48 | + user.setUserNickname(dto.getName()); | |
| 49 | + user.setUserStatus(1); | |
| 50 | + user.setType(1); | |
| 51 | + user.setCreateTime(System.currentTimeMillis() / 1000); | |
| 52 | + usersMapper.insert(user); | |
| 53 | + } | |
| 54 | + return store.getId(); | |
| 55 | + } | |
| 56 | + | |
| 57 | + @Override | |
| 58 | + public void editStore(MerchantStoreDTO dto) { | |
| 59 | + MerchantStore existing = storeMapper.selectById(dto.getId()); | |
| 60 | + if (existing == null) throw new BizException("店铺不存在"); | |
| 61 | + MerchantStore store = toEntity(dto); | |
| 62 | + store.setId(dto.getId()); | |
| 63 | + storeMapper.updateById(store); | |
| 64 | + } | |
| 65 | + | |
| 66 | + @Override | |
| 67 | + public List<MerchantStore> storeList(Long cityId, String keyword, int page) { | |
| 68 | + LambdaQueryWrapper<MerchantStore> wrapper = new LambdaQueryWrapper<MerchantStore>() | |
| 69 | + .eq(MerchantStore::getIsDel, 0) | |
| 70 | + .orderByAsc(MerchantStore::getListOrder) | |
| 71 | + .orderByDesc(MerchantStore::getId); | |
| 72 | + if (cityId != null && cityId > 0) wrapper.eq(MerchantStore::getCityId, cityId); | |
| 73 | + if (keyword != null && !keyword.isBlank()) wrapper.like(MerchantStore::getName, keyword); | |
| 74 | + int offset = (page - 1) * PAGE_SIZE; | |
| 75 | + wrapper.last("LIMIT " + offset + "," + PAGE_SIZE); | |
| 76 | + return storeMapper.selectList(wrapper); | |
| 77 | + } | |
| 78 | + | |
| 79 | + @Override | |
| 80 | + public MerchantStore getStore(Long storeId) { | |
| 81 | + return storeMapper.selectById(storeId); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + public void setOperatingState(Long storeId, int state) { | |
| 86 | + storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() | |
| 87 | + .eq(MerchantStore::getId, storeId) | |
| 88 | + .set(MerchantStore::getOperatingState, state)); | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Override | |
| 92 | + public void setAutoOrder(Long storeId, int auto) { | |
| 93 | + storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() | |
| 94 | + .eq(MerchantStore::getId, storeId) | |
| 95 | + .set(MerchantStore::getAutomaticOrder, auto)); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void updateFeeConfig(Long storeId, BigDecimal freeShipping, BigDecimal upToSend) { | |
| 100 | + storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() | |
| 101 | + .eq(MerchantStore::getId, storeId) | |
| 102 | + .set(MerchantStore::getFreeShipping, freeShipping) | |
| 103 | + .set(MerchantStore::getUpToSend, upToSend)); | |
| 104 | + } | |
| 105 | + | |
| 106 | + @Override | |
| 107 | + public void delStore(Long storeId) { | |
| 108 | + storeMapper.update(null, new LambdaUpdateWrapper<MerchantStore>() | |
| 109 | + .eq(MerchantStore::getId, storeId) | |
| 110 | + .set(MerchantStore::getIsDel, 1)); | |
| 111 | + } | |
| 112 | + | |
| 113 | + @Override | |
| 114 | + @Transactional | |
| 115 | + public MerchantStore syncStore(String appKey, MerchantStoreDTO dto) { | |
| 116 | + if (appKey == null || appKey.isBlank()) throw new BizException("AppKey不能为空"); | |
| 117 | + if (dto.getOutStoreId() == null || dto.getOutStoreId().isBlank()) throw new BizException("外部门店编号不能为空"); | |
| 118 | + | |
| 119 | + long now = System.currentTimeMillis() / 1000; | |
| 120 | + MerchantStore existing = getByOutStoreId(appKey, dto.getOutStoreId()); | |
| 121 | + | |
| 122 | + if (existing == null) { | |
| 123 | + MerchantStore store = toEntity(dto); | |
| 124 | + store.setAppKey(appKey); | |
| 125 | + store.setAddTime(now); | |
| 126 | + store.setIsDel(0); | |
| 127 | + storeMapper.insert(store); | |
| 128 | + return store; | |
| 129 | + } else { | |
| 130 | + MerchantStore store = toEntity(dto); | |
| 131 | + store.setId(existing.getId()); | |
| 132 | + store.setAppKey(appKey); | |
| 133 | + storeMapper.updateById(store); | |
| 134 | + return storeMapper.selectById(existing.getId()); | |
| 135 | + } | |
| 136 | + } | |
| 137 | + | |
| 138 | + @Override | |
| 139 | + public MerchantStore getByOutStoreId(String appKey, String outStoreId) { | |
| 140 | + return storeMapper.selectOne(new LambdaQueryWrapper<MerchantStore>() | |
| 141 | + .eq(MerchantStore::getAppKey, appKey) | |
| 142 | + .eq(MerchantStore::getOutStoreId, outStoreId) | |
| 143 | + .eq(MerchantStore::getIsDel, 0) | |
| 144 | + .last("LIMIT 1")); | |
| 145 | + } | |
| 146 | + | |
| 147 | + private MerchantStore toEntity(MerchantStoreDTO dto) { | |
| 148 | + MerchantStore store = new MerchantStore(); | |
| 149 | + store.setName(dto.getName()); | |
| 150 | + store.setCityId(dto.getCityId()); | |
| 151 | + store.setThumb(dto.getThumb()); | |
| 152 | + store.setAddress(dto.getAddress()); | |
| 153 | + store.setLng(dto.getLng()); | |
| 154 | + store.setLat(dto.getLat()); | |
| 155 | + store.setOperatingState(dto.getOperatingState() != null ? dto.getOperatingState() : 1); | |
| 156 | + store.setAutomaticOrder(dto.getAutomaticOrder() != null ? dto.getAutomaticOrder() : 0); | |
| 157 | + store.setShippingType(dto.getShippingType() != null ? dto.getShippingType() : 1); | |
| 158 | + store.setFreeShipping(dto.getFreeShipping() != null ? dto.getFreeShipping() : BigDecimal.ZERO); | |
| 159 | + store.setUpToSend(dto.getUpToSend() != null ? dto.getUpToSend() : BigDecimal.ZERO); | |
| 160 | + store.setOpenDate(dto.getOpenDate()); | |
| 161 | + store.setOpenTime(dto.getOpenTime()); | |
| 162 | + store.setAbout(dto.getAbout()); | |
| 163 | + store.setOutStoreId(dto.getOutStoreId()); | |
| 164 | + return store; | |
| 165 | + } | |
| 166 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/OpenAppServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/OpenAppServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.entity.OpenApp; | |
| 7 | +import com.diligrp.rider.mapper.OpenAppMapper; | |
| 8 | +import com.diligrp.rider.service.OpenAppService; | |
| 9 | +import com.diligrp.rider.util.SignUtil; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import org.springframework.stereotype.Service; | |
| 12 | + | |
| 13 | +import java.util.List; | |
| 14 | + | |
| 15 | +@Service | |
| 16 | +@RequiredArgsConstructor | |
| 17 | +public class OpenAppServiceImpl implements OpenAppService { | |
| 18 | + | |
| 19 | + private final OpenAppMapper openAppMapper; | |
| 20 | + | |
| 21 | + @Override | |
| 22 | + public OpenApp create(String appName, Long cityId, Long storeId, String webhookUrl, String webhookEvents, String remark) { | |
| 23 | + if (cityId == null || cityId < 1) throw new BizException("请选择关联城市/租户"); | |
| 24 | + OpenApp app = new OpenApp(); | |
| 25 | + app.setAppName(appName); | |
| 26 | + app.setAppKey(SignUtil.generateAppKey()); | |
| 27 | + app.setAppSecret(SignUtil.generateAppSecret()); | |
| 28 | + app.setCityId(cityId); | |
| 29 | + app.setStoreId(storeId != null ? storeId : 0L); | |
| 30 | + app.setStatus(1); | |
| 31 | + app.setWebhookUrl(webhookUrl); | |
| 32 | + app.setWebhookEvents(webhookEvents); | |
| 33 | + app.setRemark(remark); | |
| 34 | + app.setCreateTime(System.currentTimeMillis() / 1000); | |
| 35 | + openAppMapper.insert(app); | |
| 36 | + return app; | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public List<OpenApp> list(int page) { | |
| 41 | + int offset = (page - 1) * 20; | |
| 42 | + return openAppMapper.selectList(new LambdaQueryWrapper<OpenApp>() | |
| 43 | + .orderByDesc(OpenApp::getId) | |
| 44 | + .last("LIMIT " + offset + ",20")); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public String resetSecret(Long appId) { | |
| 49 | + OpenApp app = openAppMapper.selectById(appId); | |
| 50 | + if (app == null) throw new BizException("应用不存在"); | |
| 51 | + String newSecret = SignUtil.generateAppSecret(); | |
| 52 | + openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>() | |
| 53 | + .eq(OpenApp::getId, appId) | |
| 54 | + .set(OpenApp::getAppSecret, newSecret)); | |
| 55 | + return newSecret; | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Override | |
| 59 | + public void setStatus(Long appId, int status) { | |
| 60 | + openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>() | |
| 61 | + .eq(OpenApp::getId, appId) | |
| 62 | + .set(OpenApp::getStatus, status)); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Override | |
| 66 | + public void updateWebhook(Long appId, String webhookUrl, String webhookEvents) { | |
| 67 | + openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>() | |
| 68 | + .eq(OpenApp::getId, appId) | |
| 69 | + .set(OpenApp::getWebhookUrl, webhookUrl) | |
| 70 | + .set(OpenApp::getWebhookEvents, webhookEvents)); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public OpenApp getByAppKey(String appKey) { | |
| 75 | + return openAppMapper.selectOne(new LambdaQueryWrapper<OpenApp>() | |
| 76 | + .eq(OpenApp::getAppKey, appKey) | |
| 77 | + .last("LIMIT 1")); | |
| 78 | + } | |
| 79 | + | |
| 80 | + @Override | |
| 81 | + public boolean verifySign(String appKey, String timestamp, String nonce, String sign) { | |
| 82 | + OpenApp app = getByAppKey(appKey); | |
| 83 | + if (app == null || app.getStatus() != 1) return false; | |
| 84 | + return SignUtil.verify(appKey, timestamp, nonce, sign, app.getAppSecret()); | |
| 85 | + } | |
| 86 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RefundServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RefundServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.entity.OrderRefundReason; | |
| 7 | +import com.diligrp.rider.entity.OrderRefundRecord; | |
| 8 | +import com.diligrp.rider.entity.Orders; | |
| 9 | +import com.diligrp.rider.mapper.OrderRefundReasonMapper; | |
| 10 | +import com.diligrp.rider.mapper.OrderRefundRecordMapper; | |
| 11 | +import com.diligrp.rider.mapper.OrdersMapper; | |
| 12 | +import com.diligrp.rider.entity.*; | |
| 13 | +import com.diligrp.rider.mapper.*; | |
| 14 | +import com.diligrp.rider.service.RefundService; | |
| 15 | +import com.diligrp.rider.service.WebhookService; | |
| 16 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 17 | +import lombok.RequiredArgsConstructor; | |
| 18 | +import lombok.extern.slf4j.Slf4j; | |
| 19 | +import org.springframework.stereotype.Service; | |
| 20 | +import org.springframework.transaction.annotation.Transactional; | |
| 21 | + | |
| 22 | +import java.util.HashMap; | |
| 23 | +import java.util.List; | |
| 24 | +import java.util.Map; | |
| 25 | + | |
| 26 | +@Slf4j | |
| 27 | +@Service | |
| 28 | +@RequiredArgsConstructor | |
| 29 | +public class RefundServiceImpl implements RefundService { | |
| 30 | + | |
| 31 | + private final OrderRefundReasonMapper reasonMapper; | |
| 32 | + private final OrderRefundRecordMapper recordMapper; | |
| 33 | + private final OrdersMapper ordersMapper; | |
| 34 | + private final WebhookService webhookService; | |
| 35 | + private final ObjectMapper objectMapper; | |
| 36 | + | |
| 37 | + @Override | |
| 38 | + public List<OrderRefundReason> getReasons(int role) { | |
| 39 | + return reasonMapper.selectList(new LambdaQueryWrapper<OrderRefundReason>() | |
| 40 | + .eq(role > 0, OrderRefundReason::getRole, role) | |
| 41 | + .orderByAsc(OrderRefundReason::getListOrder)); | |
| 42 | + } | |
| 43 | + | |
| 44 | + @Override | |
| 45 | + @Transactional | |
| 46 | + public void applyRefund(Long orderId, Long uid, int role, Long reasonId, String reason) { | |
| 47 | + Orders order = ordersMapper.selectById(orderId); | |
| 48 | + if (order == null) throw new BizException("订单不存在"); | |
| 49 | + | |
| 50 | + // 用户申请:必须是自己的订单且已完成或服务中 | |
| 51 | + if (role == 1) { | |
| 52 | + if (!uid.equals(order.getUid())) throw new BizException("订单信息错误"); | |
| 53 | + if (order.getStatus() < 3 || order.getStatus() == 10) throw new BizException("当前订单状态不可申请退款"); | |
| 54 | + } | |
| 55 | + // 骑手申请:必须是自己接的单 | |
| 56 | + if (role == 2) { | |
| 57 | + if (!uid.equals(order.getRiderId())) throw new BizException("订单信息错误"); | |
| 58 | + if (order.getStatus() != 3 && order.getStatus() != 4) throw new BizException("当前订单状态不可申请退款"); | |
| 59 | + } | |
| 60 | + | |
| 61 | + // 检查是否已有退款申请 | |
| 62 | + Long exists = recordMapper.selectCount(new LambdaQueryWrapper<OrderRefundRecord>() | |
| 63 | + .eq(OrderRefundRecord::getOid, orderId) | |
| 64 | + .ne(OrderRefundRecord::getStatus, 2)); | |
| 65 | + if (exists > 0) throw new BizException("该订单已有退款申请,请勿重复提交"); | |
| 66 | + | |
| 67 | + // 更新订单状态为退款申请中 | |
| 68 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 69 | + .eq(Orders::getId, orderId) | |
| 70 | + .set(Orders::getStatus, 7)); | |
| 71 | + | |
| 72 | + // 写退款记录 | |
| 73 | + OrderRefundRecord record = new OrderRefundRecord(); | |
| 74 | + record.setOid(orderId); | |
| 75 | + record.setOrderNo(order.getOrderNo()); | |
| 76 | + record.setUid(uid); | |
| 77 | + record.setRole(role); | |
| 78 | + record.setReasonId(reasonId); | |
| 79 | + record.setReason(reason); | |
| 80 | + record.setMoney(order.getMoneyDelivery()); | |
| 81 | + record.setStatus(0); | |
| 82 | + record.setAddTime(System.currentTimeMillis() / 1000); | |
| 83 | + recordMapper.insert(record); | |
| 84 | + | |
| 85 | + // 通知接入方 | |
| 86 | + notifyRefundEvent(order, "order.refund_apply"); | |
| 87 | + } | |
| 88 | + | |
| 89 | + @Override | |
| 90 | + @Transactional | |
| 91 | + public void handleRefund(Long recordId, int status, String remark) { | |
| 92 | + OrderRefundRecord record = recordMapper.selectById(recordId); | |
| 93 | + if (record == null) throw new BizException("退款记录不存在"); | |
| 94 | + if (record.getStatus() != 0) throw new BizException("该退款申请已处理"); | |
| 95 | + | |
| 96 | + long now = System.currentTimeMillis() / 1000; | |
| 97 | + recordMapper.update(null, new LambdaUpdateWrapper<OrderRefundRecord>() | |
| 98 | + .eq(OrderRefundRecord::getId, recordId) | |
| 99 | + .set(OrderRefundRecord::getStatus, status) | |
| 100 | + .set(OrderRefundRecord::getRemark, remark) | |
| 101 | + .set(OrderRefundRecord::getHandleTime, now)); | |
| 102 | + | |
| 103 | + Orders order = ordersMapper.selectById(record.getOid()); | |
| 104 | + if (order == null) return; | |
| 105 | + | |
| 106 | + if (status == 1) { | |
| 107 | + // 退款通过 → 订单状态改为退款成功 | |
| 108 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 109 | + .eq(Orders::getId, record.getOid()) | |
| 110 | + .set(Orders::getStatus, 8)); | |
| 111 | + notifyRefundEvent(order, "order.refund_success"); | |
| 112 | + } else { | |
| 113 | + // 退款拒绝 → 订单状态改为退款拒绝 | |
| 114 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 115 | + .eq(Orders::getId, record.getOid()) | |
| 116 | + .set(Orders::getStatus, 9)); | |
| 117 | + notifyRefundEvent(order, "order.refund_reject"); | |
| 118 | + } | |
| 119 | + } | |
| 120 | + | |
| 121 | + @Override | |
| 122 | + public OrderRefundRecord getByOrderId(Long orderId) { | |
| 123 | + return recordMapper.selectOne(new LambdaQueryWrapper<OrderRefundRecord>() | |
| 124 | + .eq(OrderRefundRecord::getOid, orderId) | |
| 125 | + .orderByDesc(OrderRefundRecord::getId) | |
| 126 | + .last("LIMIT 1")); | |
| 127 | + } | |
| 128 | + | |
| 129 | + private void notifyRefundEvent(Orders order, String event) { | |
| 130 | + try { | |
| 131 | + if (order.getAppKey() == null || order.getAppKey().isBlank()) return; | |
| 132 | + Map<String, Object> payload = new HashMap<>(); | |
| 133 | + payload.put("event", event); | |
| 134 | + payload.put("outOrderNo", order.getOutOrderNo()); | |
| 135 | + payload.put("deliveryOrderId", order.getId()); | |
| 136 | + payload.put("orderNo", order.getOrderNo()); | |
| 137 | + payload.put("status", order.getStatus()); | |
| 138 | + payload.put("timestamp", System.currentTimeMillis() / 1000); | |
| 139 | + webhookService.send(event, order.getId(), objectMapper.writeValueAsString(payload)); | |
| 140 | + } catch (Exception e) { | |
| 141 | + log.warn("退款事件通知失败 orderId={}", order.getId(), e); | |
| 142 | + } | |
| 143 | + } | |
| 144 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RiderAuthServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RiderAuthServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.dto.ApplyDTO; | |
| 7 | +import com.diligrp.rider.dto.LoginDTO; | |
| 8 | +import com.diligrp.rider.entity.Rider; | |
| 9 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 10 | +import com.diligrp.rider.service.RiderAuthService; | |
| 11 | +import com.diligrp.rider.config.JwtUtil; | |
| 12 | +import com.diligrp.rider.vo.RiderVO; | |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 14 | +import org.springframework.data.redis.core.StringRedisTemplate; | |
| 15 | +import org.springframework.stereotype.Service; | |
| 16 | +import org.springframework.util.DigestUtils; | |
| 17 | + | |
| 18 | +import java.nio.charset.StandardCharsets; | |
| 19 | + | |
| 20 | +@Service | |
| 21 | +@RequiredArgsConstructor | |
| 22 | +public class RiderAuthServiceImpl implements RiderAuthService { | |
| 23 | + | |
| 24 | + private final RiderMapper riderMapper; | |
| 25 | + private final JwtUtil jwtUtil; | |
| 26 | + private final StringRedisTemplate redisTemplate; | |
| 27 | + | |
| 28 | + private static final String SMS_KEY_PREFIX = "rider_sms_apply_"; | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public void apply(ApplyDTO dto) { | |
| 32 | + // 校验验证码 | |
| 33 | + String cacheCode = redisTemplate.opsForValue().get(SMS_KEY_PREFIX + dto.getMobile()); | |
| 34 | + if (cacheCode == null || !cacheCode.equals(dto.getCode())) { | |
| 35 | + throw new BizException("验证码错误或已过期"); | |
| 36 | + } | |
| 37 | + | |
| 38 | + // 手机号唯一校验 | |
| 39 | + Long exists = riderMapper.selectCount(new LambdaQueryWrapper<Rider>() | |
| 40 | + .eq(Rider::getMobile, dto.getMobile())); | |
| 41 | + if (exists > 0) { | |
| 42 | + throw new BizException("该手机号已申请,请更换"); | |
| 43 | + } | |
| 44 | + | |
| 45 | + Rider rider = new Rider(); | |
| 46 | + rider.setMobile(dto.getMobile()); | |
| 47 | + rider.setUserNickname(dto.getName()); | |
| 48 | + rider.setUserPass(encryptPass(dto.getPass())); | |
| 49 | + rider.setIdNo(dto.getIdNo()); | |
| 50 | + rider.setThumb(dto.getThumb()); | |
| 51 | + rider.setCityId(dto.getCityId()); | |
| 52 | + rider.setUserStatus(2); // 待审核 | |
| 53 | + rider.setType(1); // 默认兼职 | |
| 54 | + rider.setIsRest(0); | |
| 55 | + rider.setUserLogin("phone_" + System.currentTimeMillis()); | |
| 56 | + rider.setCreateTime(System.currentTimeMillis() / 1000); | |
| 57 | + riderMapper.insert(rider); | |
| 58 | + | |
| 59 | + // 删除验证码 | |
| 60 | + redisTemplate.delete(SMS_KEY_PREFIX + dto.getMobile()); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public RiderVO loginByPass(LoginDTO dto) { | |
| 65 | + Rider rider = riderMapper.selectOne(new LambdaQueryWrapper<Rider>() | |
| 66 | + .eq(Rider::getMobile, dto.getUsername())); | |
| 67 | + if (rider == null) { | |
| 68 | + throw new BizException("手机号或密码错误"); | |
| 69 | + } | |
| 70 | + if (!encryptPass(dto.getPass()).equals(rider.getUserPass())) { | |
| 71 | + throw new BizException("手机号或密码错误"); | |
| 72 | + } | |
| 73 | + if (rider.getUserStatus() == 2) { | |
| 74 | + throw new BizException("账号审核中,请耐心等待"); | |
| 75 | + } | |
| 76 | + if (rider.getUserStatus() == 0) { | |
| 77 | + throw new BizException("账号审核未通过,请联系客服"); | |
| 78 | + } | |
| 79 | + if (rider.getStatus() != null && rider.getStatus() == 0) { | |
| 80 | + throw new BizException("账号已被禁用"); | |
| 81 | + } | |
| 82 | + return buildVO(rider); | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + public RiderVO getInfo(Long riderId) { | |
| 87 | + Rider rider = riderMapper.selectById(riderId); | |
| 88 | + if (rider == null) throw new BizException("骑手信息不存在"); | |
| 89 | + return buildVO(rider); | |
| 90 | + } | |
| 91 | + | |
| 92 | + @Override | |
| 93 | + public void toggleRest(Long riderId) { | |
| 94 | + Rider rider = riderMapper.selectById(riderId); | |
| 95 | + if (rider == null) throw new BizException("骑手信息不存在"); | |
| 96 | + int newRest = rider.getIsRest() == 1 ? 0 : 1; | |
| 97 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | |
| 98 | + .eq(Rider::getId, riderId) | |
| 99 | + .set(Rider::getIsRest, newRest)); | |
| 100 | + } | |
| 101 | + | |
| 102 | + private RiderVO buildVO(Rider rider) { | |
| 103 | + RiderVO vo = new RiderVO(); | |
| 104 | + vo.setId(rider.getId()); | |
| 105 | + vo.setMobile(rider.getMobile()); | |
| 106 | + vo.setUserNickname(rider.getUserNickname()); | |
| 107 | + vo.setAvatar(rider.getAvatar()); | |
| 108 | + vo.setType(rider.getType()); | |
| 109 | + vo.setTypeName(rider.getType() == 1 ? "兼职" : "全职"); | |
| 110 | + vo.setUserStatus(rider.getUserStatus()); | |
| 111 | + vo.setStatus(rider.getStatus() == null ? 1 : rider.getStatus()); | |
| 112 | + vo.setStatusName((rider.getStatus() == null || rider.getStatus() == 1) ? "正常" : "禁用"); | |
| 113 | + vo.setBalance(rider.getBalance()); | |
| 114 | + vo.setIsRest(rider.getIsRest()); | |
| 115 | + vo.setCityId(rider.getCityId()); | |
| 116 | + vo.setToken(jwtUtil.generateRiderToken(rider.getId(), rider.getCityId())); | |
| 117 | + return vo; | |
| 118 | + } | |
| 119 | + | |
| 120 | + private String encryptPass(String pass) { | |
| 121 | + return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8)); | |
| 122 | + } | |
| 123 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RiderBalanceServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RiderBalanceServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.diligrp.rider.common.exception.BizException; | |
| 5 | +import com.diligrp.rider.entity.Rider; | |
| 6 | +import com.diligrp.rider.entity.RiderBalance; | |
| 7 | +import com.diligrp.rider.mapper.RiderBalanceMapper; | |
| 8 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 9 | +import com.diligrp.rider.service.RiderBalanceService; | |
| 10 | +import com.diligrp.rider.vo.BalanceVO; | |
| 11 | +import lombok.RequiredArgsConstructor; | |
| 12 | +import org.springframework.stereotype.Service; | |
| 13 | + | |
| 14 | +import java.time.Instant; | |
| 15 | +import java.time.ZoneId; | |
| 16 | +import java.time.format.DateTimeFormatter; | |
| 17 | +import java.util.ArrayList; | |
| 18 | +import java.util.List; | |
| 19 | + | |
| 20 | +@Service | |
| 21 | +@RequiredArgsConstructor | |
| 22 | +public class RiderBalanceServiceImpl implements RiderBalanceService { | |
| 23 | + | |
| 24 | + private final RiderMapper riderMapper; | |
| 25 | + private final RiderBalanceMapper balanceMapper; | |
| 26 | + | |
| 27 | + private static final int PAGE_SIZE = 20; | |
| 28 | + private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") | |
| 29 | + .withZone(ZoneId.of("Asia/Shanghai")); | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + public BalanceVO getBalance(Long riderId, int page) { | |
| 33 | + Rider rider = riderMapper.selectById(riderId); | |
| 34 | + if (rider == null) throw new BizException("骑手信息不存在"); | |
| 35 | + | |
| 36 | + int offset = (page - 1) * PAGE_SIZE; | |
| 37 | + List<RiderBalance> records = balanceMapper.selectList( | |
| 38 | + new LambdaQueryWrapper<RiderBalance>() | |
| 39 | + .eq(RiderBalance::getUid, riderId) | |
| 40 | + .orderByDesc(RiderBalance::getId) | |
| 41 | + .last("LIMIT " + offset + "," + PAGE_SIZE)); | |
| 42 | + | |
| 43 | + List<BalanceVO.BalanceRecordVO> recordVOs = new ArrayList<>(); | |
| 44 | + for (RiderBalance r : records) { | |
| 45 | + BalanceVO.BalanceRecordVO vo = new BalanceVO.BalanceRecordVO(); | |
| 46 | + vo.setId(r.getId()); | |
| 47 | + vo.setType(r.getType()); | |
| 48 | + vo.setTypeName(r.getType() == 1 ? "收入" : "提现"); | |
| 49 | + vo.setAction(r.getAction()); | |
| 50 | + vo.setOrderNo(r.getOrderNo()); | |
| 51 | + vo.setNums(r.getNums()); | |
| 52 | + vo.setTotal(r.getTotal()); | |
| 53 | + vo.setAddTime(r.getAddTime() != null ? FMT.format(Instant.ofEpochSecond(r.getAddTime())) : ""); | |
| 54 | + recordVOs.add(vo); | |
| 55 | + } | |
| 56 | + | |
| 57 | + BalanceVO vo = new BalanceVO(); | |
| 58 | + vo.setBalance(rider.getBalance()); | |
| 59 | + vo.setRecords(recordVOs); | |
| 60 | + return vo; | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public java.math.BigDecimal getTodayIncome(Long riderId) { | |
| 65 | + // Balance.getToday():type=1(收入) action=order_complete 当日流水总和 | |
| 66 | + java.time.LocalDate today = java.time.LocalDate.now(); | |
| 67 | + long todayStart = today.atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond(); | |
| 68 | + long todayEnd = todayStart + 86400; | |
| 69 | + | |
| 70 | + java.util.List<RiderBalance> records = balanceMapper.selectList( | |
| 71 | + new LambdaQueryWrapper<RiderBalance>() | |
| 72 | + .eq(RiderBalance::getUid, riderId) | |
| 73 | + .eq(RiderBalance::getType, 1) // 收入 | |
| 74 | + .ge(RiderBalance::getAddTime, todayStart) | |
| 75 | + .lt(RiderBalance::getAddTime, todayEnd)); | |
| 76 | + | |
| 77 | + return records.stream() | |
| 78 | + .map(RiderBalance::getNums) | |
| 79 | + .filter(n -> n != null) | |
| 80 | + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add); | |
| 81 | + } | |
| 82 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RiderEvaluateServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RiderEvaluateServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.entity.Orders; | |
| 7 | +import com.diligrp.rider.entity.RiderEvaluate; | |
| 8 | +import com.diligrp.rider.entity.Rider; | |
| 9 | +import com.diligrp.rider.mapper.OrdersMapper; | |
| 10 | +import com.diligrp.rider.mapper.RiderEvaluateMapper; | |
| 11 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 12 | +import com.diligrp.rider.service.RiderEvaluateService; | |
| 13 | +import lombok.RequiredArgsConstructor; | |
| 14 | +import org.springframework.stereotype.Service; | |
| 15 | +import org.springframework.transaction.annotation.Transactional; | |
| 16 | + | |
| 17 | +import java.time.YearMonth; | |
| 18 | +import java.time.ZoneId; | |
| 19 | +import java.util.List; | |
| 20 | + | |
| 21 | +@Service | |
| 22 | +@RequiredArgsConstructor | |
| 23 | +public class RiderEvaluateServiceImpl implements RiderEvaluateService { | |
| 24 | + | |
| 25 | + private final RiderEvaluateMapper evaluateMapper; | |
| 26 | + private final OrdersMapper ordersMapper; | |
| 27 | + private final RiderMapper riderMapper; | |
| 28 | + | |
| 29 | + private static final int PAGE_SIZE = 20; | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + @Transactional | |
| 33 | + public void evaluate(Long uid, Long orderId, int star, String content) { | |
| 34 | + if (star < 1 || star > 5) throw new BizException("请选择1-5星评分"); | |
| 35 | + | |
| 36 | + Orders order = ordersMapper.selectById(orderId); | |
| 37 | + if (order == null) throw new BizException("订单不存在"); | |
| 38 | + if (!uid.equals(order.getUid())) throw new BizException("订单信息错误"); | |
| 39 | + if (order.getStatus() != 6) throw new BizException("订单未完成,无法评价"); | |
| 40 | + | |
| 41 | + // 检查是否已评价 | |
| 42 | + Long exists = evaluateMapper.selectCount(new LambdaQueryWrapper<RiderEvaluate>() | |
| 43 | + .eq(RiderEvaluate::getUid, uid) | |
| 44 | + .eq(RiderEvaluate::getOid, orderId)); | |
| 45 | + if (exists > 0) throw new BizException("订单已评价,无法再次评价"); | |
| 46 | + | |
| 47 | + // 写评价 | |
| 48 | + RiderEvaluate eval = new RiderEvaluate(); | |
| 49 | + eval.setUid(uid); | |
| 50 | + eval.setOid(orderId); | |
| 51 | + eval.setRid(order.getRiderId()); | |
| 52 | + eval.setContent(content); | |
| 53 | + eval.setStar(star); | |
| 54 | + eval.setCityId(order.getCityId()); | |
| 55 | + eval.setAddTime(System.currentTimeMillis() / 1000); | |
| 56 | + evaluateMapper.insert(eval); | |
| 57 | + | |
| 58 | + // 更新骑手星级(累加平均分) | |
| 59 | + Rider rider = riderMapper.selectById(order.getRiderId()); | |
| 60 | + if (rider != null) { | |
| 61 | + // 简单累加到 extra 统计,实际可扩展 star_total/star_count 字段 | |
| 62 | + // 此处直接用 SQL 累加到骑手备注字段,生产建议加 star_total/count 字段 | |
| 63 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | |
| 64 | + .eq(Rider::getId, order.getRiderId()) | |
| 65 | + .setSql("star_total = IFNULL(star_total,0) + " + star | |
| 66 | + + ", star_count = IFNULL(star_count,0) + 1")); | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + @Override | |
| 71 | + public List<RiderEvaluate> getRiderEvaluates(Long riderId, Integer type, int page) { | |
| 72 | + LambdaQueryWrapper<RiderEvaluate> wrapper = new LambdaQueryWrapper<RiderEvaluate>() | |
| 73 | + .eq(RiderEvaluate::getRid, riderId) | |
| 74 | + .orderByDesc(RiderEvaluate::getId); | |
| 75 | + if (type == 1) wrapper.ge(RiderEvaluate::getStar, 4); // 好评 | |
| 76 | + if (type == 2) wrapper.gt(RiderEvaluate::getStar, 2).lt(RiderEvaluate::getStar, 4); // 中评 | |
| 77 | + if (type == 3) wrapper.le(RiderEvaluate::getStar, 2); // 差评 | |
| 78 | + int offset = (page - 1) * PAGE_SIZE; | |
| 79 | + wrapper.last("LIMIT " + offset + "," + PAGE_SIZE); | |
| 80 | + return evaluateMapper.selectList(wrapper); | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public int getMonthGoodCount(Long riderId) { | |
| 85 | + YearMonth ym = YearMonth.now(); | |
| 86 | + long start = ym.atDay(1).atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond(); | |
| 87 | + long end = ym.plusMonths(1).atDay(1).atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond(); | |
| 88 | + return evaluateMapper.selectCount(new LambdaQueryWrapper<RiderEvaluate>() | |
| 89 | + .eq(RiderEvaluate::getRid, riderId) | |
| 90 | + .ge(RiderEvaluate::getStar, 4) | |
| 91 | + .ge(RiderEvaluate::getAddTime, start) | |
| 92 | + .lt(RiderEvaluate::getAddTime, end)).intValue(); | |
| 93 | + } | |
| 94 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RiderLevelServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RiderLevelServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.diligrp.rider.common.exception.BizException; | |
| 5 | +import com.diligrp.rider.entity.RiderLevel; | |
| 6 | +import com.diligrp.rider.entity.Rider; | |
| 7 | +import com.diligrp.rider.mapper.RiderLevelMapper; | |
| 8 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 9 | +import com.diligrp.rider.service.RiderLevelService; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import org.springframework.stereotype.Service; | |
| 12 | + | |
| 13 | +import java.math.BigDecimal; | |
| 14 | +import java.math.RoundingMode; | |
| 15 | + | |
| 16 | +@Service | |
| 17 | +@RequiredArgsConstructor | |
| 18 | +public class RiderLevelServiceImpl implements RiderLevelService { | |
| 19 | + | |
| 20 | + private final RiderMapper riderMapper; | |
| 21 | + private final RiderLevelMapper riderLevelMapper; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public RiderLevel getLevelByRider(Long riderId) { | |
| 25 | + Rider rider = riderMapper.selectById(riderId); | |
| 26 | + if (rider == null) throw new BizException("骑手信息不存在"); | |
| 27 | + | |
| 28 | + RiderLevel level = null; | |
| 29 | + if (rider.getLevelId() != null && rider.getLevelId() > 0) { | |
| 30 | + level = riderLevelMapper.selectById(rider.getLevelId()); | |
| 31 | + } | |
| 32 | + if (level == null) { | |
| 33 | + // 取该城市默认等级 | |
| 34 | + level = riderLevelMapper.selectOne(new LambdaQueryWrapper<RiderLevel>() | |
| 35 | + .eq(RiderLevel::getCityId, rider.getCityId()) | |
| 36 | + .eq(RiderLevel::getIsDefault, 1) | |
| 37 | + .last("LIMIT 1")); | |
| 38 | + } | |
| 39 | + return level; | |
| 40 | + } | |
| 41 | + | |
| 42 | + @Override | |
| 43 | + public BigDecimal calcIncome(Long riderId, int orderType, BigDecimal deliveryFee, long distance) { | |
| 44 | + RiderLevel level = getLevelByRider(riderId); | |
| 45 | + if (level == null) return BigDecimal.ZERO; | |
| 46 | + | |
| 47 | + // 外卖配送(type=6)按跑腿类规则计算 | |
| 48 | + // 办事类(type=5)按办事类规则计算,其余统一按跑腿类 | |
| 49 | + if (orderType == 5) { | |
| 50 | + return calcWorkIncome(level, deliveryFee); | |
| 51 | + } | |
| 52 | + return calcRunIncome(level, deliveryFee, distance); | |
| 53 | + } | |
| 54 | + | |
| 55 | + private BigDecimal calcRunIncome(RiderLevel level, BigDecimal deliveryFee, long distance) { | |
| 56 | + int mode = level.getRunFeeMode() == null ? 1 : level.getRunFeeMode(); | |
| 57 | + switch (mode) { | |
| 58 | + case 1: // 固定金额 | |
| 59 | + return level.getRunFixMoney() == null ? BigDecimal.ZERO : level.getRunFixMoney(); | |
| 60 | + case 2: // 按比例 | |
| 61 | + if (level.getRunRate() == null || deliveryFee == null) return BigDecimal.ZERO; | |
| 62 | + return deliveryFee.multiply(level.getRunRate()) | |
| 63 | + .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); | |
| 64 | + case 3: // 按距离 | |
| 65 | + return calcDistanceIncome(level, distance); | |
| 66 | + default: | |
| 67 | + return BigDecimal.ZERO; | |
| 68 | + } | |
| 69 | + } | |
| 70 | + | |
| 71 | + private BigDecimal calcDistanceIncome(RiderLevel level, long distanceMeters) { | |
| 72 | + if (level.getDistanceBasic() == null || level.getDistanceBasicMoney() == null) { | |
| 73 | + return BigDecimal.ZERO; | |
| 74 | + } | |
| 75 | + BigDecimal income = level.getDistanceBasicMoney(); | |
| 76 | + long basicMeters = level.getDistanceBasic(); | |
| 77 | + if (distanceMeters > basicMeters && level.getDistanceMoreMoney() != null) { | |
| 78 | + // 超出部分按每公里计费 | |
| 79 | + long extraMeters = distanceMeters - basicMeters; | |
| 80 | + BigDecimal extraKm = BigDecimal.valueOf(extraMeters).divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP); | |
| 81 | + income = income.add(extraKm.multiply(level.getDistanceMoreMoney())); | |
| 82 | + } | |
| 83 | + // 上限 | |
| 84 | + if (level.getDistanceMaxMoney() != null && level.getDistanceMaxMoney().compareTo(BigDecimal.ZERO) > 0) { | |
| 85 | + if (income.compareTo(level.getDistanceMaxMoney()) > 0) { | |
| 86 | + income = level.getDistanceMaxMoney(); | |
| 87 | + } | |
| 88 | + } | |
| 89 | + return income.setScale(2, RoundingMode.HALF_UP); | |
| 90 | + } | |
| 91 | + | |
| 92 | + private BigDecimal calcWorkIncome(RiderLevel level, BigDecimal deliveryFee) { | |
| 93 | + int mode = level.getWorkFeeMode() == null ? 1 : level.getWorkFeeMode(); | |
| 94 | + switch (mode) { | |
| 95 | + case 1: | |
| 96 | + return level.getWorkFixMoney() == null ? BigDecimal.ZERO : level.getWorkFixMoney(); | |
| 97 | + case 2: | |
| 98 | + if (level.getWorkRate() == null || deliveryFee == null) return BigDecimal.ZERO; | |
| 99 | + return deliveryFee.multiply(level.getWorkRate()) | |
| 100 | + .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); | |
| 101 | + default: | |
| 102 | + return BigDecimal.ZERO; | |
| 103 | + } | |
| 104 | + } | |
| 105 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RiderLocationServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RiderLocationServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 6 | +import com.diligrp.rider.dto.LocationDTO; | |
| 7 | +import com.diligrp.rider.entity.Rider; | |
| 8 | +import com.diligrp.rider.entity.RiderLocation; | |
| 9 | +import com.diligrp.rider.mapper.RiderLocationMapper; | |
| 10 | +import com.diligrp.rider.mapper.RiderMapper; | |
| 11 | +import com.diligrp.rider.service.CityService; | |
| 12 | +import com.diligrp.rider.service.RiderLocationService; | |
| 13 | +import com.diligrp.rider.util.GeoUtil; | |
| 14 | +import com.diligrp.rider.vo.NearbyRiderVO; | |
| 15 | +import lombok.RequiredArgsConstructor; | |
| 16 | +import org.springframework.stereotype.Service; | |
| 17 | + | |
| 18 | +import java.math.BigDecimal; | |
| 19 | +import java.util.ArrayList; | |
| 20 | +import java.util.List; | |
| 21 | + | |
| 22 | +@Service | |
| 23 | +@RequiredArgsConstructor | |
| 24 | +public class RiderLocationServiceImpl implements RiderLocationService { | |
| 25 | + | |
| 26 | + private final RiderLocationMapper locationMapper; | |
| 27 | + private final RiderMapper riderMapper; | |
| 28 | + private final CityService cityService; | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public void updateLocation(Long riderId, LocationDTO dto) { | |
| 32 | + RiderLocation existing = locationMapper.selectOne(new LambdaQueryWrapper<RiderLocation>() | |
| 33 | + .eq(RiderLocation::getUid, riderId)); | |
| 34 | + long now = System.currentTimeMillis() / 1000; | |
| 35 | + if (existing == null) { | |
| 36 | + RiderLocation loc = new RiderLocation(); | |
| 37 | + loc.setUid(riderId); | |
| 38 | + loc.setLng(dto.getLng()); | |
| 39 | + loc.setLat(dto.getLat()); | |
| 40 | + loc.setUpdateTime(now); | |
| 41 | + locationMapper.insert(loc); | |
| 42 | + } else { | |
| 43 | + locationMapper.update(null, new LambdaUpdateWrapper<RiderLocation>() | |
| 44 | + .eq(RiderLocation::getUid, riderId) | |
| 45 | + .set(RiderLocation::getLng, dto.getLng()) | |
| 46 | + .set(RiderLocation::getLat, dto.getLat()) | |
| 47 | + .set(RiderLocation::getUpdateTime, now)); | |
| 48 | + } | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public LocationDTO getLocation(Long riderId) { | |
| 53 | + RiderLocation loc = locationMapper.selectOne(new LambdaQueryWrapper<RiderLocation>() | |
| 54 | + .eq(RiderLocation::getUid, riderId)); | |
| 55 | + if (loc == null) return null; | |
| 56 | + LocationDTO dto = new LocationDTO(); | |
| 57 | + dto.setLng(loc.getLng()); | |
| 58 | + dto.setLat(loc.getLat()); | |
| 59 | + return dto; | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public List<NearbyRiderVO> getNearby(Long cityId, String lng, String lat) { | |
| 64 | + List<NearbyRiderVO> result = new ArrayList<>(); | |
| 65 | + if (cityId == null || cityId < 1 || lng == null || lat == null) return result; | |
| 66 | + | |
| 67 | + // 获取城市配置的附近距离范围(km) | |
| 68 | + DeliveryPricingConfigDTO config = cityService.getConfig(cityId); | |
| 69 | + if (config == null) return result; | |
| 70 | + BigDecimal limitKm = config.getRiderDistance(); | |
| 71 | + if (limitKm == null || limitKm.compareTo(BigDecimal.ZERO) <= 0) { | |
| 72 | + limitKm = BigDecimal.valueOf(3); // 默认3km | |
| 73 | + } | |
| 74 | + | |
| 75 | + // 查该城市所有在线(未休息)骑手 | |
| 76 | + List<Rider> riders = riderMapper.selectList(new LambdaQueryWrapper<Rider>() | |
| 77 | + .eq(Rider::getCityId, cityId) | |
| 78 | + .eq(Rider::getIsRest, 0) | |
| 79 | + .eq(Rider::getUserStatus, 1)); | |
| 80 | + if (riders.isEmpty()) return result; | |
| 81 | + | |
| 82 | + List<Long> riderIds = riders.stream().map(Rider::getId).toList(); | |
| 83 | + | |
| 84 | + // 查骑手位置 | |
| 85 | + List<RiderLocation> locations = locationMapper.selectList( | |
| 86 | + new LambdaQueryWrapper<RiderLocation>().in(RiderLocation::getUid, riderIds)); | |
| 87 | + | |
| 88 | + double queryLat = Double.parseDouble(lat); | |
| 89 | + double queryLng = Double.parseDouble(lng); | |
| 90 | + double limitD = limitKm.doubleValue(); | |
| 91 | + | |
| 92 | + for (RiderLocation loc : locations) { | |
| 93 | + try { | |
| 94 | + double dist = GeoUtil.calcDistanceKm( | |
| 95 | + queryLat, queryLng, | |
| 96 | + Double.parseDouble(loc.getLat()), | |
| 97 | + Double.parseDouble(loc.getLng())); | |
| 98 | + if (dist <= limitD) { | |
| 99 | + NearbyRiderVO vo = new NearbyRiderVO(); | |
| 100 | + vo.setLng(loc.getLng()); | |
| 101 | + vo.setLat(loc.getLat()); | |
| 102 | + vo.setDistance(dist); | |
| 103 | + result.add(vo); | |
| 104 | + } | |
| 105 | + } catch (Exception ignored) {} | |
| 106 | + } | |
| 107 | + return result; | |
| 108 | + } | |
| 109 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/RiderOrderServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/RiderOrderServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.entity.*; | |
| 6 | +import com.diligrp.rider.mapper.*; | |
| 7 | +import com.fasterxml.jackson.core.type.TypeReference; | |
| 8 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 9 | +import com.diligrp.rider.common.exception.BizException; | |
| 10 | +import com.diligrp.rider.entity.*; | |
| 11 | +import com.diligrp.rider.mapper.*; | |
| 12 | +import com.diligrp.rider.service.RiderLevelService; | |
| 13 | +import com.diligrp.rider.service.RiderOrderService; | |
| 14 | +import com.diligrp.rider.service.WebhookService; | |
| 15 | +import com.diligrp.rider.vo.OrderVO; | |
| 16 | +import com.diligrp.rider.vo.RiderMonthCountVO; | |
| 17 | +import com.diligrp.rider.vo.RiderTodayCountVO; | |
| 18 | +import lombok.RequiredArgsConstructor; | |
| 19 | +import lombok.extern.slf4j.Slf4j; | |
| 20 | +import org.springframework.stereotype.Service; | |
| 21 | +import org.springframework.transaction.annotation.Transactional; | |
| 22 | + | |
| 23 | +import java.math.BigDecimal; | |
| 24 | +import java.time.LocalDate; | |
| 25 | +import java.time.ZoneId; | |
| 26 | +import java.time.format.DateTimeFormatter; | |
| 27 | +import java.util.ArrayList; | |
| 28 | +import java.util.List; | |
| 29 | +import java.util.Map; | |
| 30 | + | |
| 31 | +@Slf4j | |
| 32 | +@Service | |
| 33 | +@RequiredArgsConstructor | |
| 34 | +public class RiderOrderServiceImpl implements RiderOrderService { | |
| 35 | + | |
| 36 | + private final OrdersMapper ordersMapper; | |
| 37 | + private final RiderOrderRefuseMapper refuseMapper; | |
| 38 | + private final RiderOrderCountMapper countMapper; | |
| 39 | + private final RiderBalanceMapper balanceMapper; | |
| 40 | + private final RiderMapper riderMapper; | |
| 41 | + private final RiderLevelService riderLevelService; | |
| 42 | + private final ObjectMapper objectMapper; | |
| 43 | + private final WebhookService webhookService; | |
| 44 | + | |
| 45 | + private static final int PAGE_SIZE = 20; | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public List<OrderVO> getList(Long riderId, Long cityId, Integer type, int page) { | |
| 49 | + if (type == null || !List.of(1, 2, 3).contains(type)) return List.of(); | |
| 50 | + | |
| 51 | + LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<Orders>() | |
| 52 | + .eq(Orders::getCityId, cityId) | |
| 53 | + .eq(Orders::getIsDel, 0); | |
| 54 | + | |
| 55 | + if (type == 1) { | |
| 56 | + // 待接单:已支付、无骑手、非自己转出的单、未拒绝的单 | |
| 57 | + wrapper.eq(Orders::getStatus, 2) | |
| 58 | + .eq(Orders::getRiderId, 0) | |
| 59 | + .ne(Orders::getOldRiderId, riderId); | |
| 60 | + List<Long> refusedIds = refuseMapper.selectRefuseOrderIds(riderId); | |
| 61 | + if (!refusedIds.isEmpty()) { | |
| 62 | + wrapper.notIn(Orders::getId, refusedIds); | |
| 63 | + } | |
| 64 | + } else if (type == 2) { | |
| 65 | + // 待取货:已接单 | |
| 66 | + wrapper.eq(Orders::getRiderId, riderId).eq(Orders::getStatus, 3); | |
| 67 | + } else { | |
| 68 | + // 待完成:服务中 | |
| 69 | + wrapper.eq(Orders::getRiderId, riderId).eq(Orders::getStatus, 4); | |
| 70 | + } | |
| 71 | + | |
| 72 | + int offset = (page - 1) * PAGE_SIZE; | |
| 73 | + wrapper.orderByDesc(Orders::getId).last("LIMIT " + offset + "," + PAGE_SIZE); | |
| 74 | + | |
| 75 | + List<Orders> orders = ordersMapper.selectList(wrapper); | |
| 76 | + List<OrderVO> result = new ArrayList<>(); | |
| 77 | + for (Orders o : orders) { | |
| 78 | + result.add(toVO(o, riderId)); | |
| 79 | + } | |
| 80 | + return result; | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public OrderVO getDetail(Long riderId, Long orderId) { | |
| 85 | + Orders order = ordersMapper.selectById(orderId); | |
| 86 | + if (order == null) throw new BizException(1001, "订单信息错误"); | |
| 87 | + // @TODO 注释 | |
| 88 | +// if (!riderId.equals(order.getRiderId())) throw new BizException(1002, "订单信息错误"); | |
| 89 | + if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) { | |
| 90 | + throw new BizException(1003, "转单的订单无法查看详情"); | |
| 91 | + } | |
| 92 | + return toVO(order, riderId); | |
| 93 | + } | |
| 94 | + | |
| 95 | + @Override | |
| 96 | + @Transactional | |
| 97 | + public void refuse(Long riderId, Long cityId, Long orderId) { | |
| 98 | + Orders order = ordersMapper.selectById(orderId); | |
| 99 | + if (order == null) throw new BizException(1001, "订单信息错误"); | |
| 100 | + if (!cityId.equals(order.getCityId())) throw new BizException(1002, "订单信息错误"); | |
| 101 | + if (order.getRiderId() != null && order.getRiderId() != 0) { | |
| 102 | + throw new BizException(1003, "订单已被接"); | |
| 103 | + } | |
| 104 | + RiderOrderRefuse refuse = new RiderOrderRefuse(); | |
| 105 | + refuse.setRiderId(riderId); | |
| 106 | + refuse.setOid(orderId); | |
| 107 | + refuse.setAddTime(System.currentTimeMillis() / 1000); | |
| 108 | + refuseMapper.insert(refuse); | |
| 109 | + } | |
| 110 | + | |
| 111 | + @Override | |
| 112 | + @Transactional | |
| 113 | + public void grap(Long riderId, Long cityId, Long orderId) { | |
| 114 | + // 检查今日转单次数 | |
| 115 | + checkTransLimit(riderId, cityId); | |
| 116 | + | |
| 117 | + Rider rider = riderMapper.selectById(riderId); | |
| 118 | + if (rider == null) throw new BizException(1000, "骑手信息不存在"); | |
| 119 | + if (rider.getIsRest() != null && rider.getIsRest() == 1) { | |
| 120 | + throw new BizException(1000, "休息中无法接单"); | |
| 121 | + } | |
| 122 | + | |
| 123 | + Orders order = ordersMapper.selectById(orderId); | |
| 124 | + if (order == null) throw new BizException(1001, "订单信息错误"); | |
| 125 | + if (!cityId.equals(order.getCityId())) throw new BizException(1002, "订单城市不匹配"); | |
| 126 | + if (order.getStatus() < 2) throw new BizException(1003, "订单状态错误"); | |
| 127 | + if (order.getRiderId() != null && order.getRiderId() != 0) { | |
| 128 | + throw new BizException(980, "抢单失败,订单已被接"); | |
| 129 | + } | |
| 130 | + if (order.getIsTrans() == 1 && riderId.equals(order.getOldRiderId())) { | |
| 131 | + throw new BizException(980, "抢单失败,不能抢自己转出的单"); | |
| 132 | + } | |
| 133 | + | |
| 134 | + long now = System.currentTimeMillis() / 1000; | |
| 135 | + LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>() | |
| 136 | + .eq(Orders::getId, orderId) | |
| 137 | + .eq(Orders::getRiderId, 0) | |
| 138 | + .eq(Orders::getStatus, 2) | |
| 139 | + .set(Orders::getRiderId, riderId) | |
| 140 | + .set(Orders::getStatus, 3) | |
| 141 | + .set(Orders::getGrapTime, now); | |
| 142 | + if (order.getOldRiderId() == null || order.getOldRiderId() == 0) { | |
| 143 | + wrapper.set(Orders::getOldRiderId, riderId); | |
| 144 | + } | |
| 145 | + int updated = ordersMapper.update(null, wrapper); | |
| 146 | + if (updated == 0) throw new BizException(980, "抢单失败,请重试"); | |
| 147 | + | |
| 148 | + // 预置骑手收入 | |
| 149 | + presetIncome(orderId, riderId, order); | |
| 150 | + | |
| 151 | + // 通知接入方:骑手已接单 | |
| 152 | + notifyOrderEvent(orderId, "order.accepted"); | |
| 153 | + | |
| 154 | + // 删除该骑手的拒单记录(抢到了就清掉) | |
| 155 | + refuseMapper.delete(new LambdaQueryWrapper<RiderOrderRefuse>() | |
| 156 | + .eq(RiderOrderRefuse::getOid, orderId)); | |
| 157 | + } | |
| 158 | + | |
| 159 | + @Override | |
| 160 | + @Transactional | |
| 161 | + public void start(Long riderId, Long orderId, String code) { | |
| 162 | + if (code == null || code.isBlank()) throw new BizException(1001, "请输入完成码"); | |
| 163 | + | |
| 164 | + Orders order = ordersMapper.selectById(orderId); | |
| 165 | + if (order == null) throw new BizException(1004, "订单信息错误"); | |
| 166 | + if (!riderId.equals(order.getRiderId())) throw new BizException(1005, "订单信息错误"); | |
| 167 | + if (order.getStatus() != 3) throw new BizException(1006, "订单状态错误"); | |
| 168 | + // @TODO 注释 | |
| 169 | +// if (!code.equals(order.getCode())) throw new BizException(1007, "完成码错误"); | |
| 170 | + | |
| 171 | + long now = System.currentTimeMillis() / 1000; | |
| 172 | + int updated = ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 173 | + .eq(Orders::getId, orderId) | |
| 174 | + .eq(Orders::getRiderId, riderId) | |
| 175 | + .eq(Orders::getStatus, 3) | |
| 176 | + .set(Orders::getStatus, 4) | |
| 177 | + .set(Orders::getPickTime, now)); | |
| 178 | + if (updated == 0) throw new BizException(980, "操作失败"); | |
| 179 | + // 通知接入方:骑手取件中 | |
| 180 | + notifyOrderEvent(orderId, "order.picking"); | |
| 181 | + } | |
| 182 | + | |
| 183 | + @Override | |
| 184 | + @Transactional | |
| 185 | + public void complete(Long riderId, Long orderId, String thumbsJson) { | |
| 186 | + if (thumbsJson == null || thumbsJson.isBlank()) throw new BizException(1001, "请上传照片"); | |
| 187 | + | |
| 188 | + List<String> thumbs; | |
| 189 | + try { | |
| 190 | + thumbs = objectMapper.readValue(thumbsJson, new TypeReference<>() {}); | |
| 191 | + } catch (Exception e) { | |
| 192 | + throw new BizException(1002, "照片格式错误"); | |
| 193 | + } | |
| 194 | + if (thumbs.isEmpty()) throw new BizException(1002, "请上传照片"); | |
| 195 | + if (thumbs.size() > 3) throw new BizException(1003, "最多上传3张照片"); | |
| 196 | + | |
| 197 | + Orders order = ordersMapper.selectById(orderId); | |
| 198 | + if (order == null) throw new BizException(1004, "订单信息错误"); | |
| 199 | + if (!riderId.equals(order.getRiderId())) throw new BizException(1005, "订单信息错误"); | |
| 200 | + if (order.getStatus() != 4) throw new BizException(1006, "订单状态错误"); | |
| 201 | + | |
| 202 | + long now = System.currentTimeMillis() / 1000; | |
| 203 | + int updated = ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 204 | + .eq(Orders::getId, orderId) | |
| 205 | + .eq(Orders::getRiderId, riderId) | |
| 206 | + .eq(Orders::getStatus, 4) | |
| 207 | + .set(Orders::getThumbs, thumbsJson) | |
| 208 | + .set(Orders::getStatus, 6) | |
| 209 | + .set(Orders::getCompleteTime, now)); | |
| 210 | + if (updated == 0) throw new BizException(1008, "操作失败"); | |
| 211 | + | |
| 212 | + // 结算骑手收入 | |
| 213 | + settleIncome(orderId, riderId); | |
| 214 | + | |
| 215 | + // 通知接入方:订单已完成 | |
| 216 | + notifyOrderEvent(orderId, "order.completed"); | |
| 217 | + | |
| 218 | + // 累计骑手统计 | |
| 219 | + long distance = getDistance(order); | |
| 220 | + int countDate = Integer.parseInt(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); | |
| 221 | + countMapper.upsertCount(riderId, countDate, 1, distance, 0); | |
| 222 | + } | |
| 223 | + | |
| 224 | + // ---- 私有辅助方法 ---- | |
| 225 | + | |
| 226 | + /** 通知接入方订单状态变更 */ | |
| 227 | + private void notifyOrderEvent(Long orderId, String event) { | |
| 228 | + try { | |
| 229 | + Orders order = ordersMapper.selectById(orderId); | |
| 230 | + if (order == null || order.getAppKey() == null) return; | |
| 231 | + java.util.Map<String, Object> payload = new java.util.HashMap<>(); | |
| 232 | + payload.put("event", event); | |
| 233 | + payload.put("outOrderNo", order.getOutOrderNo()); | |
| 234 | + payload.put("deliveryOrderId", order.getId()); | |
| 235 | + payload.put("orderNo", order.getOrderNo()); | |
| 236 | + payload.put("status", order.getStatus()); | |
| 237 | + payload.put("riderId", order.getRiderId()); | |
| 238 | + payload.put("timestamp", System.currentTimeMillis() / 1000); | |
| 239 | + webhookService.send(event, orderId, objectMapper.writeValueAsString(payload)); | |
| 240 | + } catch (Exception e) { | |
| 241 | + log.warn("订单事件通知失败 orderId={} event={}", orderId, event, e); | |
| 242 | + } | |
| 243 | + } | |
| 244 | + | |
| 245 | + private void checkTransLimit(Long riderId, Long cityId) { | |
| 246 | + RiderLevel level = riderLevelService.getLevelByRider(riderId); | |
| 247 | + if (level == null || level.getTransNums() == null) return; | |
| 248 | + long todayStart = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond(); | |
| 249 | + int todayTrans = ordersMapper.countTodayTrans(riderId, todayStart); | |
| 250 | + if (todayTrans >= level.getTransNums()) { | |
| 251 | + throw new BizException(1000, "今日转单已超上限,无法接单"); | |
| 252 | + } | |
| 253 | + } | |
| 254 | + | |
| 255 | + private void presetIncome(Long orderId, Long riderId, Orders order) { | |
| 256 | + try { | |
| 257 | + long distance = getDistance(order); | |
| 258 | + BigDecimal income = riderLevelService.calcIncome(riderId, order.getType(), | |
| 259 | + order.getMoneyDelivery(), distance); | |
| 260 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 261 | + .eq(Orders::getId, orderId) | |
| 262 | + .set(Orders::getRiderIncome, income) | |
| 263 | + .set(Orders::getIsIncome, 1)); | |
| 264 | + } catch (Exception e) { | |
| 265 | + log.warn("预置收入失败 orderId={}", orderId, e); | |
| 266 | + } | |
| 267 | + } | |
| 268 | + | |
| 269 | + private void settleIncome(Long orderId, Long riderId) { | |
| 270 | + Orders order = ordersMapper.selectById(orderId); | |
| 271 | + BigDecimal income = order.getRiderIncome(); | |
| 272 | + if (income == null) income = BigDecimal.ZERO; | |
| 273 | + | |
| 274 | + // 更新结算状态 | |
| 275 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 276 | + .eq(Orders::getId, orderId) | |
| 277 | + .set(Orders::getIsIncome, 2)); | |
| 278 | + | |
| 279 | + // 兼职骑手累加余额 | |
| 280 | + Rider rider = riderMapper.selectById(riderId); | |
| 281 | + if (rider != null && rider.getType() == 1 && income.compareTo(BigDecimal.ZERO) > 0) { | |
| 282 | + riderMapper.update(null, new LambdaUpdateWrapper<Rider>() | |
| 283 | + .eq(Rider::getId, riderId) | |
| 284 | + .setSql("balance = balance + " + income)); | |
| 285 | + // 记录流水 | |
| 286 | + RiderBalance balance = new RiderBalance(); | |
| 287 | + balance.setUid(riderId); | |
| 288 | + balance.setType(1); | |
| 289 | + balance.setAction("order_complete"); | |
| 290 | + balance.setActionId(orderId); | |
| 291 | + balance.setOrderNo(order.getOrderNo()); | |
| 292 | + balance.setNums(income); | |
| 293 | + balance.setAddTime(System.currentTimeMillis() / 1000); | |
| 294 | + balanceMapper.insert(balance); | |
| 295 | + } | |
| 296 | + } | |
| 297 | + | |
| 298 | + private long getDistance(Orders order) { | |
| 299 | + try { | |
| 300 | + Map<String, Object> extra = objectMapper.readValue(order.getExtra(), new TypeReference<>() {}); | |
| 301 | + Object dist = extra.get("distance"); | |
| 302 | + if (dist != null) return Long.parseLong(dist.toString()); | |
| 303 | + } catch (Exception ignored) {} | |
| 304 | + return 0; | |
| 305 | + } | |
| 306 | + | |
| 307 | + private OrderVO toVO(Orders o, Long riderId) { | |
| 308 | + OrderVO vo = new OrderVO(); | |
| 309 | + vo.setId(o.getId()); | |
| 310 | + vo.setOrderNo(o.getOrderNo()); | |
| 311 | + vo.setType(o.getType()); | |
| 312 | + vo.setTypeName(getTypeName(o.getType())); | |
| 313 | + vo.setStatus(o.getStatus()); | |
| 314 | + vo.setFName(o.getFName()); | |
| 315 | + vo.setFAddr(o.getFAddr()); | |
| 316 | + vo.setFLng(o.getFLng()); | |
| 317 | + vo.setFLat(o.getFLat()); | |
| 318 | + vo.setTName(o.getTName()); | |
| 319 | + vo.setTAddr(o.getTAddr()); | |
| 320 | + vo.setTLng(o.getTLng()); | |
| 321 | + vo.setTLat(o.getTLat()); | |
| 322 | + vo.setRecipName(o.getRecipName()); | |
| 323 | + vo.setRecipPhone(o.getRecipPhone()); | |
| 324 | + vo.setIsTrans(o.getIsTrans()); | |
| 325 | + vo.setAddTime(o.getAddTime() != null ? formatTime(o.getAddTime()) : null); | |
| 326 | + vo.setGrapTime(o.getGrapTime() != null && o.getGrapTime() > 0 ? formatTime(o.getGrapTime()) : null); | |
| 327 | + vo.setPickTime(o.getPickTime() != null && o.getPickTime() > 0 ? formatTime(o.getPickTime()) : null); | |
| 328 | + vo.setCompleteTime(o.getCompleteTime() != null && o.getCompleteTime() > 0 ? formatTime(o.getCompleteTime()) : null); | |
| 329 | + vo.setTransTime(o.getTransTime() != null && o.getTransTime() > 0 ? formatTime(o.getTransTime()) : null); | |
| 330 | + // 收入 | |
| 331 | + BigDecimal income = o.getRiderIncome() != null ? o.getRiderIncome() : BigDecimal.ZERO; | |
| 332 | + if (o.getStatus() != 6 && (o.getIsIncome() == null || o.getIsIncome() == 0)) { | |
| 333 | + income = riderLevelService.calcIncome(riderId, o.getType(), o.getMoneyDelivery(), getDistance(o)); | |
| 334 | + } | |
| 335 | + vo.setIncome(income); | |
| 336 | + vo.setOutOrderNo(o.getOutOrderNo()); | |
| 337 | + vo.setItemRemark(o.getItemRemark()); | |
| 338 | + try { | |
| 339 | + if (o.getExtra() != null) { | |
| 340 | + Map<String, Object> extraMap = objectMapper.readValue(o.getExtra(), new TypeReference<Map<String, Object>>() {}); | |
| 341 | + vo.setExtra(extraMap); | |
| 342 | + // 从extra取备注 | |
| 343 | + Object remark = extraMap.get("remark"); | |
| 344 | + if (remark != null) vo.setDes(remark.toString()); | |
| 345 | + // 从extra取预约标识 | |
| 346 | + Object isPre = extraMap.get("ispre"); | |
| 347 | + if (isPre != null) vo.setIsPre(Integer.parseInt(isPre.toString())); | |
| 348 | + // 从extra取服务时间 | |
| 349 | + Object serviceTime = extraMap.get("service_time"); | |
| 350 | + if (serviceTime != null) vo.setService_time(serviceTime.toString()); | |
| 351 | + } | |
| 352 | + if (o.getItemsJson() != null && !o.getItemsJson().isBlank()) { | |
| 353 | + vo.setItems(objectMapper.readValue(o.getItemsJson(), new TypeReference<List<Object>>() {})); | |
| 354 | + } | |
| 355 | + } catch (Exception ignored) {} | |
| 356 | + return vo; | |
| 357 | + } | |
| 358 | + | |
| 359 | + private String formatTime(long epochSecond) { | |
| 360 | + return java.time.Instant.ofEpochSecond(epochSecond) | |
| 361 | + .atZone(ZoneId.of("Asia/Shanghai")) | |
| 362 | + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); | |
| 363 | + } | |
| 364 | + | |
| 365 | + private String getTypeName(Integer type) { | |
| 366 | + if (type == null) return ""; | |
| 367 | + return switch (type) { | |
| 368 | + case 1 -> "帮我送"; | |
| 369 | + case 2 -> "帮我取"; | |
| 370 | + case 3 -> "帮我买"; | |
| 371 | + case 4 -> "帮我排队"; | |
| 372 | + case 5 -> "帮我办"; | |
| 373 | + case 6 -> "外卖配送"; | |
| 374 | + default -> ""; | |
| 375 | + }; | |
| 376 | + } | |
| 377 | + | |
| 378 | + @Override | |
| 379 | + @Transactional | |
| 380 | + public void applyTrans(Long riderId, Long orderId) { | |
| 381 | + Orders order = ordersMapper.selectById(orderId); | |
| 382 | + if (order == null) throw new BizException(1001, "订单不存在"); | |
| 383 | + if (!riderId.equals(order.getRiderId())) throw new BizException(1002, "订单信息错误"); | |
| 384 | + if (order.getStatus() != 3 && order.getStatus() != 4) { | |
| 385 | + throw new BizException(1003, "只有已接单或配送中的订单可以申请转单"); | |
| 386 | + } | |
| 387 | + if (order.getIsTrans() != 0) throw new BizException(1004, "转单申请已提交,请勿重复操作"); | |
| 388 | + | |
| 389 | + // 检查今日转单次数 | |
| 390 | + checkTransLimit(riderId, order.getCityId()); | |
| 391 | + | |
| 392 | + long now = System.currentTimeMillis() / 1000; | |
| 393 | + int countDate = Integer.parseInt(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); | |
| 394 | + | |
| 395 | + if (order.getStatus() == 3) { | |
| 396 | + // status=3(已接单,未取货):直接回抢单池,无需审批 | |
| 397 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 398 | + .eq(Orders::getId, orderId) | |
| 399 | + .eq(Orders::getRiderId, riderId) | |
| 400 | + .eq(Orders::getStatus, 3) | |
| 401 | + .set(Orders::getIsTrans, 1) // 直接转单成功 | |
| 402 | + .set(Orders::getStatus, 2) // 回到待接单 | |
| 403 | + .set(Orders::getRiderId, 0L) // 清空骑手 | |
| 404 | + .set(Orders::getGrapTime, 0L) | |
| 405 | + .set(Orders::getIsIncome, 0) | |
| 406 | + .set(Orders::getRiderIncome, BigDecimal.ZERO) | |
| 407 | + .set(Orders::getSubstationIncome, BigDecimal.ZERO) | |
| 408 | + .set(Orders::getTransTime, now)); | |
| 409 | + } else { | |
| 410 | + // status=4(取货后配送中):需分站审批,设为申请中 | |
| 411 | + ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 412 | + .eq(Orders::getId, orderId) | |
| 413 | + .eq(Orders::getRiderId, riderId) | |
| 414 | + .eq(Orders::getStatus, 4) | |
| 415 | + .set(Orders::getIsTrans, 2) // 申请中,等待审批 | |
| 416 | + .set(Orders::getTransTime, now)); | |
| 417 | + } | |
| 418 | + | |
| 419 | + // 累加骑手转单统计 | |
| 420 | + countMapper.upsertCount(riderId, countDate, 0, 0, 1); | |
| 421 | + } | |
| 422 | + | |
| 423 | + @Override | |
| 424 | + public RiderTodayCountVO getTodayCount(Long riderId) { | |
| 425 | + long todayStart = LocalDate.now().atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond(); | |
| 426 | + int countDate = Integer.parseInt(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); | |
| 427 | + | |
| 428 | + RiderOrderCount count = countMapper.selectOne( | |
| 429 | + new LambdaQueryWrapper<RiderOrderCount>() | |
| 430 | + .eq(RiderOrderCount::getUid, riderId) | |
| 431 | + .eq(RiderOrderCount::getCountDate, countDate) | |
| 432 | + .last("LIMIT 1")); | |
| 433 | + | |
| 434 | + RiderTodayCountVO vo = new RiderTodayCountVO(); | |
| 435 | + if (count != null) { | |
| 436 | + vo.setOrders(count.getOrders()); | |
| 437 | + vo.setTransfers(count.getTransfers()); | |
| 438 | + vo.setDistance(count.getDistance()); | |
| 439 | + } else { | |
| 440 | + vo.setOrders(0); | |
| 441 | + vo.setTransfers(0); | |
| 442 | + vo.setDistance(0L); | |
| 443 | + } | |
| 444 | + // 今日抢单数(今日接单总数, getGrapNums) | |
| 445 | + long todayStartTs = LocalDate.now().atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond(); | |
| 446 | + long todayEndTs = todayStartTs + 86400; | |
| 447 | + int graps = ordersMapper.selectCount(new LambdaQueryWrapper<Orders>() | |
| 448 | + .eq(Orders::getOldRiderId, riderId) | |
| 449 | + .ge(Orders::getGrapTime, todayStartTs) | |
| 450 | + .lt(Orders::getGrapTime, todayEndTs)).intValue(); | |
| 451 | + vo.setGraps(graps); | |
| 452 | + return vo; | |
| 453 | + } | |
| 454 | + | |
| 455 | + @Override | |
| 456 | + public List<RiderMonthCountVO> getMonthCount(Long riderId, int year) { | |
| 457 | + if (year == 0) year = LocalDate.now().getYear(); | |
| 458 | + int startDate = year * 10000 + 101; // yyyyMMdd 格式起始 | |
| 459 | + int endDate = (year + 1) * 10000 + 101; | |
| 460 | + | |
| 461 | + List<RiderOrderCount> list = countMapper.selectList( | |
| 462 | + new LambdaQueryWrapper<RiderOrderCount>() | |
| 463 | + .eq(RiderOrderCount::getUid, riderId) | |
| 464 | + .ge(RiderOrderCount::getCountDate, startDate) | |
| 465 | + .lt(RiderOrderCount::getCountDate, endDate) | |
| 466 | + .orderByAsc(RiderOrderCount::getCountDate)); | |
| 467 | + | |
| 468 | + int currentYearMonth = Integer.parseInt( | |
| 469 | + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"))); | |
| 470 | + | |
| 471 | + List<RiderMonthCountVO> result = new ArrayList<>(); | |
| 472 | + for (var c : list) { | |
| 473 | + RiderMonthCountVO vo = new RiderMonthCountVO(); | |
| 474 | + int ym = c.getCountDate() / 100; // yyyyMM | |
| 475 | + int month = ym % 100; | |
| 476 | + if (ym == currentYearMonth) { | |
| 477 | + vo.setTitle("本月"); | |
| 478 | + vo.setDes(String.format("%02d-01至%s", month, | |
| 479 | + LocalDate.now().format(DateTimeFormatter.ofPattern("MM-dd")))); | |
| 480 | + } else { | |
| 481 | + vo.setTitle(month + "月"); | |
| 482 | + vo.setDes(""); | |
| 483 | + } | |
| 484 | + vo.setOrders(c.getOrders()); | |
| 485 | + vo.setTransfers(c.getTransfers()); | |
| 486 | + vo.setDistance(c.getDistance()); | |
| 487 | + result.add(vo); | |
| 488 | + } | |
| 489 | + return result; | |
| 490 | + } | |
| 491 | + | |
| 492 | + @Override | |
| 493 | + public List<OrderVO> getCountList(Long riderId, int type, int page) { | |
| 494 | + // getCountList:以 oldriderid 为维度查询骑手历史订单 | |
| 495 | + // type=0全部(完成+转单) 1已完成 2已转单 | |
| 496 | + if (!List.of(0, 1, 2).contains(type)) return List.of(); | |
| 497 | + | |
| 498 | + LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<Orders>() | |
| 499 | + .eq(Orders::getOldRiderId, riderId) | |
| 500 | + .orderByDesc(Orders::getId); | |
| 501 | + | |
| 502 | + if (type == 0) { | |
| 503 | + // 全部:(status=6 且 isTrans=0) 或 isTrans=1(转单成功) | |
| 504 | + wrapper.and(w -> w | |
| 505 | + .nested(n -> n.eq(Orders::getStatus, 6).eq(Orders::getIsTrans, 0)) | |
| 506 | + .or() | |
| 507 | + .eq(Orders::getIsTrans, 1)); | |
| 508 | + } else if (type == 1) { | |
| 509 | + wrapper.eq(Orders::getStatus, 6).eq(Orders::getIsTrans, 0); | |
| 510 | + } else { | |
| 511 | + wrapper.eq(Orders::getIsTrans, 1); | |
| 512 | + } | |
| 513 | + | |
| 514 | + int offset = (page - 1) * PAGE_SIZE; | |
| 515 | + wrapper.last("LIMIT " + offset + "," + PAGE_SIZE); | |
| 516 | + | |
| 517 | + List<Orders> orders = ordersMapper.selectList(wrapper); | |
| 518 | + List<OrderVO> result = new ArrayList<>(); | |
| 519 | + for (Orders o : orders) result.add(toVO(o, riderId)); | |
| 520 | + return result; | |
| 521 | + } | |
| 522 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/SubstationServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/SubstationServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.diligrp.rider.common.exception.BizException; | |
| 6 | +import com.diligrp.rider.entity.Substation; | |
| 7 | +import com.diligrp.rider.mapper.SubstationMapper; | |
| 8 | +import com.diligrp.rider.service.SubstationService; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import org.springframework.stereotype.Service; | |
| 11 | +import org.springframework.util.DigestUtils; | |
| 12 | + | |
| 13 | +import java.nio.charset.StandardCharsets; | |
| 14 | +import java.util.List; | |
| 15 | + | |
| 16 | +@Service | |
| 17 | +@RequiredArgsConstructor | |
| 18 | +public class SubstationServiceImpl implements SubstationService { | |
| 19 | + | |
| 20 | + private final SubstationMapper substationMapper; | |
| 21 | + | |
| 22 | + @Override | |
| 23 | + public List<Substation> list(String keyword) { | |
| 24 | + LambdaQueryWrapper<Substation> wrapper = new LambdaQueryWrapper<Substation>() | |
| 25 | + .orderByDesc(Substation::getId); | |
| 26 | + if (keyword != null && !keyword.isBlank()) { | |
| 27 | + wrapper.like(Substation::getUserLogin, keyword) | |
| 28 | + .or().like(Substation::getUserNickname, keyword) | |
| 29 | + .or().like(Substation::getMobile, keyword); | |
| 30 | + } | |
| 31 | + return substationMapper.selectList(wrapper); | |
| 32 | + } | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + public void add(Substation substation) { | |
| 36 | + if (substation.getCityId() == null || substation.getCityId() < 1) { | |
| 37 | + throw new BizException("请选择管理城市"); | |
| 38 | + } | |
| 39 | + | |
| 40 | + Long loginExists = substationMapper.selectCount(new LambdaQueryWrapper<Substation>() | |
| 41 | + .eq(Substation::getUserLogin, substation.getUserLogin())); | |
| 42 | + if (loginExists > 0) throw new BizException("账号已存在,请更换"); | |
| 43 | + | |
| 44 | + substation.setUserPass(encryptPass(substation.getUserPass())); | |
| 45 | + substation.setUserStatus(1); | |
| 46 | + substation.setCreateTime(System.currentTimeMillis() / 1000); | |
| 47 | + substationMapper.insert(substation); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public void edit(Substation substation) { | |
| 52 | + Substation existing = substationMapper.selectById(substation.getId()); | |
| 53 | + if (existing == null) throw new BizException("分站管理员不存在"); | |
| 54 | + // 密码为空则不更新 | |
| 55 | + if (substation.getUserPass() == null || substation.getUserPass().isBlank()) { | |
| 56 | + substation.setUserPass(null); | |
| 57 | + } else { | |
| 58 | + substation.setUserPass(encryptPass(substation.getUserPass())); | |
| 59 | + } | |
| 60 | + substationMapper.updateById(substation); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public void ban(Long id) { | |
| 65 | + substationMapper.update(null, new LambdaUpdateWrapper<Substation>() | |
| 66 | + .eq(Substation::getId, id).set(Substation::getUserStatus, 0)); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public void cancelBan(Long id) { | |
| 71 | + substationMapper.update(null, new LambdaUpdateWrapper<Substation>() | |
| 72 | + .eq(Substation::getId, id).set(Substation::getUserStatus, 1)); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + public void del(Long id) { | |
| 77 | + substationMapper.deleteById(id); | |
| 78 | + } | |
| 79 | + | |
| 80 | + @Override | |
| 81 | + public Substation getByCityId(Long cityId) { | |
| 82 | + return substationMapper.selectOne(new LambdaQueryWrapper<Substation>() | |
| 83 | + .eq(Substation::getCityId, cityId).last("LIMIT 1")); | |
| 84 | + } | |
| 85 | + | |
| 86 | + @Override | |
| 87 | + public void changePassword(Long substationId, String oldPassword, String newPassword) { | |
| 88 | + Substation sub = substationMapper.selectById(substationId); | |
| 89 | + if (sub == null) throw new BizException("账号不存在"); | |
| 90 | + if (!encryptPass(oldPassword).equals(sub.getUserPass())) { | |
| 91 | + throw new BizException("原密码不正确"); | |
| 92 | + } | |
| 93 | + if (encryptPass(newPassword).equals(sub.getUserPass())) { | |
| 94 | + throw new BizException("新密码不能与原密码相同"); | |
| 95 | + } | |
| 96 | + substationMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<Substation>() | |
| 97 | + .eq(Substation::getId, substationId) | |
| 98 | + .set(Substation::getUserPass, encryptPass(newPassword))); | |
| 99 | + } | |
| 100 | + | |
| 101 | + private String encryptPass(String pass) { | |
| 102 | + return DigestUtils.md5DigestAsHex(pass.getBytes(StandardCharsets.UTF_8)); | |
| 103 | + } | |
| 104 | +} | ... | ... |
src/main/java/com/diligrp/rider/service/impl/WebhookServiceImpl.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/service/impl/WebhookServiceImpl.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 5 | +import com.diligrp.rider.entity.OpenApp; | |
| 6 | +import com.diligrp.rider.entity.WebhookLog; | |
| 7 | +import com.diligrp.rider.mapper.OpenAppMapper; | |
| 8 | +import com.diligrp.rider.mapper.WebhookLogMapper; | |
| 9 | +import com.diligrp.rider.service.WebhookService; | |
| 10 | +import lombok.RequiredArgsConstructor; | |
| 11 | +import lombok.extern.slf4j.Slf4j; | |
| 12 | +import org.springframework.scheduling.annotation.Async; | |
| 13 | +import org.springframework.stereotype.Service; | |
| 14 | + | |
| 15 | +import java.net.URI; | |
| 16 | +import java.net.http.HttpClient; | |
| 17 | +import java.net.http.HttpRequest; | |
| 18 | +import java.net.http.HttpResponse; | |
| 19 | +import java.time.Duration; | |
| 20 | +import java.util.List; | |
| 21 | + | |
| 22 | +@Slf4j | |
| 23 | +@Service | |
| 24 | +@RequiredArgsConstructor | |
| 25 | +public class WebhookServiceImpl implements WebhookService { | |
| 26 | + | |
| 27 | + private final OpenAppMapper openAppMapper; | |
| 28 | + private final WebhookLogMapper webhookLogMapper; | |
| 29 | + private final ObjectMapper objectMapper; | |
| 30 | + | |
| 31 | + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() | |
| 32 | + .connectTimeout(Duration.ofSeconds(5)) | |
| 33 | + .build(); | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + @Async | |
| 37 | + public void send(String event, Long bizId, String payload) { | |
| 38 | + // 查找订阅该事件的所有应用 | |
| 39 | + List<OpenApp> apps = openAppMapper.selectList( | |
| 40 | + new LambdaQueryWrapper<OpenApp>().eq(OpenApp::getStatus, 1)); | |
| 41 | + | |
| 42 | + for (OpenApp app : apps) { | |
| 43 | + if (app.getWebhookUrl() == null || app.getWebhookUrl().isBlank()) continue; | |
| 44 | + if (!isSubscribed(app.getWebhookEvents(), event)) continue; | |
| 45 | + doSend(app, event, bizId, payload, 0); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + | |
| 49 | + @Override | |
| 50 | + public void retry(Long logId) { | |
| 51 | + WebhookLog log = webhookLogMapper.selectById(logId); | |
| 52 | + if (log == null || log.getStatus() == 1) return; | |
| 53 | + OpenApp app = openAppMapper.selectById(log.getAppId()); | |
| 54 | + if (app == null) return; | |
| 55 | + doSend(app, log.getEvent(), log.getBizId(), log.getPayload(), log.getRetryCount() + 1); | |
| 56 | + } | |
| 57 | + | |
| 58 | + private void doSend(OpenApp app, String event, Long bizId, String payload, int retryCount) { | |
| 59 | + WebhookLog webhookLog = new WebhookLog(); | |
| 60 | + webhookLog.setAppId(app.getId()); | |
| 61 | + webhookLog.setEvent(event); | |
| 62 | + webhookLog.setBizId(bizId); | |
| 63 | + webhookLog.setUrl(app.getWebhookUrl()); | |
| 64 | + webhookLog.setPayload(payload); | |
| 65 | + webhookLog.setRetryCount(retryCount); | |
| 66 | + webhookLog.setCreateTime(System.currentTimeMillis() / 1000); | |
| 67 | + | |
| 68 | + int responseCode = 0; | |
| 69 | + String responseBody = ""; | |
| 70 | + int status = 0; | |
| 71 | + | |
| 72 | + try { | |
| 73 | + HttpRequest request = HttpRequest.newBuilder() | |
| 74 | + .uri(URI.create(app.getWebhookUrl())) | |
| 75 | + .header("Content-Type", "application/json") | |
| 76 | + .header("X-App-Key", app.getAppKey()) | |
| 77 | + .header("X-Event", event) | |
| 78 | + .header("X-Timestamp", String.valueOf(System.currentTimeMillis() / 1000)) | |
| 79 | + .POST(HttpRequest.BodyPublishers.ofString(payload)) | |
| 80 | + .timeout(Duration.ofSeconds(10)) | |
| 81 | + .build(); | |
| 82 | + | |
| 83 | + HttpResponse<String> response = HTTP_CLIENT.send(request, | |
| 84 | + HttpResponse.BodyHandlers.ofString()); | |
| 85 | + responseCode = response.statusCode(); | |
| 86 | + responseBody = response.body(); | |
| 87 | + if (responseCode == 200) status = 1; | |
| 88 | + } catch (Exception e) { | |
| 89 | + log.warn("Webhook 发送失败 appId={} event={} err={}", app.getId(), event, e.getMessage()); | |
| 90 | + responseBody = e.getMessage(); | |
| 91 | + } | |
| 92 | + | |
| 93 | + webhookLog.setResponseCode(responseCode); | |
| 94 | + webhookLog.setResponseBody(responseBody.length() > 500 ? responseBody.substring(0, 500) : responseBody); | |
| 95 | + webhookLog.setStatus(status); | |
| 96 | + webhookLogMapper.insert(webhookLog); | |
| 97 | + } | |
| 98 | + | |
| 99 | + /** 检查应用是否订阅了某事件 */ | |
| 100 | + private boolean isSubscribed(String webhookEvents, String event) { | |
| 101 | + if (webhookEvents == null || webhookEvents.isBlank()) return false; | |
| 102 | + try { | |
| 103 | + List<String> events = objectMapper.readValue(webhookEvents, | |
| 104 | + objectMapper.getTypeFactory().constructCollectionType(List.class, String.class)); | |
| 105 | + return events.contains(event) || events.contains("*"); | |
| 106 | + } catch (Exception e) { | |
| 107 | + return false; | |
| 108 | + } | |
| 109 | + } | |
| 110 | +} | ... | ... |
src/main/java/com/diligrp/rider/task/OrderScheduleTask.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/task/OrderScheduleTask.java | |
| 1 | +package com.diligrp.rider.task; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 5 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
| 6 | +import com.diligrp.rider.entity.Orders; | |
| 7 | +import com.diligrp.rider.mapper.OrdersMapper; | |
| 8 | +import com.diligrp.rider.service.WebhookService; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import lombok.extern.slf4j.Slf4j; | |
| 11 | +import org.springframework.scheduling.annotation.EnableScheduling; | |
| 12 | +import org.springframework.scheduling.annotation.Scheduled; | |
| 13 | +import org.springframework.stereotype.Component; | |
| 14 | + | |
| 15 | +import java.util.HashMap; | |
| 16 | +import java.util.List; | |
| 17 | +import java.util.Map; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * 订单定时任务 | |
| 21 | + * OrderhandleCron(每3秒执行) | |
| 22 | + */ | |
| 23 | +@Slf4j | |
| 24 | +@Component | |
| 25 | +@EnableScheduling | |
| 26 | +@RequiredArgsConstructor | |
| 27 | +public class OrderScheduleTask { | |
| 28 | + | |
| 29 | + private final OrdersMapper ordersMapper; | |
| 30 | + private final WebhookService webhookService; | |
| 31 | + private final ObjectMapper objectMapper; | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 超时未接单订单自动取消(30分钟) | |
| 35 | + * Orders::cancel() | |
| 36 | + * 每分钟执行一次 | |
| 37 | + */ | |
| 38 | + @Scheduled(fixedDelay = 60_000) | |
| 39 | + public void autoCancelTimeout() { | |
| 40 | + try { | |
| 41 | + long expireTime = System.currentTimeMillis() / 1000 - 30 * 60; | |
| 42 | + List<Orders> timeoutOrders = ordersMapper.selectList( | |
| 43 | + new LambdaQueryWrapper<Orders>() | |
| 44 | + .eq(Orders::getStatus, 2) | |
| 45 | + .le(Orders::getAddTime, expireTime)); | |
| 46 | + | |
| 47 | + for (Orders order : timeoutOrders) { | |
| 48 | + int updated = ordersMapper.update(null, new LambdaUpdateWrapper<Orders>() | |
| 49 | + .eq(Orders::getId, order.getId()) | |
| 50 | + .eq(Orders::getStatus, 2) | |
| 51 | + .set(Orders::getStatus, 10)); | |
| 52 | + if (updated > 0) { | |
| 53 | + log.info("订单超时自动取消 orderId={}", order.getId()); | |
| 54 | + // 通知接入方 | |
| 55 | + notifyCancel(order); | |
| 56 | + } | |
| 57 | + } | |
| 58 | + } catch (Exception e) { | |
| 59 | + log.error("超时取消任务异常", e); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * 检查骑手是否有新的指派订单(每3秒,dispatchNotice) | |
| 65 | + * 注:实时推送版本需 WebSocket,此处仅做日志记录 | |
| 66 | + * 后续接入 WebSocket 后可在此触发推送 | |
| 67 | + */ | |
| 68 | + @Scheduled(fixedDelay = 3_000) | |
| 69 | + public void checkDispatchOrders() { | |
| 70 | + // TODO: 接入 WebSocket 后在此推送给骑手 | |
| 71 | + // 目前骑手通过轮询 /api/rider/order/list?type=1 获取待接单列表 | |
| 72 | + } | |
| 73 | + | |
| 74 | + private void notifyCancel(Orders order) { | |
| 75 | + try { | |
| 76 | + if (order.getAppKey() == null || order.getAppKey().isBlank()) return; | |
| 77 | + Map<String, Object> payload = new HashMap<>(); | |
| 78 | + payload.put("event", "order.cancelled"); | |
| 79 | + payload.put("outOrderNo", order.getOutOrderNo()); | |
| 80 | + payload.put("deliveryOrderId", order.getId()); | |
| 81 | + payload.put("orderNo", order.getOrderNo()); | |
| 82 | + payload.put("status", 10); | |
| 83 | + payload.put("reason", "超时无人接单,系统自动取消"); | |
| 84 | + payload.put("timestamp", System.currentTimeMillis() / 1000); | |
| 85 | + webhookService.send("order.cancelled", order.getId(), | |
| 86 | + objectMapper.writeValueAsString(payload)); | |
| 87 | + } catch (Exception e) { | |
| 88 | + log.warn("取消通知失败 orderId={}", order.getId(), e); | |
| 89 | + } | |
| 90 | + } | |
| 91 | +} | ... | ... |
src/main/java/com/diligrp/rider/util/GeoUtil.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/util/GeoUtil.java | |
| 1 | +package com.diligrp.rider.util; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * 地理距离计算工具 | |
| 5 | + * getDistance() 函数,使用 Haversine 公式 | |
| 6 | + */ | |
| 7 | +public class GeoUtil { | |
| 8 | + | |
| 9 | + private static final double EARTH_RADIUS_KM = 6371.0; | |
| 10 | + | |
| 11 | + /** | |
| 12 | + * 计算两点之间的距离(千米) | |
| 13 | + * @param lat1 起点纬度 | |
| 14 | + * @param lng1 起点经度 | |
| 15 | + * @param lat2 终点纬度 | |
| 16 | + * @param lng2 终点经度 | |
| 17 | + * @return 距离(km),保留1位小数 | |
| 18 | + */ | |
| 19 | + public static double calcDistanceKm(double lat1, double lng1, double lat2, double lng2) { | |
| 20 | + double dLat = Math.toRadians(lat2 - lat1); | |
| 21 | + double dLng = Math.toRadians(lng2 - lng1); | |
| 22 | + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) | |
| 23 | + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) | |
| 24 | + * Math.sin(dLng / 2) * Math.sin(dLng / 2); | |
| 25 | + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
| 26 | + double distKm = EARTH_RADIUS_KM * c; | |
| 27 | + // 保留1位小数 | |
| 28 | + return Math.round(distKm * 10.0) / 10.0; | |
| 29 | + } | |
| 30 | +} | ... | ... |
src/main/java/com/diligrp/rider/util/SignUtil.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/util/SignUtil.java | |
| 1 | +package com.diligrp.rider.util; | |
| 2 | + | |
| 3 | +import javax.crypto.Mac; | |
| 4 | +import javax.crypto.spec.SecretKeySpec; | |
| 5 | +import java.nio.charset.StandardCharsets; | |
| 6 | +import java.util.UUID; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 开放平台签名工具 | |
| 10 | + * 签名算法:HmacSHA256 | |
| 11 | + * 签名字符串:appKey + timestamp + nonce,按字典序拼接后用 AppSecret 做 HMAC-SHA256 | |
| 12 | + */ | |
| 13 | +public class SignUtil { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 生成签名 | |
| 17 | + * @param appKey 应用Key | |
| 18 | + * @param timestamp 时间戳(秒) | |
| 19 | + * @param nonce 随机字符串 | |
| 20 | + * @param appSecret 应用密钥 | |
| 21 | + */ | |
| 22 | + public static String sign(String appKey, String timestamp, String nonce, String appSecret) { | |
| 23 | + // 拼接待签名字符串(字典序排列) | |
| 24 | + String[] parts = {appKey, timestamp, nonce}; | |
| 25 | + java.util.Arrays.sort(parts); | |
| 26 | + String signStr = String.join("", parts); | |
| 27 | + return hmacSha256(signStr, appSecret); | |
| 28 | + } | |
| 29 | + | |
| 30 | + public static boolean verify(String appKey, String timestamp, String nonce, | |
| 31 | + String sign, String appSecret) { | |
| 32 | + // 防重放:timestamp 与当前时间差不超过5分钟 | |
| 33 | + try { | |
| 34 | + long ts = Long.parseLong(timestamp); | |
| 35 | + long now = System.currentTimeMillis() / 1000; | |
| 36 | + if (Math.abs(now - ts) > 300) return false; | |
| 37 | + } catch (NumberFormatException e) { | |
| 38 | + return false; | |
| 39 | + } | |
| 40 | + String expected = sign(appKey, timestamp, nonce, appSecret); | |
| 41 | + return expected.equalsIgnoreCase(sign); | |
| 42 | + } | |
| 43 | + | |
| 44 | + /** 生成随机 AppKey(32位) */ | |
| 45 | + public static String generateAppKey() { | |
| 46 | + return UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase(); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** 生成随机 AppSecret(64位) */ | |
| 50 | + public static String generateAppSecret() { | |
| 51 | + return UUID.randomUUID().toString().replace("-", "") | |
| 52 | + + UUID.randomUUID().toString().replace("-", ""); | |
| 53 | + } | |
| 54 | + | |
| 55 | + private static String hmacSha256(String data, String key) { | |
| 56 | + try { | |
| 57 | + Mac mac = Mac.getInstance("HmacSHA256"); | |
| 58 | + mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); | |
| 59 | + byte[] bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); | |
| 60 | + StringBuilder sb = new StringBuilder(); | |
| 61 | + for (byte b : bytes) sb.append(String.format("%02x", b)); | |
| 62 | + return sb.toString(); | |
| 63 | + } catch (Exception e) { | |
| 64 | + throw new RuntimeException("签名计算失败", e); | |
| 65 | + } | |
| 66 | + } | |
| 67 | + | |
| 68 | + public static void main(String[] args) { | |
| 69 | + System.out.println(SignUtil.sign("8444D338919C4498","1774854151","111","7e0d80b82acb426e90a8b74ad4f23114500e65737ed34ce4803710cd2d4a324e")); | |
| 70 | + } | |
| 71 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/AdminLoginVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/AdminLoginVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +@Data | |
| 6 | +public class AdminLoginVO { | |
| 7 | + private Long id; | |
| 8 | + private String userLogin; | |
| 9 | + private String userNickname; | |
| 10 | + /** 角色:admin / substation */ | |
| 11 | + private String role; | |
| 12 | + /** 分站关联城市ID(substation角色才有) */ | |
| 13 | + private Long cityId; | |
| 14 | + private String token; | |
| 15 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/BalanceVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/BalanceVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +@Data | |
| 9 | +public class BalanceVO { | |
| 10 | + /** 当前余额 */ | |
| 11 | + private BigDecimal balance; | |
| 12 | + /** 流水列表 */ | |
| 13 | + private List<BalanceRecordVO> records; | |
| 14 | + | |
| 15 | + @Data | |
| 16 | + public static class BalanceRecordVO { | |
| 17 | + private Long id; | |
| 18 | + private Integer type; | |
| 19 | + private String typeName; | |
| 20 | + private String action; | |
| 21 | + private String orderNo; | |
| 22 | + private BigDecimal nums; | |
| 23 | + private BigDecimal total; | |
| 24 | + private String addTime; | |
| 25 | + } | |
| 26 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/CityVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/CityVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | +import java.util.List; | |
| 7 | + | |
| 8 | +@Data | |
| 9 | +public class CityVO { | |
| 10 | + private Long id; | |
| 11 | + private Long pid; | |
| 12 | + private String name; | |
| 13 | + private String areaCode; | |
| 14 | + private Integer status; | |
| 15 | + private String statusName; | |
| 16 | + private BigDecimal rate; | |
| 17 | + private Integer listOrder; | |
| 18 | + /** 下级城市(市级列表) */ | |
| 19 | + private List<CityVO> children; | |
| 20 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/DeliveryFeePlanDetailVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/DeliveryFeePlanDetailVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 4 | +import lombok.Data; | |
| 5 | +import lombok.EqualsAndHashCode; | |
| 6 | + | |
| 7 | +@Data | |
| 8 | +@EqualsAndHashCode(callSuper = true) | |
| 9 | +public class DeliveryFeePlanDetailVO extends DeliveryFeePlanVO { | |
| 10 | + | |
| 11 | + private DeliveryPricingConfigDTO config; | |
| 12 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/DeliveryFeePlanVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/DeliveryFeePlanVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +@Data | |
| 6 | +public class DeliveryFeePlanVO { | |
| 7 | + | |
| 8 | + private Long id; | |
| 9 | + | |
| 10 | + private Long cityId; | |
| 11 | + | |
| 12 | + private String name; | |
| 13 | + | |
| 14 | + private Integer isDefault; | |
| 15 | + | |
| 16 | + private Integer status; | |
| 17 | + | |
| 18 | + private Integer listOrder; | |
| 19 | + | |
| 20 | + private String remark; | |
| 21 | + | |
| 22 | + private Long createTime; | |
| 23 | + | |
| 24 | + private Long updateTime; | |
| 25 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/DeliveryFeeResultVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/DeliveryFeeResultVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 配送费计算结果 VO | |
| 9 | + * Helpsend.computed() 返回值 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +public class DeliveryFeeResultVO { | |
| 13 | + | |
| 14 | + /** 基础距离费(起步距离内) */ | |
| 15 | + private BigDecimal moneyBasic; | |
| 16 | + | |
| 17 | + /** 基础距离说明,如"(3km)" */ | |
| 18 | + private String moneyBasicTxt; | |
| 19 | + | |
| 20 | + /** 超出距离费 */ | |
| 21 | + private BigDecimal moneyDistance; | |
| 22 | + | |
| 23 | + /** 超出距离说明,如"(2.5km)" */ | |
| 24 | + private String moneyDistanceTxt; | |
| 25 | + | |
| 26 | + /** 超重费 */ | |
| 27 | + private BigDecimal moneyWeight; | |
| 28 | + | |
| 29 | + /** 超重说明 */ | |
| 30 | + private String moneyWeightTxt; | |
| 31 | + | |
| 32 | + /** 件数费 */ | |
| 33 | + private BigDecimal moneyPiece; | |
| 34 | + | |
| 35 | + /** 件数费说明 */ | |
| 36 | + private String moneyPieceTxt; | |
| 37 | + | |
| 38 | + /** 时段附加费 */ | |
| 39 | + private BigDecimal moneyTime; | |
| 40 | + | |
| 41 | + /** 保底费用 */ | |
| 42 | + private BigDecimal minFee; | |
| 43 | + | |
| 44 | + /** 是否命中保底 */ | |
| 45 | + private Integer minFeeApplied; | |
| 46 | + | |
| 47 | + /** 合计配送费 */ | |
| 48 | + private BigDecimal totalFee; | |
| 49 | + | |
| 50 | + /** 实际配送距离(km) */ | |
| 51 | + private BigDecimal distance; | |
| 52 | + | |
| 53 | + /** 实际重量(kg) */ | |
| 54 | + private BigDecimal weight; | |
| 55 | + | |
| 56 | + /** 实际件数 */ | |
| 57 | + private Integer pieces; | |
| 58 | + | |
| 59 | + /** 预计送达时间(分钟) */ | |
| 60 | + private Integer estimatedMinutes; | |
| 61 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/DeliveryOrderCreateVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/DeliveryOrderCreateVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 创建配送订单返回 VO | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +public class DeliveryOrderCreateVO { | |
| 12 | + /** 配送中台订单ID */ | |
| 13 | + private Long deliveryOrderId; | |
| 14 | + /** 配送中台订单号 */ | |
| 15 | + private String orderNo; | |
| 16 | + /** 外部系统订单号 */ | |
| 17 | + private String outOrderNo; | |
| 18 | + /** 配送费明细:基础费 */ | |
| 19 | + private BigDecimal moneyBasic; | |
| 20 | + /** 配送费明细:超距费 */ | |
| 21 | + private BigDecimal moneyDistance; | |
| 22 | + /** 配送费明细:时段附加费 */ | |
| 23 | + private BigDecimal moneyTime; | |
| 24 | + /** 总配送费 */ | |
| 25 | + private BigDecimal totalFee; | |
| 26 | + /** 配送距离(km) */ | |
| 27 | + private BigDecimal distance; | |
| 28 | + /** 预计送达时间(分钟) */ | |
| 29 | + private Integer estimatedMinutes; | |
| 30 | + /** 订单状态:2=待接单 */ | |
| 31 | + private Integer status; | |
| 32 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/NearbyRiderVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/NearbyRiderVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 附近骑手信息 VO | |
| 7 | + */ | |
| 8 | +@Data | |
| 9 | +public class NearbyRiderVO { | |
| 10 | + /** 经度 */ | |
| 11 | + private String lng; | |
| 12 | + /** 纬度 */ | |
| 13 | + private String lat; | |
| 14 | + /** 与查询点的距离(km) */ | |
| 15 | + private Double distance; | |
| 16 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/OrderVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/OrderVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | +@Data | |
| 10 | +public class OrderVO { | |
| 11 | + private Long id; | |
| 12 | + private String orderNo; | |
| 13 | + private String outOrderNo; | |
| 14 | + private Integer type; | |
| 15 | + private String typeName; | |
| 16 | + private Integer status; | |
| 17 | + private String statusName; | |
| 18 | + @JsonProperty("fName") | |
| 19 | + private String fName; | |
| 20 | + @JsonProperty("fAddr") | |
| 21 | + private String fAddr; | |
| 22 | + @JsonProperty("fLng") | |
| 23 | + private String fLng; | |
| 24 | + @JsonProperty("fLat") | |
| 25 | + private String fLat; | |
| 26 | + @JsonProperty("tName") | |
| 27 | + private String tName; | |
| 28 | + @JsonProperty("tAddr") | |
| 29 | + private String tAddr; | |
| 30 | + @JsonProperty("tLng") | |
| 31 | + private String tLng; | |
| 32 | + @JsonProperty("tLat") | |
| 33 | + private String tLat; | |
| 34 | + private String recipName; | |
| 35 | + private String recipPhone; | |
| 36 | + private BigDecimal income; | |
| 37 | + private Integer isTrans; | |
| 38 | + private String addTime; | |
| 39 | + private String grapTime; | |
| 40 | + private String pickTime; | |
| 41 | + private String completeTime; | |
| 42 | + private String transTime; | |
| 43 | + private Object extra; | |
| 44 | + /** 货物清单 */ | |
| 45 | + private List<Object> items; | |
| 46 | + /** 整单货物备注 */ | |
| 47 | + private String itemRemark; | |
| 48 | + /** 订单备注(from extra.remark) */ | |
| 49 | + private String des; | |
| 50 | + /** 预约标识 */ | |
| 51 | + private Integer isPre; | |
| 52 | + /** 服务时间 */ | |
| 53 | + private String service_time; | |
| 54 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/RiderMonthCountVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/RiderMonthCountVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +@Data | |
| 6 | +public class RiderMonthCountVO { | |
| 7 | + /** 月份标题,如"3月",本月显示"本月" */ | |
| 8 | + private String title; | |
| 9 | + /** 日期区间描述,如"03-01至03-25" */ | |
| 10 | + private String des; | |
| 11 | + /** 完成订单数 */ | |
| 12 | + private Integer orders; | |
| 13 | + /** 转单数 */ | |
| 14 | + private Integer transfers; | |
| 15 | + /** 配送距离(米) */ | |
| 16 | + private Long distance; | |
| 17 | + /** 统计月份时间戳 */ | |
| 18 | + private Long time; | |
| 19 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/RiderTodayCountVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/RiderTodayCountVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +@Data | |
| 6 | +public class RiderTodayCountVO { | |
| 7 | + /** 今日完成订单数 */ | |
| 8 | + private Integer orders; | |
| 9 | + /** 今日转单数 */ | |
| 10 | + private Integer transfers; | |
| 11 | + /** 今日配送距离(米) */ | |
| 12 | + private Long distance; | |
| 13 | + /** 今日抢单数(今日接单总数) */ | |
| 14 | + private Integer graps; | |
| 15 | +} | ... | ... |
src/main/java/com/diligrp/rider/vo/RiderVO.java
0 → 100644
| 1 | +++ a/src/main/java/com/diligrp/rider/vo/RiderVO.java | |
| 1 | +package com.diligrp.rider.vo; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | + | |
| 7 | +@Data | |
| 8 | +public class RiderVO { | |
| 9 | + private Long id; | |
| 10 | + private String mobile; | |
| 11 | + private String userNickname; | |
| 12 | + private String avatar; | |
| 13 | + private Integer type; | |
| 14 | + private String typeName; | |
| 15 | + private Integer userStatus; | |
| 16 | + private Integer status; | |
| 17 | + private String statusName; | |
| 18 | + private BigDecimal balance; | |
| 19 | + private Integer isRest; | |
| 20 | + private Long cityId; | |
| 21 | + private String token; | |
| 22 | +} | ... | ... |
src/main/resources/application.yml
0 → 100644
| 1 | +++ a/src/main/resources/application.yml | |
| 1 | +server: | |
| 2 | + port: 8080 | |
| 3 | + | |
| 4 | +spring: | |
| 5 | + datasource: | |
| 6 | + driver-class-name: com.mysql.cj.jdbc.Driver | |
| 7 | + url: jdbc:mysql://mysql.diligrp.com:3306/dili_rider?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai | |
| 8 | + username: root | |
| 9 | + password: OTM0NjAwMTMyMjZlNzgy | |
| 10 | + data: | |
| 11 | + redis: | |
| 12 | + host: redis.diligrp.com | |
| 13 | + port: 6379 | |
| 14 | + password: | |
| 15 | + database: 0 | |
| 16 | + timeout: 3000ms | |
| 17 | + | |
| 18 | +mybatis-plus: | |
| 19 | + mapper-locations: classpath:mapper/*.xml | |
| 20 | + type-aliases-package: com.diligrp.rider.entity | |
| 21 | + configuration: | |
| 22 | + map-underscore-to-camel-case: true | |
| 23 | + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl | |
| 24 | + global-config: | |
| 25 | + db-config: | |
| 26 | + logic-delete-field: isDel | |
| 27 | + logic-delete-value: 1 | |
| 28 | + logic-not-delete-value: 0 | |
| 29 | + | |
| 30 | +jwt: | |
| 31 | + secret: diligrp-rider-secret-key-2024-please-change-this | |
| 32 | + expire: 604800 # 7天,单位秒 | |
| 33 | + | |
| 34 | +logging: | |
| 35 | + level: | |
| 36 | + com.diligrp.rider: debug | ... | ... |
src/main/resources/data-init.sql
0 → 100644
| 1 | +++ a/src/main/resources/data-init.sql | |
| 1 | +-- 外卖配送中台 初始化数据脚本 | |
| 2 | +-- 执行前请确保已执行 schema.sql 建表 | |
| 3 | +-- 执行方式:mysql -u root -p dili_rider < data-init.sql | |
| 4 | + | |
| 5 | +USE dili_rider; | |
| 6 | + | |
| 7 | +-- ============================================================ | |
| 8 | +-- 1. 城市数据(省+市两级) | |
| 9 | +-- ============================================================ | |
| 10 | +INSERT INTO `city` (`id`, `pid`, `name`, `area_code`, `status`, `rate`, `list_order`, `config`) VALUES | |
| 11 | +(1, 0, '广东省', '44000000', 0, 0.00, 1, NULL), | |
| 12 | +(2, 1, '广州市', '44010000', 1, 10.00, 1, | |
| 13 | +'{ | |
| 14 | + "type": [6], | |
| 15 | + "type6": { | |
| 16 | + "feeMode": 2, | |
| 17 | + "fixMoney": 0, | |
| 18 | + "distanceSwitch": 1, | |
| 19 | + "distanceBasic": 3, | |
| 20 | + "distanceBasicMoney": 4.00, | |
| 21 | + "distanceMoreMoney": 1.50, | |
| 22 | + "distanceMode": 1, | |
| 23 | + "distanceType": 1, | |
| 24 | + "weightSwitch": 0, | |
| 25 | + "weightBasic": 0, | |
| 26 | + "weightBasicMoney": 0, | |
| 27 | + "weightMoreMoney": 0, | |
| 28 | + "weightType": 1, | |
| 29 | + "times": [ | |
| 30 | + {"start": 0, "end": 480, "isOpen": 0, "money": 0}, | |
| 31 | + {"start": 480, "end": 1320, "isOpen": 1, "money": 0}, | |
| 32 | + {"start": 1320, "end": 1440, "isOpen": 1, "money": 2} | |
| 33 | + ] | |
| 34 | + }, | |
| 35 | + "distanceBasic": 3, | |
| 36 | + "distanceBasicTime": 30, | |
| 37 | + "distanceMoreTime": 10 | |
| 38 | +}'), | |
| 39 | +(3, 1, '深圳市', '44030000', 1, 10.00, 2, | |
| 40 | +'{ | |
| 41 | + "type": [6], | |
| 42 | + "type6": { | |
| 43 | + "feeMode": 1, | |
| 44 | + "fixMoney": 5.00, | |
| 45 | + "distanceSwitch": 0, | |
| 46 | + "distanceBasic": 0, | |
| 47 | + "distanceBasicMoney": 0, | |
| 48 | + "distanceMoreMoney": 0, | |
| 49 | + "distanceMode": 1, | |
| 50 | + "distanceType": 1, | |
| 51 | + "weightSwitch": 0, | |
| 52 | + "weightBasic": 0, | |
| 53 | + "weightBasicMoney": 0, | |
| 54 | + "weightMoreMoney": 0, | |
| 55 | + "weightType": 1, | |
| 56 | + "times": [] | |
| 57 | + }, | |
| 58 | + "distanceBasic": 3, | |
| 59 | + "distanceBasicTime": 25, | |
| 60 | + "distanceMoreTime": 8 | |
| 61 | +}'); | |
| 62 | + | |
| 63 | +-- ============================================================ | |
| 64 | +-- 2. 分站管理员(每个已开通城市一个) | |
| 65 | +-- 默认密码均为 admin123(MD5: 0192023a7bbd73250516f069df18b500) | |
| 66 | +-- ============================================================ | |
| 67 | +INSERT INTO `substation` (`city_id`, `user_login`, `user_nickname`, `user_pass`, `mobile`, `user_status`, `create_time`) VALUES | |
| 68 | +(2, 'gz_admin', '广州分站管理员', '0192023a7bbd73250516f069df18b500', '13800000001', 1, UNIX_TIMESTAMP()), | |
| 69 | +(3, 'sz_admin', '深圳分站管理员', '0192023a7bbd73250516f069df18b500', '13800000002', 1, UNIX_TIMESTAMP()); | |
| 70 | + | |
| 71 | +-- ============================================================ | |
| 72 | +-- 3. 骑手等级配置(广州) | |
| 73 | +-- ============================================================ | |
| 74 | +INSERT INTO `rider_level` (`city_id`, `level_id`, `name`, `is_default`, `trans_nums`, | |
| 75 | + `run_fee_mode`, `run_fix_money`, `run_rate`, `distance_basic`, `distance_basic_money`, | |
| 76 | + `distance_more_money`, `distance_max_money`, `work_fee_mode`, `work_fix_money`, `work_rate`) VALUES | |
| 77 | +-- 普通骑手:按比例拿配送费的70% | |
| 78 | +(2, 1, '普通骑手', 1, 3, | |
| 79 | + 2, 0.00, 70.00, 0, 0.00, 0.00, 0.00, | |
| 80 | + 1, 5.00, 0.00), | |
| 81 | +-- 资深骑手:按比例拿配送费的80% | |
| 82 | +(2, 2, '资深骑手', 0, 5, | |
| 83 | + 2, 0.00, 80.00, 0, 0.00, 0.00, 0.00, | |
| 84 | + 1, 6.00, 0.00); | |
| 85 | + | |
| 86 | +-- 深圳骑手等级 | |
| 87 | +INSERT INTO `rider_level` (`city_id`, `level_id`, `name`, `is_default`, `trans_nums`, | |
| 88 | + `run_fee_mode`, `run_fix_money`, `run_rate`, `distance_basic`, `distance_basic_money`, | |
| 89 | + `distance_more_money`, `distance_max_money`, `work_fee_mode`, `work_fix_money`, `work_rate`) VALUES | |
| 90 | +(3, 1, '普通骑手', 1, 3, | |
| 91 | + 2, 0.00, 70.00, 0, 0.00, 0.00, 0.00, | |
| 92 | + 1, 5.00, 0.00); | |
| 93 | + | |
| 94 | +-- ============================================================ | |
| 95 | +-- 4. 示例骑手账号 | |
| 96 | +-- 默认密码均为 test1234(MD5: 16d7a4fca7442dda3ad93c9a726597e4) | |
| 97 | +-- ============================================================ | |
| 98 | +INSERT INTO `rider` (`mobile`, `user_login`, `user_nickname`, `user_pass`, `city_id`, `level_id`, | |
| 99 | + `type`, `user_status`, `balance`, `is_rest`, `create_time`) VALUES | |
| 100 | +('13900000001', 'phone_rider001', '张骑手', '16d7a4fca7442dda3ad93c9a726597e4', 2, 1, 1, 1, 0.00, 0, UNIX_TIMESTAMP()), | |
| 101 | +('13900000002', 'phone_rider002', '李骑手', '16d7a4fca7442dda3ad93c9a726597e4', 2, 1, 2, 1, 0.00, 0, UNIX_TIMESTAMP()); | |
| 102 | + | |
| 103 | +-- ============================================================ | |
| 104 | +-- 5. 示例商家店铺 | |
| 105 | +-- ============================================================ | |
| 106 | +INSERT INTO `merchant_store` (`name`, `thumb`, `city_id`, `address`, `lng`, `lat`, | |
| 107 | + `operating_state`, `automatic_order`, `shipping_type`, `free_shipping`, `up_to_send`, | |
| 108 | + `open_date`, `open_time`, `about`, `list_order`, `is_del`, `add_time`) VALUES | |
| 109 | +('测试餐厅', '', 2, '广州市天河区测试路1号', '113.330010', '23.132891', | |
| 110 | + 1, 1, 1, 30.00, 15.00, | |
| 111 | + '[1,2,3,4,5,6,7]', '["09:00","22:00"]', '测试店铺,仅供开发调试', 1, 0, UNIX_TIMESTAMP()); | |
| 112 | + | |
| 113 | +-- 创建商家账号 | |
| 114 | +INSERT INTO `merchant_users` (`store_id`, `mobile`, `user_nickname`, `user_status`, `type`, `create_time`) | |
| 115 | +VALUES (LAST_INSERT_ID(), '13700000001', '测试餐厅老板', 1, 1, UNIX_TIMESTAMP()); | |
| 116 | + | |
| 117 | +-- ============================================================ | |
| 118 | +-- 6. 开放平台示例应用 | |
| 119 | +-- ============================================================ | |
| 120 | +INSERT INTO `open_app` (`app_name`, `app_key`, `app_secret`, `store_id`, `status`, | |
| 121 | + `webhook_url`, `webhook_events`, `remark`, `create_time`) VALUES | |
| 122 | +('内部电商系统', 'TESTAPPKEY00001', 'testsecret0000000000000000000000000000000000000000000000000001', 0, 1, | |
| 123 | + '', '["order.paid","order.completed","order.cancelled"]', '用于测试的内部应用', UNIX_TIMESTAMP()); | |
| 124 | + | |
| 125 | +-- ============================================================ | |
| 126 | +-- 完成提示 | |
| 127 | +-- ============================================================ | |
| 128 | +SELECT '初始化完成!' AS 提示; | |
| 129 | +SELECT '骑手登录账号: 13900000001 / 13900000002,密码: test1234' AS 骑手账号; | |
| 130 | +SELECT '分站管理员: gz_admin / sz_admin,密码: admin123' AS 分站账号; | |
| 131 | +SELECT '商家手机号: 13700000001' AS 商家账号; | |
| 132 | +SELECT '开放平台 AppKey: TESTAPPKEY00001' AS 开放平台; | |
| 133 | + | |
| 134 | +-- ============================================================ | |
| 135 | +-- 7. 超级管理员账号 | |
| 136 | +-- 默认密码:admin123(MD5: 0192023a7bbd73250516f069df18b500) | |
| 137 | +-- ============================================================ | |
| 138 | +INSERT INTO `admin_user` (`user_login`, `user_pass`, `user_nickname`, `user_status`, `create_time`) VALUES | |
| 139 | +('admin', '0192023a7bbd73250516f069df18b500', '超级管理员', 1, UNIX_TIMESTAMP()); | |
| 140 | + | |
| 141 | +SELECT '超级管理员: admin / admin123(role=admin)' AS 超管账号; | ... | ... |
src/main/resources/mapper/OrdersMapper.xml
0 → 100644
| 1 | +++ a/src/main/resources/mapper/OrdersMapper.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
| 3 | +<mapper namespace="com.diligrp.rider.mapper.OrdersMapper"> | |
| 4 | + | |
| 5 | + <select id="countTodayTrans" resultType="int"> | |
| 6 | + SELECT COUNT(*) | |
| 7 | + FROM orders | |
| 8 | + WHERE old_rider_id = #{riderId} | |
| 9 | + AND is_trans = 1 | |
| 10 | + AND trans_time >= #{todayStart} | |
| 11 | + </select> | |
| 12 | + | |
| 13 | +</mapper> | ... | ... |
src/main/resources/mapper/RiderOrderCountMapper.xml
0 → 100644
| 1 | +++ a/src/main/resources/mapper/RiderOrderCountMapper.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
| 3 | +<mapper namespace="com.diligrp.rider.mapper.RiderOrderCountMapper"> | |
| 4 | + | |
| 5 | + <!-- | |
| 6 | + 骑手当日统计 upsert:有则累加,无则插入 | |
| 7 | + --> | |
| 8 | + <insert id="upsertCount"> | |
| 9 | + INSERT INTO rider_order_count (uid, count_date, orders, distance, transfers) | |
| 10 | + VALUES (#{uid}, #{countDate}, #{orders}, #{distance}, #{transfers}) | |
| 11 | + ON DUPLICATE KEY UPDATE | |
| 12 | + orders = orders + VALUES(orders), | |
| 13 | + distance = distance + VALUES(distance), | |
| 14 | + transfers = transfers + VALUES(transfers) | |
| 15 | + </insert> | |
| 16 | + | |
| 17 | +</mapper> | ... | ... |
src/main/resources/mapper/RiderOrderRefuseMapper.xml
0 → 100644
| 1 | +++ a/src/main/resources/mapper/RiderOrderRefuseMapper.xml | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
| 3 | +<mapper namespace="com.diligrp.rider.mapper.RiderOrderRefuseMapper"> | |
| 4 | + | |
| 5 | + <select id="selectRefuseOrderIds" resultType="long"> | |
| 6 | + SELECT oid | |
| 7 | + FROM rider_orders_refuse | |
| 8 | + WHERE rider_id = #{riderId} | |
| 9 | + </select> | |
| 10 | + | |
| 11 | +</mapper> | ... | ... |
src/main/resources/schema.sql
0 → 100644
| 1 | +++ a/src/main/resources/schema.sql | |
| 1 | +-- 外卖骑手配送模块 数据库建表脚本 | |
| 2 | +-- 数据库:dili_rider | |
| 3 | + | |
| 4 | +CREATE DATABASE IF NOT EXISTS dili_rider DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; | |
| 5 | +USE dili_rider; | |
| 6 | + | |
| 7 | +-- 骑手信息表 | |
| 8 | +CREATE TABLE `rider` ( | |
| 9 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '骑手ID', | |
| 10 | + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号', | |
| 11 | + `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录名', | |
| 12 | + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', | |
| 13 | + `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)', | |
| 14 | + `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像', | |
| 15 | + `avatar_thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像缩略图', | |
| 16 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID', | |
| 17 | + `level_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '等级ID', | |
| 18 | + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=兼职 2=全职', | |
| 19 | + `user_status` TINYINT NOT NULL DEFAULT 2 COMMENT '审核状态:0=拒绝 1=通过 2=待审核', | |
| 20 | + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0=禁用 1=正常', | |
| 21 | + `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '余额(兼职用)', | |
| 22 | + `is_rest` TINYINT NOT NULL DEFAULT 0 COMMENT '是否休息:0=否 1=是', | |
| 23 | + `id_no` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '身份证号', | |
| 24 | + `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '手持身份证照片', | |
| 25 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '注册时间', | |
| 26 | + `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=正常 1=已删除', | |
| 27 | + PRIMARY KEY (`id`), | |
| 28 | + UNIQUE KEY `uk_mobile` (`mobile`), | |
| 29 | + KEY `idx_city_id` (`city_id`) | |
| 30 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手信息表'; | |
| 31 | + | |
| 32 | +-- 骑手等级配置表 | |
| 33 | +CREATE TABLE `rider_level` ( | |
| 34 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 35 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID', | |
| 36 | + `level_id` INT NOT NULL DEFAULT 0 COMMENT '等级编号', | |
| 37 | + `name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '等级名称', | |
| 38 | + `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认', | |
| 39 | + `trans_nums` INT NOT NULL DEFAULT 0 COMMENT '每日转单次数上限', | |
| 40 | + `run_fee_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '跑腿收入模式:1=固定 2=比例 3=距离', | |
| 41 | + `run_fix_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '跑腿固定金额', | |
| 42 | + `run_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '跑腿比例(%)', | |
| 43 | + `distance_basic` INT NOT NULL DEFAULT 0 COMMENT '起始距离(米)', | |
| 44 | + `distance_basic_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础配送费', | |
| 45 | + `distance_more_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '超出每公里费', | |
| 46 | + `distance_max_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '最高配送费上限', | |
| 47 | + `work_fee_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '办事收入模式:1=固定 2=比例', | |
| 48 | + `work_fix_money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '办事固定金额', | |
| 49 | + `work_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '办事比例(%)', | |
| 50 | + PRIMARY KEY (`id`), | |
| 51 | + UNIQUE KEY `uk_city_level` (`city_id`, `level_id`), | |
| 52 | + KEY `idx_city_default` (`city_id`, `is_default`) | |
| 53 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手等级配置表'; | |
| 54 | + | |
| 55 | +-- 骑手实时位置表 | |
| 56 | +CREATE TABLE `rider_location` ( | |
| 57 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 58 | + `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID', | |
| 59 | + `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度', | |
| 60 | + `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度', | |
| 61 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间', | |
| 62 | + PRIMARY KEY (`id`), | |
| 63 | + UNIQUE KEY `uk_uid` (`uid`) | |
| 64 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手实时位置表'; | |
| 65 | + | |
| 66 | +-- 骑手余额流水表 | |
| 67 | +CREATE TABLE `rider_balance` ( | |
| 68 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 69 | + `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID', | |
| 70 | + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=收入 2=提现', | |
| 71 | + `action` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '动作标识', | |
| 72 | + `action_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联ID', | |
| 73 | + `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号', | |
| 74 | + `nums` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '变动金额', | |
| 75 | + `total` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '变动后余额', | |
| 76 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '记录时间', | |
| 77 | + PRIMARY KEY (`id`), | |
| 78 | + KEY `idx_uid` (`uid`), | |
| 79 | + KEY `idx_action_id` (`action_id`) | |
| 80 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手余额流水表'; | |
| 81 | + | |
| 82 | +-- 骑手订单统计表 | |
| 83 | +CREATE TABLE `rider_order_count` ( | |
| 84 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 85 | + `uid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID', | |
| 86 | + `count_date` INT NOT NULL COMMENT '统计日期yyyyMMdd', | |
| 87 | + `orders` INT NOT NULL DEFAULT 0 COMMENT '完成订单数', | |
| 88 | + `transfers` INT NOT NULL DEFAULT 0 COMMENT '转单数', | |
| 89 | + `distance` BIGINT NOT NULL DEFAULT 0 COMMENT '配送距离(米)', | |
| 90 | + PRIMARY KEY (`id`), | |
| 91 | + UNIQUE KEY `uk_uid_date` (`uid`, `count_date`) | |
| 92 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手订单统计表'; | |
| 93 | + | |
| 94 | +-- 骑手拒单记录表 | |
| 95 | +CREATE TABLE `rider_orders_refuse` ( | |
| 96 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 97 | + `rider_id` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID', | |
| 98 | + `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID', | |
| 99 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '拒单时间', | |
| 100 | + PRIMARY KEY (`id`), | |
| 101 | + KEY `idx_rider_id` (`rider_id`), | |
| 102 | + KEY `idx_oid` (`oid`) | |
| 103 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手拒单记录表'; | |
| 104 | + | |
| 105 | +-- 订单主表 | |
| 106 | +CREATE TABLE `orders` ( | |
| 107 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID', | |
| 108 | + `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号', | |
| 109 | + `uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID', | |
| 110 | + `rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '骑手ID', | |
| 111 | + `old_rider_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '原始骑手ID', | |
| 112 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID', | |
| 113 | + `type` TINYINT NOT NULL DEFAULT 6 COMMENT '订单类型:6=外卖配送', | |
| 114 | + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1待支付 2已支付 3已接单 4服务中 6已完成 7退款申请 8退款成功 9退款拒绝 10已取消', | |
| 115 | + `pay_type` TINYINT NOT NULL DEFAULT 0 COMMENT '支付类型:1=支付宝 2=微信', | |
| 116 | + `money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额', | |
| 117 | + `money_delivery` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '配送费', | |
| 118 | + `money_total` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '实付总金额', | |
| 119 | + `rider_income` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '骑手收入', | |
| 120 | + `substation_income` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '站点收入', | |
| 121 | + `is_income` TINYINT NOT NULL DEFAULT 0 COMMENT '结算状态:0=未结算 1=待结算 2=已结算', | |
| 122 | + `is_trans` TINYINT NOT NULL DEFAULT 0 COMMENT '转单状态:0=未转 1=通过 2=申请中 3=拒绝', | |
| 123 | + `code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '完成码', | |
| 124 | + `f_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '起点名称', | |
| 125 | + `f_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '起点地址', | |
| 126 | + `f_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '起点经度', | |
| 127 | + `f_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '起点纬度', | |
| 128 | + `t_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '终点名称', | |
| 129 | + `t_addr` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '终点地址', | |
| 130 | + `t_lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终点经度', | |
| 131 | + `t_lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终点纬度', | |
| 132 | + `recip_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收件人姓名', | |
| 133 | + `recip_phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '收件人电话', | |
| 134 | + `extra` TEXT COMMENT '附加信息JSON(距离、重量等)', | |
| 135 | + `thumbs` TEXT COMMENT '取件照片JSON数组', | |
| 136 | + `store_oid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺订单ID', | |
| 137 | + `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除', | |
| 138 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '下单时间', | |
| 139 | + `pay_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付时间', | |
| 140 | + `grap_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '接单时间', | |
| 141 | + `pick_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '取件时间', | |
| 142 | + `complete_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '完成时间', | |
| 143 | + `trans_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '转单时间', | |
| 144 | + PRIMARY KEY (`id`), | |
| 145 | + UNIQUE KEY `uk_order_no` (`order_no`), | |
| 146 | + KEY `idx_rider_id` (`rider_id`), | |
| 147 | + KEY `idx_uid` (`uid`), | |
| 148 | + KEY `idx_city_status` (`city_id`, `status`), | |
| 149 | + KEY `idx_old_rider_trans` (`old_rider_id`, `is_trans`, `trans_time`) | |
| 150 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表'; | |
| 151 | + | |
| 152 | +-- 城市表(配送中台核心配置) | |
| 153 | +CREATE TABLE `city` ( | |
| 154 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '城市ID', | |
| 155 | + `pid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父级ID,0=省级', | |
| 156 | + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '城市名称', | |
| 157 | + `area_code` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '行政区划码', | |
| 158 | + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未开通 1=已开通', | |
| 159 | + `rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '平台抽成比例(%)', | |
| 160 | + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序', | |
| 161 | + PRIMARY KEY (`id`), | |
| 162 | + KEY `idx_pid_order` (`pid`, `list_order`), | |
| 163 | + KEY `idx_area_code` (`area_code`) | |
| 164 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='城市表'; | |
| 165 | + | |
| 166 | +-- 配送计价方案主表 | |
| 167 | +CREATE TABLE `delivery_fee_plan` ( | |
| 168 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 169 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '租户ID', | |
| 170 | + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '方案名称', | |
| 171 | + `is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认方案', | |
| 172 | + `min_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '保底费用', | |
| 173 | + `distance_basic` DECIMAL(10,2) NOT NULL DEFAULT 3.00 COMMENT '预计送达基础距离(km)', | |
| 174 | + `distance_basic_time` INT NOT NULL DEFAULT 30 COMMENT '预计送达基础时间(分钟)', | |
| 175 | + `distance_more_time` INT NOT NULL DEFAULT 10 COMMENT '预计送达超出每km增加时间(分钟)', | |
| 176 | + `rider_distance` DECIMAL(10,2) NOT NULL DEFAULT 3.00 COMMENT '附近骑手展示范围(km)', | |
| 177 | + `rider_time` INT NOT NULL DEFAULT 0 COMMENT '预计接单时间(分钟)', | |
| 178 | + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用', | |
| 179 | + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序', | |
| 180 | + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注', | |
| 181 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 182 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 183 | + PRIMARY KEY (`id`), | |
| 184 | + KEY `idx_city_default` (`city_id`, `is_default`), | |
| 185 | + KEY `idx_city_status` (`city_id`, `status`) | |
| 186 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价方案主表'; | |
| 187 | + | |
| 188 | +-- 配送计价维度主配置表 | |
| 189 | +CREATE TABLE `delivery_fee_plan_dimension` ( | |
| 190 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 191 | + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID', | |
| 192 | + `dimension_type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '维度类型:base/distance/weight/piece/time', | |
| 193 | + `enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用', | |
| 194 | + `base_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础费', | |
| 195 | + `start_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起步里程(km)', | |
| 196 | + `start_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起步费用', | |
| 197 | + `first_weight` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '首重(kg)', | |
| 198 | + `first_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '首重费用', | |
| 199 | + `unit_weight_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '续重单价', | |
| 200 | + `cap_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '封顶费用', | |
| 201 | + `extra_json` TEXT COMMENT '扩展配置', | |
| 202 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 203 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 204 | + PRIMARY KEY (`id`), | |
| 205 | + UNIQUE KEY `uk_plan_dimension` (`plan_id`, `dimension_type`) | |
| 206 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价维度主配置表'; | |
| 207 | + | |
| 208 | +-- 里程阶梯表 | |
| 209 | +CREATE TABLE `delivery_fee_plan_distance_step` ( | |
| 210 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 211 | + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID', | |
| 212 | + `end_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '结束里程(km)', | |
| 213 | + `unit_distance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '每档里程(km)', | |
| 214 | + `unit_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '每档加价', | |
| 215 | + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序', | |
| 216 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 217 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 218 | + PRIMARY KEY (`id`), | |
| 219 | + KEY `idx_plan_order` (`plan_id`, `list_order`) | |
| 220 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价里程阶梯表'; | |
| 221 | + | |
| 222 | +-- 件数区间表 | |
| 223 | +CREATE TABLE `delivery_fee_plan_piece_rule` ( | |
| 224 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 225 | + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID', | |
| 226 | + `start_piece` INT NOT NULL DEFAULT 0 COMMENT '起始件数', | |
| 227 | + `end_piece` INT NOT NULL DEFAULT 0 COMMENT '结束件数', | |
| 228 | + `fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '费用', | |
| 229 | + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序', | |
| 230 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 231 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 232 | + PRIMARY KEY (`id`), | |
| 233 | + KEY `idx_plan_order` (`plan_id`, `list_order`) | |
| 234 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价件数区间表'; | |
| 235 | + | |
| 236 | +-- 时段附加费表 | |
| 237 | +CREATE TABLE `delivery_fee_plan_time_rule` ( | |
| 238 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 239 | + `plan_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '方案ID', | |
| 240 | + `start_minute` INT NOT NULL DEFAULT 0 COMMENT '开始分钟', | |
| 241 | + `end_minute` INT NOT NULL DEFAULT 0 COMMENT '结束分钟', | |
| 242 | + `fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '附加费', | |
| 243 | + `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用', | |
| 244 | + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序', | |
| 245 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 246 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 247 | + PRIMARY KEY (`id`), | |
| 248 | + KEY `idx_plan_order` (`plan_id`, `list_order`) | |
| 249 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配送计价时段附加费表'; | |
| 250 | + | |
| 251 | +-- 分站管理员表(每城市一个,管理本城市骑手和订单) | |
| 252 | +CREATE TABLE `substation` ( | |
| 253 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分站ID', | |
| 254 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '管理城市ID', | |
| 255 | + `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录账号', | |
| 256 | + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', | |
| 257 | + `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)', | |
| 258 | + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号', | |
| 259 | + `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像', | |
| 260 | + `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', | |
| 261 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', | |
| 262 | + PRIMARY KEY (`id`), | |
| 263 | + UNIQUE KEY `uk_user_login` (`user_login`) | |
| 264 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分站管理员表(一个城市/租户下可有多个管理员)'; | |
| 265 | + | |
| 266 | +-- 商家入驻申请表 | |
| 267 | +CREATE TABLE `merchant_enter` ( | |
| 268 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 269 | + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '联系人姓名', | |
| 270 | + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号', | |
| 271 | + `store_name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '店铺名称', | |
| 272 | + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=商家入驻 2=骑手入驻 3=商务合作', | |
| 273 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '城市ID', | |
| 274 | + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注', | |
| 275 | + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未处理 1=已通过 -1=已拒绝', | |
| 276 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请时间', | |
| 277 | + PRIMARY KEY (`id`), | |
| 278 | + KEY `idx_status_type` (`status`, `type`), | |
| 279 | + KEY `idx_city_id` (`city_id`) | |
| 280 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家入驻申请表'; | |
| 281 | + | |
| 282 | +-- 商家账号表 | |
| 283 | +CREATE TABLE `merchant_users` ( | |
| 284 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 285 | + `store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺ID', | |
| 286 | + `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号(登录账号)', | |
| 287 | + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', | |
| 288 | + `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', | |
| 289 | + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1=商家', | |
| 290 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', | |
| 291 | + PRIMARY KEY (`id`), | |
| 292 | + UNIQUE KEY `uk_mobile` (`mobile`), | |
| 293 | + KEY `idx_store_id` (`store_id`) | |
| 294 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家账号表'; | |
| 295 | + | |
| 296 | +-- 商家店铺表 | |
| 297 | +CREATE TABLE `merchant_store` ( | |
| 298 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '店铺ID', | |
| 299 | + `name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '店铺名称', | |
| 300 | + `thumb` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封面图', | |
| 301 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属城市ID', | |
| 302 | + `address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '店铺地址', | |
| 303 | + `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度', | |
| 304 | + `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度', | |
| 305 | + `operating_state` TINYINT NOT NULL DEFAULT 1 COMMENT '营业状态:0=打烊 1=营业', | |
| 306 | + `automatic_order` TINYINT NOT NULL DEFAULT 0 COMMENT '自动接单:0=否 1=是', | |
| 307 | + `shipping_type` TINYINT NOT NULL DEFAULT 1 COMMENT '配送类型:1=外卖配送 2=到店自提', | |
| 308 | + `free_shipping` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '免运费门槛,0=不免', | |
| 309 | + `up_to_send` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '起送金额,0=不限', | |
| 310 | + `open_date` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '营业日期JSON,如[1,2,3,4,5]', | |
| 311 | + `open_time` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '营业时间JSON,如["09:00","22:00"]', | |
| 312 | + `about` TEXT COMMENT '店铺简介', | |
| 313 | + `account_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联账号ID', | |
| 314 | + `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '接入方AppKey,为空=平台自建', | |
| 315 | + `out_store_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '接入方门店编号,用于推单时自动匹配', | |
| 316 | + `list_order` INT NOT NULL DEFAULT 0 COMMENT '排序', | |
| 317 | + `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除', | |
| 318 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间', | |
| 319 | + PRIMARY KEY (`id`), | |
| 320 | + KEY `idx_city_id` (`city_id`), | |
| 321 | + KEY `idx_app_out_store` (`app_key`, `out_store_id`), | |
| 322 | + KEY `idx_order_del` (`list_order`, `is_del`) | |
| 323 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家店铺表'; | |
| 324 | + | |
| 325 | +-- 开放平台应用表 | |
| 326 | +CREATE TABLE `open_app` ( | |
| 327 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 328 | + `app_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '应用名称', | |
| 329 | + `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'AppKey', | |
| 330 | + `app_secret` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'AppSecret', | |
| 331 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联城市/租户ID(必填,租户隔离核心字段)', | |
| 332 | + `store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联店铺ID,0=不限制', | |
| 333 | + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', | |
| 334 | + `webhook_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Webhook回调地址', | |
| 335 | + `webhook_events` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '订阅事件JSON数组', | |
| 336 | + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注', | |
| 337 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 338 | + PRIMARY KEY (`id`), | |
| 339 | + UNIQUE KEY `uk_app_key` (`app_key`), | |
| 340 | + KEY `idx_city_id` (`city_id`) | |
| 341 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放平台应用表'; | |
| 342 | + | |
| 343 | +-- Webhook 推送日志表 | |
| 344 | +CREATE TABLE `webhook_log` ( | |
| 345 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 346 | + `app_id` BIGINT UNSIGNED NOT NULL COMMENT '应用ID', | |
| 347 | + `event` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '事件类型', | |
| 348 | + `biz_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '业务ID', | |
| 349 | + `url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '推送URL', | |
| 350 | + `payload` TEXT COMMENT '推送内容JSON', | |
| 351 | + `response_code` INT NOT NULL DEFAULT 0 COMMENT 'HTTP响应码', | |
| 352 | + `response_body` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '响应内容', | |
| 353 | + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=失败 1=成功', | |
| 354 | + `retry_count` INT NOT NULL DEFAULT 0 COMMENT '重试次数', | |
| 355 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 356 | + PRIMARY KEY (`id`), | |
| 357 | + KEY `idx_app_event` (`app_id`, `event`), | |
| 358 | + KEY `idx_biz_id` (`biz_id`), | |
| 359 | + KEY `idx_status` (`status`) | |
| 360 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Webhook推送日志表'; | |
| 361 | + | |
| 362 | +-- 超级管理员表 | |
| 363 | +CREATE TABLE `admin_user` ( | |
| 364 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 365 | + `user_login` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录账号', | |
| 366 | + `user_pass` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码(MD5)', | |
| 367 | + `user_nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', | |
| 368 | + `user_status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=正常', | |
| 369 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 370 | + PRIMARY KEY (`id`), | |
| 371 | + UNIQUE KEY `uk_user_login` (`user_login`) | |
| 372 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='超级管理员表'; | |
| 373 | + | |
| 374 | +-- orders 表补充字段(如已有 orders 表,执行以下 ALTER) | |
| 375 | +ALTER TABLE `orders` ADD COLUMN `out_order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '外部系统订单号' AFTER `order_no`; | |
| 376 | +ALTER TABLE `orders` ADD COLUMN `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '接入方AppKey' AFTER `out_order_no`; | |
| 377 | +ALTER TABLE `orders` ADD COLUMN `callback_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '状态回调地址' AFTER `app_key`; | |
| 378 | +ALTER TABLE `orders` ADD INDEX `idx_app_out_order` (`app_key`, `out_order_no`); | |
| 379 | + | |
| 380 | +-- 骑手评价表 | |
| 381 | +CREATE TABLE `rider_evaluate` ( | |
| 382 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 383 | + `uid` BIGINT UNSIGNED NOT NULL COMMENT '评价用户ID', | |
| 384 | + `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID', | |
| 385 | + `rid` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID', | |
| 386 | + `content` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '评价内容', | |
| 387 | + `star` TINYINT NOT NULL DEFAULT 5 COMMENT '星级1-5', | |
| 388 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 389 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 390 | + PRIMARY KEY (`id`), | |
| 391 | + UNIQUE KEY `uk_uid_oid` (`uid`, `oid`), | |
| 392 | + KEY `idx_rid` (`rid`) | |
| 393 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手评价表'; | |
| 394 | + | |
| 395 | +-- 退款原因配置表 | |
| 396 | +CREATE TABLE `orders_refund_reason` ( | |
| 397 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 398 | + `name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '原因描述', | |
| 399 | + `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1=用户 2=骑手', | |
| 400 | + `list_order` INT NOT NULL DEFAULT 0, | |
| 401 | + PRIMARY KEY (`id`) | |
| 402 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款原因配置表'; | |
| 403 | + | |
| 404 | +-- 退款申请记录表 | |
| 405 | +CREATE TABLE `orders_refund_record` ( | |
| 406 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 407 | + `oid` BIGINT UNSIGNED NOT NULL COMMENT '订单ID', | |
| 408 | + `order_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单号', | |
| 409 | + `uid` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '申请人ID', | |
| 410 | + `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1=用户 2=骑手', | |
| 411 | + `reason_id` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 412 | + `reason` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '退款原因', | |
| 413 | + `money` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '退款金额', | |
| 414 | + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=待处理 1=通过 2=拒绝', | |
| 415 | + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '处理备注', | |
| 416 | + `add_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 417 | + `handle_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 418 | + PRIMARY KEY (`id`), | |
| 419 | + KEY `idx_oid` (`oid`), | |
| 420 | + KEY `idx_status` (`status`) | |
| 421 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款申请记录表'; | |
| 422 | + | |
| 423 | +-- 退款原因初始数据 | |
| 424 | +INSERT INTO `orders_refund_reason` (`name`, `role`, `list_order`) VALUES | |
| 425 | +('骑手长时间未接单', 1, 1), | |
| 426 | +('骑手态度恶劣', 1, 2), | |
| 427 | +('物品损坏', 1, 3), | |
| 428 | +('其他原因', 1, 99), | |
| 429 | +('用户恶意单', 2, 1), | |
| 430 | +('无法完成配送', 2, 2), | |
| 431 | +('其他原因', 2, 99); | |
| 432 | + | |
| 433 | +-- 外部门店表(接入方通过开放平台同步自己系统的门店) | |
| 434 | +CREATE TABLE `ext_store` ( | |
| 435 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |
| 436 | + `app_key` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '所属应用AppKey', | |
| 437 | + `out_store_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '接入方门店原始ID', | |
| 438 | + `name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '门店名称', | |
| 439 | + `address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '门店地址', | |
| 440 | + `lng` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '经度', | |
| 441 | + `lat` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '纬度', | |
| 442 | + `city_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '所属城市ID', | |
| 443 | + `phone` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '联系电话', | |
| 444 | + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=关闭 1=营业', | |
| 445 | + `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注', | |
| 446 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 447 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | |
| 448 | + PRIMARY KEY (`id`), | |
| 449 | + UNIQUE KEY `uk_app_store` (`app_key`, `out_store_id`), | |
| 450 | + KEY `idx_city_id` (`city_id`) | |
| 451 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部门店表'; | |
| 452 | + | |
| 453 | +-- orders 表补充货物快照字段 | |
| 454 | +ALTER TABLE `orders` ADD COLUMN `items_json` TEXT COMMENT '货物清单快照JSON' AFTER `callback_url`; | |
| 455 | +ALTER TABLE `orders` ADD COLUMN `item_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '整单货物备注' AFTER `items_json`; | |
| 456 | +ALTER TABLE `orders` ADD COLUMN `ext_store_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联外部门店ID' AFTER `item_remark`; | |
| 0 | 457 | \ No newline at end of file | ... | ... |
src/test/java/com/diligrp/rider/service/impl/DeliveryFeeServiceImplTest.java
0 → 100644
| 1 | +++ a/src/test/java/com/diligrp/rider/service/impl/DeliveryFeeServiceImplTest.java | |
| 1 | +package com.diligrp.rider.service.impl; | |
| 2 | + | |
| 3 | +import com.diligrp.rider.dto.DeliveryPricingConfigDTO; | |
| 4 | +import com.diligrp.rider.dto.DeliveryPricingRuleDTO; | |
| 5 | +import com.diligrp.rider.dto.DeliveryFeeCalcDTO; | |
| 6 | +import com.diligrp.rider.service.CityService; | |
| 7 | +import com.diligrp.rider.util.GeoUtil; | |
| 8 | +import com.diligrp.rider.vo.DeliveryFeeResultVO; | |
| 9 | +import org.junit.jupiter.api.Test; | |
| 10 | + | |
| 11 | +import java.math.BigDecimal; | |
| 12 | +import java.math.RoundingMode; | |
| 13 | +import java.time.LocalDateTime; | |
| 14 | +import java.time.ZoneId; | |
| 15 | +import java.util.Arrays; | |
| 16 | +import java.util.List; | |
| 17 | + | |
| 18 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 19 | +import static org.mockito.Mockito.mock; | |
| 20 | + | |
| 21 | +class DeliveryFeeServiceImplTest { | |
| 22 | + | |
| 23 | + private final DeliveryFeeServiceImpl service = new DeliveryFeeServiceImpl(mock(CityService.class)); | |
| 24 | + | |
| 25 | + @Test | |
| 26 | + void shouldApplyMinFee() { | |
| 27 | + DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig(); | |
| 28 | + pricingConfig.getType6().setBaseSwitch(1); | |
| 29 | + pricingConfig.getType6().setBaseFee(new BigDecimal("2.00")); | |
| 30 | + pricingConfig.getType6().setDistanceSwitch(0); | |
| 31 | + pricingConfig.getType6().setWeightSwitch(0); | |
| 32 | + pricingConfig.getType6().setMinFee(new BigDecimal("5.00")); | |
| 33 | + | |
| 34 | + DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, baseCalc()); | |
| 35 | + | |
| 36 | + assertEquals(new BigDecimal("2.00"), result.getMoneyBasic()); | |
| 37 | + assertEquals(new BigDecimal("5.00"), result.getTotalFee()); | |
| 38 | + assertEquals(1, result.getMinFeeApplied()); | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Test | |
| 42 | + void shouldCalculateDistanceStepFee() { | |
| 43 | + DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig(); | |
| 44 | + pricingConfig.getType6().setDistanceBasic(new BigDecimal("1.0")); | |
| 45 | + pricingConfig.getType6().setDistanceBasicMoney(new BigDecimal("4.0")); | |
| 46 | + pricingConfig.getType6().setWeightSwitch(0); | |
| 47 | + pricingConfig.getType6().setDistanceSteps(List.of( | |
| 48 | + distanceStep("3.0", "1.0", "2.0", 0), | |
| 49 | + distanceStep("5.0", "1.0", "3.0", 1) | |
| 50 | + )); | |
| 51 | + | |
| 52 | + DeliveryFeeCalcDTO calc = baseCalc(); | |
| 53 | + calc.setStartLat("31.2304"); | |
| 54 | + calc.setStartLng("121.4737"); | |
| 55 | + calc.setEndLat("31.2574"); | |
| 56 | + calc.setEndLng("121.4737"); | |
| 57 | + | |
| 58 | + DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc); | |
| 59 | + | |
| 60 | + BigDecimal actualDistance = BigDecimal.valueOf(GeoUtil.calcDistanceKm(31.2304, 121.4737, 31.2574, 121.4737)) | |
| 61 | + .setScale(0, RoundingMode.CEILING) | |
| 62 | + .setScale(1); | |
| 63 | + BigDecimal expectedDistanceFee = actualDistance.compareTo(new BigDecimal("3.0")) <= 0 | |
| 64 | + ? new BigDecimal("4.00") | |
| 65 | + : new BigDecimal("7.00"); | |
| 66 | + assertEquals(actualDistance, result.getDistance()); | |
| 67 | + assertEquals(expectedDistanceFee, result.getMoneyDistance()); | |
| 68 | + assertEquals(result.getMoneyBasic().add(result.getMoneyDistance()), result.getTotalFee()); | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Test | |
| 72 | + void shouldCapWeightFee() { | |
| 73 | + DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig(); | |
| 74 | + pricingConfig.getType6().setDistanceSwitch(0); | |
| 75 | + pricingConfig.getType6().setWeightFirst(new BigDecimal("5")); | |
| 76 | + pricingConfig.getType6().setWeightFirstFee(new BigDecimal("3")); | |
| 77 | + pricingConfig.getType6().setWeightUnitFee(new BigDecimal("2")); | |
| 78 | + pricingConfig.getType6().setWeightCapFee(new BigDecimal("10")); | |
| 79 | + | |
| 80 | + DeliveryFeeCalcDTO calc = baseCalc(); | |
| 81 | + calc.setWeight(new BigDecimal("10")); | |
| 82 | + | |
| 83 | + DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc); | |
| 84 | + | |
| 85 | + assertEquals(new BigDecimal("3"), result.getMoneyBasic()); | |
| 86 | + assertEquals(new BigDecimal("7.00"), result.getMoneyWeight()); | |
| 87 | + assertEquals(new BigDecimal("10.00"), result.getTotalFee()); | |
| 88 | + } | |
| 89 | + | |
| 90 | + @Test | |
| 91 | + void shouldApplyPieceRule() { | |
| 92 | + DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig(); | |
| 93 | + pricingConfig.getType6().setDistanceSwitch(0); | |
| 94 | + pricingConfig.getType6().setWeightSwitch(0); | |
| 95 | + pricingConfig.getType6().setPieceSwitch(1); | |
| 96 | + pricingConfig.getType6().setPieceRules(List.of( | |
| 97 | + pieceRule(1, 2, "1.00", 0), | |
| 98 | + pieceRule(3, 5, "3.50", 1) | |
| 99 | + )); | |
| 100 | + | |
| 101 | + DeliveryFeeCalcDTO calc = baseCalc(); | |
| 102 | + calc.setPieces(4); | |
| 103 | + | |
| 104 | + DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc); | |
| 105 | + | |
| 106 | + assertEquals(new BigDecimal("3.50"), result.getMoneyPiece()); | |
| 107 | + assertEquals("(3-5件)", result.getMoneyPieceTxt()); | |
| 108 | + assertEquals(new BigDecimal("3.50"), result.getTotalFee()); | |
| 109 | + } | |
| 110 | + | |
| 111 | + @Test | |
| 112 | + void shouldApplyCrossDayTimeFee() { | |
| 113 | + DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig(); | |
| 114 | + pricingConfig.getType6().setDistanceSwitch(0); | |
| 115 | + pricingConfig.getType6().setWeightSwitch(0); | |
| 116 | + pricingConfig.getType6().setTimes(List.of(timeRule(22 * 60, 6 * 60, "2.50", 1))); | |
| 117 | + | |
| 118 | + DeliveryFeeCalcDTO calc = baseCalc(); | |
| 119 | + calc.setServiceTime(LocalDateTime.of(2026, 4, 2, 23, 30) | |
| 120 | + .atZone(ZoneId.of("Asia/Shanghai")) | |
| 121 | + .toEpochSecond()); | |
| 122 | + | |
| 123 | + DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc); | |
| 124 | + | |
| 125 | + assertEquals(new BigDecimal("2.50"), result.getMoneyTime()); | |
| 126 | + assertEquals(new BigDecimal("2.50"), result.getTotalFee()); | |
| 127 | + } | |
| 128 | + | |
| 129 | + private DeliveryPricingConfigDTO defaultPricingConfig() { | |
| 130 | + DeliveryPricingConfigDTO config = new DeliveryPricingConfigDTO(); | |
| 131 | + config.setType(Arrays.asList(6)); | |
| 132 | + config.setDistanceBasic(new BigDecimal("3")); | |
| 133 | + config.setDistanceBasicTime(30); | |
| 134 | + config.setDistanceMoreTime(10); | |
| 135 | + config.setRiderDistance(new BigDecimal("3")); | |
| 136 | + | |
| 137 | + DeliveryPricingRuleDTO type6 = new DeliveryPricingRuleDTO(); | |
| 138 | + type6.setFeeMode(2); | |
| 139 | + type6.setBaseSwitch(0); | |
| 140 | + type6.setBaseFee(BigDecimal.ZERO); | |
| 141 | + type6.setDistanceSwitch(1); | |
| 142 | + type6.setDistanceBasic(new BigDecimal("3")); | |
| 143 | + type6.setDistanceBasicMoney(new BigDecimal("4")); | |
| 144 | + type6.setDistanceType(2); | |
| 145 | + type6.setWeightSwitch(1); | |
| 146 | + type6.setWeightFirst(new BigDecimal("5")); | |
| 147 | + type6.setWeightFirstFee(BigDecimal.ZERO); | |
| 148 | + type6.setWeightUnitFee(BigDecimal.ONE); | |
| 149 | + type6.setWeightCapFee(new BigDecimal("30")); | |
| 150 | + type6.setPieceSwitch(0); | |
| 151 | + config.setType6(type6); | |
| 152 | + return config; | |
| 153 | + } | |
| 154 | + | |
| 155 | + private DeliveryFeeCalcDTO baseCalc() { | |
| 156 | + DeliveryFeeCalcDTO calc = new DeliveryFeeCalcDTO(); | |
| 157 | + calc.setCityId(1L); | |
| 158 | + calc.setOrderType(6); | |
| 159 | + calc.setStartLng("121.4737"); | |
| 160 | + calc.setStartLat("31.2304"); | |
| 161 | + calc.setEndLng("121.4737"); | |
| 162 | + calc.setEndLat("31.2304"); | |
| 163 | + calc.setWeight(BigDecimal.ZERO); | |
| 164 | + calc.setPieces(0); | |
| 165 | + calc.setServiceTime(0L); | |
| 166 | + return calc; | |
| 167 | + } | |
| 168 | + | |
| 169 | + private DeliveryPricingRuleDTO.DistanceStepDTO distanceStep(String endDistance, String unitDistance, String unitFee, int order) { | |
| 170 | + DeliveryPricingRuleDTO.DistanceStepDTO dto = new DeliveryPricingRuleDTO.DistanceStepDTO(); | |
| 171 | + dto.setEndDistance(new BigDecimal(endDistance)); | |
| 172 | + dto.setUnitDistance(new BigDecimal(unitDistance)); | |
| 173 | + dto.setUnitFee(new BigDecimal(unitFee)); | |
| 174 | + dto.setListOrder(order); | |
| 175 | + return dto; | |
| 176 | + } | |
| 177 | + | |
| 178 | + private DeliveryPricingRuleDTO.PieceRuleDTO pieceRule(int start, int end, String fee, int order) { | |
| 179 | + DeliveryPricingRuleDTO.PieceRuleDTO dto = new DeliveryPricingRuleDTO.PieceRuleDTO(); | |
| 180 | + dto.setStartPiece(start); | |
| 181 | + dto.setEndPiece(end); | |
| 182 | + dto.setFee(new BigDecimal(fee)); | |
| 183 | + dto.setListOrder(order); | |
| 184 | + return dto; | |
| 185 | + } | |
| 186 | + | |
| 187 | + private DeliveryPricingRuleDTO.TimePeriodDTO timeRule(int start, int end, String fee, int isOpen) { | |
| 188 | + DeliveryPricingRuleDTO.TimePeriodDTO dto = new DeliveryPricingRuleDTO.TimePeriodDTO(); | |
| 189 | + dto.setStart(start); | |
| 190 | + dto.setEnd(end); | |
| 191 | + dto.setMoney(new BigDecimal(fee)); | |
| 192 | + dto.setIsOpen(isOpen); | |
| 193 | + return dto; | |
| 194 | + } | |
| 195 | +} | ... | ... |