rk
6 小时以前 cf1d82548a1bd8155ffe9b486df8167aa9e63a7d
功能开发
已添加5个文件
已修改19个文件
520 ■■■■■ 文件已修改
server/platform/src/main/java/com/doumee/api/business/ReportController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/douyin/DouyinClient.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/douyin/dto/DouyinBoundProduct.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareParam.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/wx/WxMiniUtilService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyRecord.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/vo/BikeIncomeStatVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/vo/BikeUsageStatVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/vo/DashboardVO.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/vo/PackageSourceStatVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/web/request/GoodsorderBackDTO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DouyinVerifyService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/GoodsorderService.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/MemberService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/resources/application-dev.yml 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/AccountApi.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/DouyinApi.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/ManagerApi.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/platform/src/main/java/com/doumee/api/business/ReportController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package com.doumee.api.business;
import com.doumee.api.BaseController;
import com.doumee.core.model.ApiResponse;
import com.doumee.dao.business.vo.BikeUsageStatVO;
import com.doumee.dao.business.vo.DashboardVO;
import com.doumee.dao.business.vo.IncomeStatVO;
import com.doumee.dao.business.vo.PackageSourceStatVO;
import com.doumee.service.business.ReportService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * æ•°æ®æŠ¥è¡¨(管理端新增:近30天收益 / è½¦è¾†ä½¿ç”¨æƒ…况 / å¥—餐来源 / ç»¼åˆçœ‹æ¿)。
 * <p>不做菜单/按钮级权限限制:无 @RequiresPermissions,仅受 Shiro authc ç™»å½•校验保护,登录后台即可访问。
 * <p>Service å¤ç”¨ services æ¨¡å— {@link ReportService}(与 web ç«¯ /web/report åŒæº)。
 *
 * @author rk
 * @date 2026/06/26
 */
@Api(tags = "数据报表(管理端)")
@RestController
@RequestMapping("/business/report")
public class ReportController extends BaseController {
    @Autowired
    private ReportService reportService;
    @ApiOperation("近30天收益统计(按日柱状图+累计+环比/同比)")
    @GetMapping("/incomeStat30")
    public ApiResponse<IncomeStatVO> incomeStat30() {
        return ApiResponse.success(reportService.incomeStat30());
    }
    @ApiOperation("车辆使用情况:自行车/电动车各自使用中与空闲数量")
    @GetMapping("/bikeUsageStat")
    public ApiResponse<BikeUsageStatVO> bikeUsageStat() {
        return ApiResponse.success(reportService.bikeUsageStat());
    }
    @ApiOperation("套餐销售来源:本月/本年维度,抖音兑换与小程序购买数量")
    @GetMapping("/packageSourceStat")
    public ApiResponse<PackageSourceStatVO> packageSourceStat() {
        return ApiResponse.success(reportService.packageSourceStat());
    }
    @ApiOperation("综合看板:本月/昨日/今日收益与订单数,车辆总数/使用中/空闲")
    @GetMapping("/dashboard")
    public ApiResponse<DashboardVO> dashboard() {
        return ApiResponse.success(reportService.dashboard());
    }
}
server/services/src/main/java/com/doumee/core/douyin/DouyinClient.java
@@ -27,6 +27,7 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
@@ -277,15 +278,50 @@
            return objectId;
        }
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(input).openConnection();
            conn.setInstanceFollowRedirects(true);
            // æ‰‹åŠ¨è·Ÿéšé‡å®šå‘:HttpURLConnection è‡ªåŠ¨é‡å®šå‘ä¸è·¨åè®®(http↔https)且 getURL() æ›´æ–°ä¸ç¨³å®š,
            // æ”¹ä¸ºé€è·³è¯»å– Location å¤´,最终从落地长链里提取 object_id
            String current = input;
            int maxRedirects = 5;
            for (int i = 0; i < maxRedirects; i++) {
                HttpURLConnection conn = (HttpURLConnection) new URL(current).openConnection();
                conn.setInstanceFollowRedirects(false); // æ‰‹åŠ¨è·Ÿéš
            conn.setRequestMethod("GET");
                conn.setRequestProperty("User-Agent", "Mozilla/5.0");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.connect();
            String finalUrl = conn.getURL().toString();
            conn.disconnect();
            return extractObjectId(finalUrl);
                int code = conn.getResponseCode();
                String location = conn.getHeaderField("Location");
                // å¿…须读取并关闭错误/输入流,否则连接资源泄漏
                try (InputStream is = (code >= 400) ? conn.getErrorStream() : conn.getInputStream()) {
                    if (is != null) {
                        // ä¸éœ€è¦å†…容,仅消费流以释放连接
                        byte[] buf = new byte[1024];
                        while (is.read(buf) > 0) {
                            // drain
                        }
                    }
                }
                if (code == HttpURLConnection.HTTP_MOVED_PERM
                        || code == HttpURLConnection.HTTP_MOVED_TEMP
                        || code == HttpURLConnection.HTTP_SEE_OTHER
                        || code == 307 || code == 308) {
                    if (StringUtils.isBlank(location)) {
                        // é‡å®šå‘但无 Location,无法继续
                        return null;
                    }
                    current = location;
                    continue;
                }
                // éžé‡å®šå‘:用最终 URL æå– object_id
                String resolved = location != null ? location : conn.getURL().toString();
                objectId = extractObjectId(resolved);
                if (objectId == null) {
                    log.warn("解析抖音短链未提取到 object_id,input={},final={}", input, resolved);
                }
                return objectId;
            }
            log.warn("解析抖音短链超过最大重定向次数,input={}", input);
            return null;
        } catch (Exception e) {
            log.error("解析抖音短链异常:{}", input, e);
            return null;
server/services/src/main/java/com/doumee/core/douyin/dto/DouyinBoundProduct.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.doumee.core.douyin.dto;
import com.doumee.dao.business.model.Discount;
import com.doumee.dao.business.model.DouyinProduct;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
 * æŠ–音券核销前校验结果:承载 skuId åæŸ¥åˆ°çš„æŠ–音商品 + ç»‘定的本地套餐。
 * <p>scanVerify åœ¨æ ¸é”€å‰æ ¡éªŒæ—¶ä¸€æ¬¡æ€§æŸ¥å‡º,跨层透传给 verify å¼€å¡,避免重复查询。
 *
 * @author rk
 * @date 2026/06/26
 */
@Data
@AllArgsConstructor
public class DouyinBoundProduct {
    /** æŠ–音商品(用于回填核销记录的商品快照:productId/productName) */
    private DouyinProduct product;
    /** ç»‘定的本地套餐(用于开通套餐卡:discountMember æ‹·è´æº) */
    private Discount discount;
}
server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareParam.java
@@ -20,6 +20,6 @@
    @ApiModelProperty(value = "券码明文(手动输入场景),与 qrContent äºŒé€‰ä¸€")
    private String code;
    @ApiModelProperty(value = "核销抖音门店ID", required = true)
    @ApiModelProperty(value = "核销抖音门店ID")
    private String poiId;
}
server/services/src/main/java/com/doumee/core/wx/WxMiniUtilService.java
@@ -80,8 +80,8 @@
        request.setSubMchid(WxMiniConfig.wxProperties.getSubMchId());
        request.setNotifyUrl(WxMiniConfig.wxProperties.getRefundNotifyUrl());
        AmountReq amountReq = new AmountReq();
        amountReq.setTotal(refundDTO.getTotalAmount().longValue());
        amountReq.setRefund(refundDTO.getRefundAmount().longValue());
        amountReq.setTotal(1L);//refundDTO.getTotalAmount().longValue());
        amountReq.setRefund(1L);//refundDTO.getRefundAmount().longValue());
        amountReq.setCurrency("CNY");
        request.setAmount(amountReq);
        try {
server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyRecord.java
@@ -98,4 +98,8 @@
    @ApiModelProperty(value = "是否已删除 0未删除 1已删除")
    private Integer isdeleted;
    /** æ ¸é”€å¼€é€šçš„套餐卡详情(非数据库字段,scanVerify è¿”回时附带,供前端展示套餐信息) */
    @com.baomidou.mybatisplus.annotation.TableField(exist = false)
    private DiscountMember packageInfo;
}
server/services/src/main/java/com/doumee/dao/business/vo/BikeIncomeStatVO.java
@@ -28,4 +28,7 @@
    @ApiModelProperty(value = "收入金额(元)")
    private BigDecimal income;
    @ApiModelProperty(value = "该车型的车辆数量(bikes è¡¨æœªåˆ é™¤)")
    private Long bikeCount;
}
server/services/src/main/java/com/doumee/dao/business/vo/BikeUsageStatVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.doumee.dao.business.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * è½¦è¾†ä½¿ç”¨æƒ…况 VO(管理端报表:自行车/电动车 Ã— ä½¿ç”¨ä¸­/空闲)。
 * <p>口径:bikes æœªåˆ é™¤è½¦è¾†,type=0 è‡ªè¡Œè½¦ / 1 ç”µåŠ¨è½¦;
 * status=0 ç©ºé—² / 1 ä½¿ç”¨ä¸­,3 ç¦ç”¨è½¦è¾†ä¸è®¡å…¥ã€‚
 *
 * @author rk
 * @date 2026/06/26
 */
@Data
@ApiModel("车辆使用情况")
public class BikeUsageStatVO {
    @ApiModelProperty(value = "自行车空闲数量(status=0)")
    private Long bikeIdle;
    @ApiModelProperty(value = "自行车使用中数量(status=1)")
    private Long bikeInUse;
    @ApiModelProperty(value = "电动车空闲数量(status=0)")
    private Long eleBikeIdle;
    @ApiModelProperty(value = "电动车使用中数量(status=1)")
    private Long eleBikeInUse;
}
server/services/src/main/java/com/doumee/dao/business/vo/DashboardVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.doumee.dao.business.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * ç»¼åˆçœ‹æ¿ VO(管理端报表:收益 + è®¢å•æ•° + è½¦è¾† + å®¢æˆ·æ•°)。
 * <p>口径:
 * <ul>
 *   <li>收益:goodsorder type=0 æŠ¼é‡‘ + status=4 å·²ç»“ç®— çš„ closeMoney ä¹‹å’Œ(分→元),同 incomeStat</li>
 *   <li>订单数:goodsorder payStatus=1 å·²æ”¯ä»˜è®¢å•计数</li>
 *   <li>车辆:未删除车辆总数</li>
 *   <li>客户数:总会员=未删除全部;今日/昨日新增按 member.create_date è½åœ¨å¯¹åº”区间</li>
 * </ul>
 *
 * @author rk
 * @date 2026/06/26
 */
@Data
@ApiModel("综合看板")
public class DashboardVO {
    // ---------------- æ”¶ç›Š(元) ----------------
    @ApiModelProperty(value = "本月收益(元)")
    private BigDecimal monthIncome;
    @ApiModelProperty(value = "昨日收益(元)")
    private BigDecimal yesterdayIncome;
    @ApiModelProperty(value = "今日收益(元)")
    private BigDecimal todayIncome;
    // ---------------- è®¢å•æ•° ----------------
    @ApiModelProperty(value = "本月订单数(已支付)")
    private Long monthOrderCount;
    @ApiModelProperty(value = "昨日订单数(已支付)")
    private Long yesterdayOrderCount;
    @ApiModelProperty(value = "今日订单数(已支付)")
    private Long todayOrderCount;
    // ---------------- è½¦è¾† ----------------
    @ApiModelProperty(value = "车辆总数(未删除)")
    private Long totalBikeCount;
    // ---------------- å®¢æˆ·æ•° ----------------
    @ApiModelProperty(value = "客户总数(未删除会员总数)")
    private Long totalMemberCount;
    @ApiModelProperty(value = "昨日新增客户(create_date è½åœ¨æ˜¨æ—¥)")
    private Long yesterdayNewMember;
    @ApiModelProperty(value = "今日新增客户(create_date â‰¥ ä»Šæ—¥0点)")
    private Long todayNewMember;
}
server/services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java
@@ -63,6 +63,9 @@
    @ApiModelProperty(value = "兑换人(member.name,核销操作人=购买会员本人)")
    private String exchangerName;
    @ApiModelProperty(value = "套餐使用次数(该套餐卡关联的 member_rides è®°å½•æ•°;0=未使用)")
    private Long useCount;
    @ApiModelProperty(value = "状态文案(已兑换/已撤销/核销失败)")
    private String statusName;
}
server/services/src/main/java/com/doumee/dao/business/vo/PackageSourceStatVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.doumee.dao.business.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * å¥—餐销售来源统计 VO(管理端报表:本月/本年 Ã— æŠ–音兑换/小程序购买)。
 * <p>口径:goodsorder type=1 å¥—餐卡购买、payStatus=1 å·²æ”¯ä»˜;
 * æŠ–音兑换 = payWay 2,小程序购买 = payWay 0(微信);按 pay_date é™å®šæœ¬æœˆ/本年。
 *
 * @author rk
 * @date 2026/06/26
 */
@Data
@ApiModel("套餐销售来源统计")
public class PackageSourceStatVO {
    @ApiModelProperty(value = "本月套餐销售来源")
    private PeriodCount month;
    @ApiModelProperty(value = "本年套餐销售来源")
    private PeriodCount year;
    /**
     * å•个时段的套餐销售来源计数(抖音兑换 / å°ç¨‹åºè´­ä¹°)。
     */
    @Data
    @ApiModel("套餐销售来源计数")
    public static class PeriodCount {
        @ApiModelProperty(value = "抖音兑换数量(payWay=2)")
        private Long douyinCount;
        @ApiModelProperty(value = "小程序购买数量(payWay=0 å¾®ä¿¡)")
        private Long miniCount;
    }
}
server/services/src/main/java/com/doumee/dao/business/web/request/GoodsorderBackDTO.java
@@ -19,7 +19,7 @@
    @ApiModelProperty(value = "退款金额")
    private BigDecimal money;
    @ApiModelProperty(value = "退款金额")
    @ApiModelProperty(value = "退款说明")
    private String reason;
    @ApiModelProperty(value = "套餐卡退款类型 0退货退款 1仅退款")
server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java
@@ -2,6 +2,7 @@
import com.doumee.dao.business.model.Ad;
import com.doumee.dao.business.model.Discount;
import com.doumee.dao.business.model.DiscountMember;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -74,6 +75,8 @@
    @ApiModelProperty(value = "电车运营区域")
    private String eleBusinessArea;
    @ApiModelProperty(value = "当前会员有效套餐(最多10条,按获得时间倒序)")
    private List<DiscountMember> validDiscountList;
}
server/services/src/main/java/com/doumee/service/business/DouyinVerifyService.java
@@ -1,6 +1,7 @@
package com.doumee.service.business;
import com.doumee.core.douyin.dto.DouyinBaseResp;
import com.doumee.core.douyin.dto.DouyinBoundProduct;
import com.doumee.core.douyin.dto.DouyinCancelParam;
import com.doumee.core.douyin.dto.DouyinPrepareParam;
import com.doumee.core.douyin.dto.DouyinPrepareResp;
@@ -30,12 +31,37 @@
    DouyinVerifyRecord verify(DouyinVerifyParam param, String operator);
    /**
     * éªŒåˆ¸(核销),使用调用方已校验好的套餐绑定结果(scanVerify æ ¸é”€å‰æ ¡éªŒåŽè°ƒç”¨)。
     * <p>核销成功后直接用 boundProduct å¼€é€šå¥—餐,不再重复查询商品/套餐。
     * @param param        æ ¸é”€å…¥å‚
     * @param operator     æ“ä½œäººID
     * @param boundProduct æ ¸é”€å‰å·²æ ¡éªŒçš„æŠ–音商品 + æœ¬åœ°å¥—餐(scanVerify ä¼ å…¥)
     */
    DouyinVerifyRecord verify(DouyinVerifyParam param, String operator, DouyinBoundProduct boundProduct);
    /**
     * æ ¸é”€å‰æ ¡éªŒ:按 skuId æ ¡éªŒæŠ–音商品在库且已绑定有效本地套餐。
     * <p>scanVerify åœ¨æ ¸é”€å‰è°ƒç”¨,失败抛业务异常(券尚未核销,避免抖音已核销但本地未开卡的不一致)。
     * @param skuId æ ¸é”€åˆ¸å¯¹åº”的抖音 SKU ID
     * @return æŠ–音商品 + ç»‘定的本地套餐(供 verify å¼€å¡å¤ç”¨)
     */
    DouyinBoundProduct resolveBoundProduct(String skuId);
    /**
     * æ’¤é”€æ ¸é”€(核销后 1 å°æ—¶å†…),更新记录撤销状态
     * @param operator æ“ä½œäººID(由调用端传入,web ç«¯å–登录会员ID)
     */
    DouyinVerifyRecord cancel(DouyinCancelParam param, String operator);
    /**
     * æŒ‰ discountMemberId æŠŠå¥—餐卡详情填到 record.packageInfo(scanVerify å±•示用)。
     * <p>无套餐卡ID或查不到时,packageInfo ç½® null,不影响主流程。
     *
     * @param record æ ¸é”€è®°å½•(含 discountMemberId)
     */
    void fillPackageInfo(DouyinVerifyRecord record);
    /**
     * æ ¸é”€è®°å½•分页(web ç«¯å°ç¨‹åºè‡ªç”¨,简单分页)
     */
    PageData<DouyinVerifyRecord> findPage(PageWrap<DouyinVerifyRecord> pageWrap);
server/services/src/main/java/com/doumee/service/business/GoodsorderService.java
@@ -172,9 +172,16 @@
    void forceCloseGoodsorder(String orderId);
    /**
     * é€€æ¬¾
     * é€€æ¬¾(登录人从 Shiro ä¸Šä¸‹æ–‡å–,platform ç«¯ç”¨)
     */
    void backGoodsorder(GoodsorderBackDTO goodsorderBackDTO);
    /**
     * é€€æ¬¾(登录人由调用方传入,web ç«¯ JWT åœºæ™¯ç”¨,creator=绑定的管理员ID)
     * @param goodsorderBackDTO é€€æ¬¾å…¥å‚
     * @param creator           æ“ä½œäººID(退款单 + å¥—餐操作日志的 creator)
     */
    void backGoodsorder(GoodsorderBackDTO goodsorderBackDTO, String creator);
    List<MemberRides> getMemberRidesForClose(String orderid );
    /**
     * èŽ·å–å¯é€€æ¬¾ä¿¡æ¯
server/services/src/main/java/com/doumee/service/business/MemberService.java
@@ -143,4 +143,13 @@
    AccountResponse wxPhone(WxPhoneRequest wxPhoneRequest);
    UserResponse getUserInfo(String memberId);
    /**
     * é€€å‡ºç™»å½•:清空当前会员手机号(置空串)。
     * <p>JWT æ— çŠ¶æ€,无法吊销 token;清 phone åŽä¼šå‘˜å›žåˆ°æœªæŽˆæƒæ‰‹æœºå·çŠ¶æ€,
     * ä¸‹æ¬¡è¿›å…¥éœ€é‡æ–° wxLogin + wxPhone。
     *
     * @param memberId ä¼šå‘˜ID
     */
    void clearPhone(String memberId);
}
server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java
@@ -9,6 +9,7 @@
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.douyin.DouyinClient;
import com.doumee.core.douyin.dto.DouyinBaseResp;
import com.doumee.core.douyin.dto.DouyinBoundProduct;
import com.doumee.core.douyin.dto.DouyinCancelParam;
import com.doumee.core.douyin.dto.DouyinCancelReq;
import com.doumee.core.douyin.dto.DouyinCancelResp;
@@ -135,7 +136,7 @@
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator) {
    public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator, DouyinBoundProduct boundProduct) {
        // å…¥å‚校验
        if (param == null || StringUtils.isBlank(param.getVerifyToken())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "verifyToken ä¸èƒ½ä¸ºç©º");
@@ -153,6 +154,10 @@
        if (StringUtils.isBlank(param.getSkuId())) {
            // æ—  skuId åˆ™æ— æ³•反查套餐(核销返回本身不含商品标识),直接拦截
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "skuId ä¸èƒ½ä¸ºç©º");
        }
        if (boundProduct == null || boundProduct.getProduct() == null || boundProduct.getDiscount() == null) {
            // å…œåº•:套餐绑定结果缺失(scanVerify åº”已校验,这里防 NPE)
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "未找到该券对应的本地套餐,请先在管理端绑定");
        }
        Date now = new Date();
@@ -204,8 +209,16 @@
        }
        // æ ¸é”€æˆåŠŸ:为当前登录人开通套餐(任一步失败 â†’ æ•´å•回滚)
        openDiscountForVerify(rec, param, operator);
        // æ³¨:scanVerify å·²åœ¨æ ¸é”€å‰æ ¡éªŒè¿‡å¥—餐绑定并透传结果,这里不再重复查询
        openDiscountForVerify(rec, param, boundProduct, operator);
        return rec;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator) {
        // æ— é¢„校验结果的兼容入口:核销前先校验套餐绑定(核销动作在重载内完成)
        return verify(param, operator, resolveBoundProduct(param.getSkuId()));
    }
    /**
@@ -215,19 +228,23 @@
     *
     * @param rec      æ ¸é”€è®°å½•(含 originCode/certificateId ç­‰æŠ–音标识,开通后回填套餐卡ID)
     * @param param    æ ¸é”€å…¥å‚(含 skuId åæŸ¥å¥—餐、payAmount å¿«ç…§)
     * @param boundProduct æ ¸é”€å‰å·²æ ¡éªŒçš„æŠ–音商品 + æœ¬åœ°å¥—餐(避免重复查询)
     * @param operator æ“ä½œäºº = å¥—餐归属人(web ç«¯ç™»å½•会员 id)
     */
    private void openDiscountForVerify(DouyinVerifyRecord rec, DouyinVerifyParam param, String operator) {
        // â‘  åæŸ¥å¥—餐:skuId â†’ douyin_product_sku â†’ product_id â†’ douyin_product.out_id â†’ discount
        DouyinProduct product = resolveProduct(param.getSkuId());
        Discount discount = resolveDiscount(product);
    private void openDiscountForVerify(DouyinVerifyRecord rec, DouyinVerifyParam param,
                                       DouyinBoundProduct boundProduct, String operator) {
        // å•†å“ + å¥—餐已由 scanVerify æ ¸é”€å‰æ ¡éªŒ(resolveBoundProduct)查出并透传,直接使用
        DouyinProduct product = boundProduct.getProduct();
        Discount discount = boundProduct.getDiscount();
        Date now = new Date();
        // â‘¡ é˜²é‡:同一券码已为该用户开过套餐卡则跳过(避免重复核销重开)
        // â‘¡ é˜²é‡:同一券码已为该用户开过「正常」套餐卡则跳过(避免重复核销重开)。
        //    æ³¨æ„:已作废(status=1,如撤销核销后)的卡不参与防重——撤销后重新核销应正常开新卡。
        DiscountMember existCard = discountMemberMapper.selectOne(new QueryWrapper<DiscountMember>().lambda()
                .eq(DiscountMember::getCode, rec.getOriginCode())
                .eq(DiscountMember::getMemberId, operator)
                .eq(DiscountMember::getStatus, Constants.ZERO)
                .eq(DiscountMember::getIsdeleted, Constants.ZERO)
                .last("limit 1"));
        if (existCard != null) {
@@ -340,13 +357,23 @@
    private Discount resolveDiscount(DouyinProduct product) {
        Discount discount = discountMapper.selectOne(new QueryWrapper<Discount>().lambda()
                .eq(Discount::getId, product.getOutId())
                .eq(Discount::getStatus, Constants.ZERO)
                .eq(Discount::getIsdeleted, Constants.ZERO)
                .last("limit 1"));
        if (discount == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "未找到该券对应的本地套餐,请先在管理端绑定");
        }
        return discount;
    }
    @Override
    public DouyinBoundProduct resolveBoundProduct(String skuId) {
        if (StringUtils.isBlank(skuId)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "skuId ä¸èƒ½ä¸ºç©º");
        }
        // å¤ç”¨æ—¢æœ‰æ ¡éªŒé“¾è·¯:skuId â†’ æŠ–音商品(在库 + å·²ç»‘ out_id)→ æœ¬åœ°å¥—餐(有效)
        DouyinProduct product = resolveProduct(skuId);
        Discount discount = resolveDiscount(product);
        return new DouyinBoundProduct(product, discount);
    }
    @Override
@@ -389,10 +416,13 @@
        rec.setRawResponse(respText);
        // æ’¤é”€å¤±è´¥:更新记录描述后抛出(记录保留原核销状态)
        if (!ok) {
            // å¤±è´¥åŽŸå› :优先 extra.description,有 sub_description æ—¶è¿½åŠ (sub_description æ›´å…·ä½“)
            String desc = resp == null || resp.getExtra() == null ? "无响应" : resp.getExtra().getDescription();
            rec.setCancelMsg("撤销失败:" + desc);
            String subDesc = resp == null || resp.getExtra() == null ? null : resp.getExtra().getSubDescription();
            String failMsg = StringUtils.isBlank(subDesc) ? desc : desc + ";" + subDesc;
            rec.setCancelMsg("撤销失败:" + failMsg);
            douyinVerifyRecordMapper.updateById(rec);
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "撤销失败:" + desc);
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "撤销失败:" + failMsg);
        }
        // æ’¤é”€æˆåŠŸ:置撤销状态、撤销时间与撤销人
        rec.setCancelStatus(Constants.DOUYIN_VERIFY_CANCEL_STATUS.DONE.getKey());
@@ -403,6 +433,15 @@
        // åŒæ­¥ä½œåºŸæœ¬åœ°å·²å¼€é€šçš„套餐卡(防止抖音撤销后套餐仍被使用);参照 backGoodsorder é€€å¡
        cancelDiscountMember(rec, operator, now);
        return rec;
    }
    @Override
    public void fillPackageInfo(DouyinVerifyRecord record) {
        // æ— å¥—餐卡ID或查不到时置 null,不影响主流程(scanVerify ç”¨äºŽå‰ç«¯å±•示套餐信息)
        if (record == null || StringUtils.isBlank(record.getDiscountMemberId())) {
            return;
        }
        record.setPackageInfo(discountMemberMapper.selectById(record.getDiscountMemberId()));
    }
    /**
@@ -495,6 +534,8 @@
                .selectAs(DouyinProduct::getCategory, DouyinVerifyRecordPageVO::getCategory)
                // æŠ–音券名:discount_member.name(本地开通套餐名)
                .selectAs(DiscountMember::getName, DouyinVerifyRecordPageVO::getCouponName)
                // å¥—餐使用次数:子查询统计该套餐卡在 member_rides çš„骑行记录数(t ä¸ºä¸»è¡¨ douyin_verify_record)
                .select(" ( select count(1) from member_rides where discount_member_id = t.discount_member_id and isdeleted = 0 ) ", DouyinVerifyRecordPageVO::getUseCount)
                // ä¸‰è¡¨ leftJoin:discount_member(经 discount_member_id)→ member(经 member_id);douyin_product(经 product_id)
                .leftJoin(DiscountMember.class, DiscountMember::getId, DouyinVerifyRecord::getDiscountMemberId)
                .leftJoin(Member.class, Member::getId, DiscountMember::getMemberId)
server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java
@@ -482,6 +482,13 @@
                }
            }
        }
        // å½“前会员有效套餐(status=0 æ­£å¸¸ + isdeleted=0),按获得时间(createDate)倒序,最多10条
        homeResponse.setValidDiscountList(discountMemberMapper.selectList(new QueryWrapper<DiscountMember>().lambda()
                .eq(DiscountMember::getMemberId, memberId)
                .eq(DiscountMember::getStatus, Constants.ZERO)
                .eq(DiscountMember::getIsdeleted, Constants.ZERO)
                .orderByDesc(DiscountMember::getCreateDate)
                .last(" limit 10 ")));
        return homeResponse;
    }
@@ -977,7 +984,16 @@
    @Override
    public void backGoodsorder(GoodsorderBackDTO goodsorderBackDTO) {
        // platform ç«¯:登录人从 Shiro ä¸Šä¸‹æ–‡å–,委托给带 creator çš„重载方法
        LoginUserInfo principal = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        if (Objects.isNull(principal) || StringUtils.isBlank(principal.getId())) {
            throw new BusinessException(ResponseStatus.BE_OVERDUE);
        }
        backGoodsorder(goodsorderBackDTO, principal.getId());
    }
    @Override
    public void backGoodsorder(GoodsorderBackDTO goodsorderBackDTO, String creator) {
        if(Objects.isNull(goodsorderBackDTO)
        || StringUtils.isBlank(goodsorderBackDTO.getOrderId())
                || Objects.isNull(goodsorderBackDTO.getBackType())
@@ -1013,7 +1029,7 @@
            refundDTO.setRefundAmount(goodsorderBackDTO.getMoney().multiply(new BigDecimal(100)));
            refundDTO.setTotalAmount(goodsorder.getMoney());
            refundDTO.setMemberId(goodsorder.getMemberId());
            refundDTO.setCreator(principal.getId());
            refundDTO.setCreator(creator);
            refundDTO.setReason(goodsorderBackDTO.getReason());
            refundDTO.setType(Constants.REFUND_TYPE.BACK.getKey());
            //退货退款 æŸ¥çœ‹å¥—餐订单状态 æ›´æ–°è®¢å•信息
@@ -1033,7 +1049,7 @@
                    //操作日志
                    DiscountLog discountLog = new DiscountLog();
                    discountLog.setIsdeleted(Constants.ZERO);
                    discountLog.setCreator(principal.getId());
                    discountLog.setCreator(creator);
                    discountLog.setCreateDate(new Date());
                    discountLog.setDiscountMemberId(discountMember.getId());
                    discountLog.setType(Constants.ONE);
@@ -1361,7 +1377,7 @@
        request.setOutTradeNo(goodsorder.getId());
        request.setNotifyUrl(WxMiniConfig.wxProperties.getNotifyUrl());//这个回调url必须是https开头的
        Amount amount = new Amount();
        amount.setTotal(goodsorder.getMoney().intValue());
        amount.setTotal(1);//goodsorder.getMoney().intValue());
        request.setAmount(amount);
//        PrepayResponse res = WxMiniConfig.wxPayService.prepay(request);
            // è·Ÿä¹‹å‰ä¸‹å•示例一样,填充预下单参数
server/services/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
@@ -417,5 +417,13 @@
        return userResponse;
    }
    @Override
    public void clearPhone(String memberId) {
        // é€€å‡ºç™»å½•:清空会员手机号(置空串),保留其他信息(含 sysuser ç»‘定)
        memberMapper.update(null, new UpdateWrapper<Member>().lambda()
                .set(Member::getPhone, StringUtils.EMPTY)
                .eq(Member::getId, memberId));
    }
}
server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
@@ -155,7 +155,23 @@
            incomeByParam.merge(o.getParamId(), amount, BigDecimal::add);
        }
        // 5. ç»„装结果:车型名 + å¤§ç±» + æ”¶å…¥(分→元,2位),按收入降序
        // 5. æŒ‰ paramId ç»Ÿè®¡æ¯ç±»è½¦åž‹çš„车辆数量(bikes è¡¨æœªåˆ é™¤),一次分组查询避免 N æ¬¡ count
        Map<String, Long> bikeCountByParam;
        if (incomeByParam.isEmpty()) {
            // æ— æœ‰æ”¶å…¥çš„车型时跳过查询:空集合传给 in() ä¼šç”Ÿæˆ "IN ()",PostgreSQL è¯­æ³•错误
            bikeCountByParam = Collections.emptyMap();
        } else {
            bikeCountByParam = bikesMapper.selectList(
                    new QueryWrapper<Bikes>().lambda()
                            .select(Bikes::getParamId)
                            .eq(Bikes::getIsdeleted, Constants.ZERO)
                            .in(Bikes::getParamId, incomeByParam.keySet()))
                    .stream()
                    .filter(b -> b.getParamId() != null)
                    .collect(Collectors.groupingBy(Bikes::getParamId, Collectors.counting()));
        }
        // 6. ç»„装结果:车型名 + å¤§ç±» + æ”¶å…¥(分→元,2位)+ è½¦è¾†æ•°,按收入降序
        List<BikeIncomeStatVO> result = new ArrayList<>();
        for (Map.Entry<String, BigDecimal> e : incomeByParam.entrySet()) {
            BaseParam param = paramMap.get(e.getKey());
@@ -164,6 +180,7 @@
            vo.setParamName(param == null ? "未知车型" : param.getName());
            vo.setCategory(param == null ? "未知" : categoryOf(param.getType()));
            vo.setIncome(e.getValue().divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
            vo.setBikeCount(bikeCountByParam.getOrDefault(e.getKey(), 0L));
            result.add(vo);
        }
        result.sort(Comparator.comparing(BikeIncomeStatVO::getIncome).reversed());
@@ -262,7 +279,7 @@
        BigDecimal totalCents = BigDecimal.ZERO;
        for (Goodsorder o : orders) {
            BigDecimal amount = o.getCloseMoney() == null ? BigDecimal.ZERO : o.getCloseMoney();
            sumByDay.merge(DateUtil.getShortDateStr(o.getPayDate()), amount, BigDecimal::add);
            sumByDay.merge(DateUtil.getDateLong(o.getPayDate()), amount, BigDecimal::add);
            totalCents = totalCents.add(amount);
        }
@@ -272,7 +289,7 @@
        List<IncomeDailyVO> dailyList = new ArrayList<>();
        for (Date d : DateUtil.getDateList(DateUtil.getStartOfDay(start), DateUtil.getStartOfDay(end))) {
            IncomeDailyVO vo = new IncomeDailyVO();
            vo.setDate(DateUtil.getShortDateStr(d));
            vo.setDate(DateUtil.getDateLong(d));
            BigDecimal daySum = sumByDay.getOrDefault(vo.getDate(), BigDecimal.ZERO);
            // åˆ†â†’å…ƒ,2位小数
            vo.setIncome(daySum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
server/services/src/main/resources/application-dev.yml
@@ -52,14 +52,14 @@
    mchKey: W97N53Q71326D6JZ2E9HY5M4VT4BAC8S
#    notifyUrl: http://xiaopiqiu3.natapp1.cc/api/wxPayNotify
#    refundNotifyUrl: http://xiaopiqiu3.natapp1.cc/api/wxRefundNotify
    notifyUrl: https://dmtest.ahapp.net/bike_h5_api/api/wxPayNotify
    refundNotifyUrl: https://dmtest.ahapp.net/bike_h5_api/api/wxRefundNotify
#    keyPath: /usr/local/aliConfig/bike/apiclient_cert.p12
#    privateCertPath: /usr/local/aliConfig/bike/apiclient_cert.pem
#    privateKeyPath: /usr/local/aliConfig/bike/apiclient_key.pem
    keyPath: d://apiclient_cert.p12
    privateCertPath: d://apiclient_cert.pem
    privateKeyPath: d://apiclient_key.pem
    notifyUrl: https://test.doumee.cn/bikeWeb/api/wxPayNotify
    refundNotifyUrl: https://test.doumee.cn/bikeWeb/api/wxRefundNotify
    keyPath: /usr/local/jars/bike/apiclient_cert.p12
    privateCertPath: /usr/local/jars/bike/apiclient_cert.pem
    privateKeyPath: /usr/local/jars/bike/apiclient_key.pem
#    keyPath: d://apiclient_cert.p12
#    privateCertPath: d://apiclient_cert.pem
#    privateKeyPath: d://apiclient_key.pem
    #服务商-------------end---
    existsSub: 1
    appSecret: 1ceb7c9dff3c4330d653adc3ca55ea24
server/web/src/main/java/com/doumee/api/web/AccountApi.java
@@ -107,6 +107,18 @@
        return  ApiResponse.success("查询成功",userResponse);
    }
    @LoginRequired
    @ApiOperation(value = "退出登录", notes = "清空当前会员手机号,下次进入需重新授权")
    @PostMapping("/logout")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse logout() {
        // JWT æ— çŠ¶æ€,无法吊销 token;此处清空会员手机号,使其回到未授权手机号状态
        memberService.clearPhone(getMemberId());
        return ApiResponse.success("操作成功");
    }
    @ApiOperation(value = "测试生成二维码", notes = "小程序端")
    @GetMapping("/generateWXMiniCode")
server/web/src/main/java/com/doumee/api/web/DouyinApi.java
@@ -7,6 +7,7 @@
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.douyin.DouyinClient;
import com.doumee.core.douyin.dto.DouyinBaseResp;
import com.doumee.core.douyin.dto.DouyinBoundProduct;
import com.doumee.core.douyin.dto.DouyinPrepareParam;
import com.doumee.core.douyin.dto.DouyinPrepareResp;
import com.doumee.core.douyin.dto.DouyinShopPoiResp;
@@ -22,6 +23,8 @@
import com.doumee.service.business.DouyinVerifyLogService;
import com.doumee.service.business.DouyinVerifyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -88,6 +91,9 @@
    @PreventRepeat
    @ApiOperation("扫码一步核销(验券准备 + æ ¸é”€åˆå¹¶;前端只调此接口)")
    @PostMapping("/scanVerify")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<DouyinVerifyRecord> scanVerify(@RequestBody DouyinPrepareParam param) {
        String apiPath = "/web/douyin/scanVerify";
        String memberId = getMemberId();
@@ -137,13 +143,19 @@
        verifyParam.setSkuId(cert.getSku().getSkuId());
        verifyParam.setPayAmount(cert.getAmount() == null ? null : cert.getAmount().getPayAmount());
        // â‘£ æ ¸é”€ + å¼€å¥—餐(单独记一条 VERIFY æ—¥å¿—)
        // â‘£ æ ¸é”€å‰æ ¡éªŒ:商品在库 + å·²ç»‘定有效套餐;失败直接拦截(券尚未核销,避免抖音已核销但本地未开卡)
        DouyinBoundProduct boundProduct = douyinVerifyService.resolveBoundProduct(verifyParam.getSkuId());
        // â‘¤ æ ¸é”€ + å¼€å¥—餐(单独记一条 VERIFY æ—¥å¿—)
        long verifyStart = System.currentTimeMillis();
        DouyinVerifyLog verifyLog = baseLog(Constants.DOUYIN_VERIFY_OPERATE_TYPE.VERIFY.getKey(), apiPath, verifyStart);
        verifyLog.setRawRequest(JSON.toJSONString(verifyParam));
        verifyLog.setPoiId(verifyParam.getPoiId());
        try {
            DouyinVerifyRecord rec = douyinVerifyService.verify(verifyParam, memberId);
            // é€ä¼ æ ¸é”€å‰æ ¡éªŒç»“æžœ,verify å†…不再重复查询商品/套餐
            DouyinVerifyRecord rec = douyinVerifyService.verify(verifyParam, memberId, boundProduct);
            // é™„带本次开通的套餐卡详情(供前端核销后展示套餐信息)
            douyinVerifyService.fillPackageInfo(rec);
            fillByRecord(verifyLog, rec);
            return ApiResponse.success(rec);
        } catch (Throwable e) {
@@ -173,6 +185,9 @@
    @LoginRequired
    @ApiOperation("核销记录分页")
    @PostMapping("/page")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<PageData<DouyinVerifyRecord>> findPage(@RequestBody PageWrap<DouyinVerifyRecord> pageWrap) {
        return ApiResponse.success(douyinVerifyService.findPage(pageWrap));
    }
@@ -180,6 +195,9 @@
    @LoginRequired
    @ApiOperation("核销记录详情")
    @GetMapping("/{id}")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
    })
    public ApiResponse<DouyinVerifyRecord> findById(@PathVariable String id) {
        return ApiResponse.success(douyinVerifyService.findById(id));
    }
server/web/src/main/java/com/doumee/api/web/ManagerApi.java
@@ -10,6 +10,8 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.*;
import com.doumee.dao.business.web.request.GoodsorderBackDTO;
import com.doumee.dao.business.web.request.GoodsorderCanBanlanceDTO;
import com.doumee.dao.business.web.response.UserResponse;
import com.doumee.dao.system.dto.WebLoginDTO;
import com.doumee.dao.system.model.SystemUser;
@@ -30,6 +32,7 @@
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * Created by IntelliJ IDEA.
@@ -54,6 +57,9 @@
    private SystemLoginService systemLoginService;
    @Autowired
    private SystemUserService systemUserService;
    /** è®¢å•退款/可退款信息查询(web ç«¯ JWT åœºæ™¯) */
    @Autowired
    private GoodsorderService goodsorderService;
    @LoginRequired
    @PreventRepeat(limit = 10, lockTime = 10000)
    @ApiOperation("登录管理员账号")
@@ -130,4 +136,33 @@
        return ApiResponse.success(list);
    }
    @LoginRequired
    @ApiOperation("获取可退款信息")
    @GetMapping("/getGoodsorderCanBanlanceDTO")
    public ApiResponse<GoodsorderCanBanlanceDTO> getGoodsorderCanBanlanceDTO(@RequestParam String orderId) {
        // ä»…查询,无登录人写入;校验管理员
        UserResponse user = this.getUserResponse();
        if (user.getSysuser() == null) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED);
        }
        return ApiResponse.success(goodsorderService.getGoodsorderCanBanlanceDTO(orderId));
    }
    @PreventRepeat
    @LoginRequired
    @ApiOperation("退款")
    @PostMapping("/backGoodsorder")
    public ApiResponse backGoodsorder(@RequestBody GoodsorderBackDTO goodsorderBackDTO) {
        // é€€æ¬¾ä¸ºç®¡ç†å‘˜æ“ä½œ:校验已绑定系统管理员,creator å– sysuser.id(与 platform Shiro å£å¾„一致)
        UserResponse user = this.getUserResponse();
        if (user.getSysuser() == null) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED);
        }
        if (Objects.nonNull(goodsorderBackDTO) && Objects.isNull(goodsorderBackDTO.getBackType())) {
            goodsorderBackDTO.setBackType(Constants.ONE);
        }
        goodsorderService.backGoodsorder(goodsorderBackDTO, user.getSysuser().getId());
        return ApiResponse.success(null);
    }
}