From 9eeb62c02a7b3c7b95c20678b6a9c74e7f12f943 Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期三, 01 七月 2026 18:15:10 +0800
Subject: [PATCH] Merge branch '3.0.1' of http://139.186.142.91:10010/r/productDev/parkBike into 3.0.1

---
 server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java |  793 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 793 insertions(+), 0 deletions(-)

diff --git a/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
new file mode 100644
index 0000000..8f85057
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
@@ -0,0 +1,793 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.DateUtil;
+import com.doumee.dao.business.BaseParamMapper;
+import com.doumee.dao.business.BikesMapper;
+import com.doumee.dao.business.GoodsorderMapper;
+import com.doumee.dao.business.MemberMapper;
+import com.doumee.dao.business.MemberRidesTrackMapper;
+import com.doumee.dao.business.join.MemberRidesJoinMapper;
+import com.doumee.dao.business.model.BaseParam;
+import com.doumee.dao.business.model.Bikes;
+import com.doumee.dao.business.model.Goodsorder;
+import com.doumee.dao.business.model.Member;
+import com.doumee.dao.business.model.MemberRides;
+import com.doumee.dao.business.model.MemberRidesTrack;
+import com.doumee.dao.business.vo.BikeIncomeStatVO;
+import com.doumee.dao.business.vo.BikeUsageStatVO;
+import com.doumee.dao.business.vo.DashboardVO;
+import com.doumee.dao.business.vo.IncomeDailyVO;
+import com.doumee.dao.business.vo.IncomeStatVO;
+import com.doumee.dao.business.vo.OperationCenterVO;
+import com.doumee.dao.business.vo.OperationOrderVO;
+import com.doumee.dao.business.vo.OrderRideItemVO;
+import com.doumee.dao.business.vo.OrderRideTrackVO;
+import com.doumee.dao.business.vo.OrderRidesDetailVO;
+import com.doumee.dao.business.vo.OverviewStatVO;
+import com.doumee.dao.business.vo.PackageSourceStatVO;
+import com.doumee.dao.business.web.request.BikeIncomeQueryDTO;
+import com.doumee.dao.business.web.request.OperationOrderQueryDTO;
+import com.doumee.service.business.ReportService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 鏁版嵁鎶ヨ〃 Service 瀹炵幇(web 绔�:姒傝缁熻 + 鏀跺叆杞﹀瀷鍒嗘瀽)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Slf4j
+@Service
+public class ReportServiceImpl implements ReportService {
+
+    @Autowired
+    private MemberMapper memberMapper;
+    @Autowired
+    private MemberRidesJoinMapper memberRidesJoinMapper;
+    @Autowired
+    private BikesMapper bikesMapper;
+    @Autowired
+    private GoodsorderMapper goodsorderMapper;
+    @Autowired
+    private BaseParamMapper baseParamMapper;
+    /** 鐢佃溅楠戣杞ㄨ抗 Mapper(鑷杞﹁蛋 MQTT 鏃犺建杩�,浠呯數杞︽湁鏁版嵁) */
+    @Autowired
+    private MemberRidesTrackMapper memberRidesTrackMapper;
+
+    /** 鏃舵蹇嵎绫诲瀷 鈫� 杩� N 澶�:dateType 1鈫�7銆�2鈫�15銆�3鈫�30(鍧囧惈浠婂ぉ) */
+    private static final Map<Integer, Integer> RECENT_DAYS = new LinkedHashMap<>();
+
+    static {
+        RECENT_DAYS.put(1, 7);
+        RECENT_DAYS.put(2, 15);
+        RECENT_DAYS.put(3, 30);
+    }
+
+    /** 缁撶畻閲戦鍒嗏啋鍏冩崲绠楅櫎鏁� */
+    private static final BigDecimal CENT_PER_YUAN = new BigDecimal("100");
+
+    /** 鐧惧垎姣斿熀鏁�(澧為暱鐜� = (鏈湡 - 瀵规瘮鏈�) / 瀵规瘮鏈� 脳 100) */
+    private static final BigDecimal PERCENT_BASE = new BigDecimal("100");
+
+    /** 鏀粯鏂瑰紡:鎶栭煶鍒告牳閿�(濂楅閿�鍞潵婧愯瘑鍒敤) */
+    private static final int PAY_WAY_DOUYIN = 2;
+
+    @Override
+    public OverviewStatVO overview() {
+        OverviewStatVO vo = new OverviewStatVO();
+        // 鎬绘敞鍐岀敤鎴�:鏈垹闄ょ殑鍏ㄩ儴鐢ㄦ埛
+        vo.setTotalMembers((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda().eq(Member::getIsdeleted, Constants.ZERO)));
+        // 浠婃棩鏂板:鍒涘缓鏃堕棿 鈮� 浠婃棩0鐐�
+        Date todayStart = DateUtil.getStartOfDay(new Date());
+        vo.setTodayMembers((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda()
+                        .eq(Member::getIsdeleted, Constants.ZERO)
+                        .ge(Member::getCreateDate, todayStart)));
+        // 鑷杞︽暟閲�(type=0),鐢靛姩杞︽暟閲�(type=1);鍧囧惈鍏ㄩ儴鏈垹闄よ溅杈�(鍚鐢�)
+        vo.setBikeCount((long) bikesMapper.selectCount(
+                new QueryWrapper<Bikes>().lambda()
+                        .eq(Bikes::getType, Constants.ZERO)
+                        .eq(Bikes::getIsdeleted, Constants.ZERO)));
+        vo.setEleBikeCount((long) bikesMapper.selectCount(
+                new QueryWrapper<Bikes>().lambda()
+                        .eq(Bikes::getType, Constants.ONE)
+                        .eq(Bikes::getIsdeleted, Constants.ZERO)));
+        return vo;
+    }
+
+    @Override
+    public List<BikeIncomeStatVO> bikeIncome(BikeIncomeQueryDTO query) {
+        // 1. 瑙f瀽鏃舵:1/2/3 杩� N 澶�(鍚粖澶╁叡 N 澶�),4 鑷畾涔�
+        DateRange range = resolveRange(query);
+        Date start = range.start;
+        Date end = range.end;
+
+        // 2. 杞﹀瀷瀛楀吀:base_param type=3 鍗曡溅 / 4 鐢佃溅,渚涜溅鍨嬪悕 + 澶х被褰掔被
+        List<BaseParam> paramList = baseParamMapper.selectList(
+                new QueryWrapper<BaseParam>().lambda()
+                        .eq(BaseParam::getIsdeleted, Constants.ZERO)
+                        .in(BaseParam::getType, Constants.THREE, Constants.FOUR));
+        Map<String, BaseParam> paramMap = paramList.stream()
+                .collect(Collectors.toMap(BaseParam::getId, p -> p, (a, b) -> a));
+
+        // 3. 鏃舵鍐呭凡缁撶畻鐨勭杞︽娂閲戣鍗�(鏌ヨ妯″紡鍙傝�冨悗鍙� getBikeIncomeReportVOList:
+        //    type=0 鎶奸噾绫汇�乻tatus=4 宸茬粨绠椼�乸aramId 闈炵┖銆乸ayDate 钀藉湪鍖洪棿鍐�)
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.FOUR)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .isNotNull(Goodsorder::getParamId)
+                        .ne(Goodsorder::getParamId, StringUtils.EMPTY)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+
+        // 4. 鎸� paramId(杞﹀瀷)鍒嗙粍鍚堣缁撶畻閲戦 closeMoney(鍗曚綅:鍒�)
+        Map<String, BigDecimal> incomeByParam = new LinkedHashMap<>();
+        for (Goodsorder o : orders) {
+            BigDecimal amount = o.getCloseMoney() == null ? BigDecimal.ZERO : o.getCloseMoney();
+            incomeByParam.merge(o.getParamId(), amount, BigDecimal::add);
+        }
+
+        // 5. 缁勮缁撴灉:杞﹀瀷鍚� + 澶х被 + 鏀跺叆(鍒嗏啋鍏�,2浣�),鎸夋敹鍏ラ檷搴�
+        List<BikeIncomeStatVO> result = new ArrayList<>();
+        for (Map.Entry<String, BigDecimal> e : incomeByParam.entrySet()) {
+            BaseParam param = paramMap.get(e.getKey());
+            BikeIncomeStatVO vo = new BikeIncomeStatVO();
+            vo.setParamId(e.getKey());
+            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));
+            result.add(vo);
+        }
+        result.sort(Comparator.comparing(BikeIncomeStatVO::getIncome).reversed());
+        return result;
+    }
+
+    /**
+     * 鎸� base_param.type 娲剧敓杞﹁締澶х被:3 鑷杞� / 4 鐢靛姩杞�,鍏朵綑褰�"鏈煡"銆�
+     *
+     * @param type base_param.type(3 鍗曡溅绫诲瀷 / 4 鐢佃溅绫诲瀷)
+     * @return 澶х被涓枃鍚�
+     */
+    private String categoryOf(Integer type) {
+        if (type == null) {
+            return "鏈煡";
+        }
+        // 娉ㄦ剰:Constants.THREE 鏄� Integer銆丆onstants.FOUR 鏄� int(绫诲瀷涓嶄竴鑷�),
+        // 缁熶竴鎷嗘垚 int 姣旇緝,閬垮紑瀵瑰熀鏈被鍨嬭皟鐢� equals / 鍖呰绫诲紩鐢ㄦ瘮杈冪殑鍧�
+        int t = type;
+        if (t == Constants.THREE) {
+            return "鑷杞�";
+        }
+        if (t == Constants.FOUR) {
+            return "鐢靛姩杞�";
+        }
+        return "鏈煡";
+    }
+
+    /**
+     * 瑙f瀽鍚庣殑鏌ヨ鏃舵(鍧囧惈绔偣)銆�
+     */
+    private static final class DateRange {
+        /** 璧峰鏃堕棿(鍚�) */
+        final Date start;
+        /** 缁撴潫鏃堕棿(鍚�) */
+        final Date end;
+
+        DateRange(Date start, Date end) {
+            this.start = start;
+            this.end = end;
+        }
+    }
+
+    /**
+     * 瑙f瀽鏌ヨ鏃舵:dateType 1/2/3 鈫� 杩� N 澶�(鍚粖澶╁叡 N 澶�),4 鈫� 鑷畾涔夎捣姝�(鍧囧惈)銆�
+     * <p>{@link #bikeIncome} 涓� {@link #incomeStat} 鍏辩敤,淇濊瘉涓ょ鏃舵鍙e緞涓�鑷淬��
+     *
+     * @param query 鏃舵鏌ヨ鍏ュ弬
+     * @return 瑙f瀽鍚庣殑 [start, end] 鍖洪棿
+     */
+    private DateRange resolveRange(BikeIncomeQueryDTO query) {
+        if (query == null) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST);
+        }
+        Integer dateType = query.getDateType();
+        Date start;
+        Date end;
+        if (dateType != null && RECENT_DAYS.containsKey(dateType)) {
+            // 蹇嵎:杩� N 澶�,鍚粖澶╁叡 N 澶�,璧峰 = 浠婂ぉ寰�鍓� N-1 澶╃殑0鐐�
+            int days = RECENT_DAYS.get(dateType);
+            end = DateUtil.getEndOfDay(new Date());
+            start = DateUtil.getStartOfDay(DateUtil.increaseDay(new Date(), -(days - 1)));
+        } else if (dateType != null && dateType == 4) {
+            // 鑷畾涔�:璧锋鍧囧惈,鏍¢獙闈炵┖涓� start<=end
+            if (query.getStartDate() == null || query.getEndDate() == null
+                    || query.getStartDate().getTime() > query.getEndDate().getTime()) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鑷畾涔夋椂娈佃捣姝㈡棩鏈熶笉鍚堟硶");
+            }
+            start = query.getStartDate();
+            end = query.getEndDate();
+        } else {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏃舵绫诲瀷涓嶅悎娉�");
+        }
+        return new DateRange(start, end);
+    }
+
+    @Override
+    public IncomeStatVO incomeStat(BikeIncomeQueryDTO query) {
+        // 1. 瑙f瀽鏃舵
+        DateRange range = resolveRange(query);
+        Date start = range.start;
+        Date end = range.end;
+
+        // 2. 鏌ヨ鏈湡宸茬粨绠楃殑绉熻溅鎶奸噾璁㈠崟(鏌ヨ妯″紡鍙傝�冨悗鍙� getTotalData / getBikeIncomeReportVOList:
+        //    type=0 鎶奸噾绫汇�乻tatus=4 宸茬粨绠椼�乸ayDate 钀藉湪鍖洪棿鍐�)銆傛敹鍏ョ粺璁′笉闄愯溅鍨�,鏁呬笉绾︽潫 paramId
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.FOUR)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+
+        // 3. 鎸� payDate 鐨勬棩鏈�(yyyy-MM-dd)鍒嗙粍姹囨�� closeMoney(鍗曚綅:鍒�),鍚屾椂绱鍖洪棿鎬婚
+        Map<String, BigDecimal> sumByDay = new LinkedHashMap<>();
+        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);
+            totalCents = totalCents.add(amount);
+        }
+
+        // 4. 鐢熸垚瀹屾暣鏃ユ湡搴忓垪(鏌辩姸鍥炬í杞磋繛缁�),鏃犳暟鎹棩琛� 0銆�
+        //    娉�:DateUtil.getDateList 鐢� dEnd.after(begin) 姣旇緝,鑻ヤ袱绔椂鍒嗙涓嶄竴鑷翠細澶氱畻涓�澶�,
+        //    鏁呯粺涓�瑙勬暣鍒板綋澶� 0 鐐瑰啀鐢熸垚搴忓垪
+        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));
+            BigDecimal daySum = sumByDay.getOrDefault(vo.getDate(), BigDecimal.ZERO);
+            // 鍒嗏啋鍏�,2浣嶅皬鏁�
+            vo.setIncome(daySum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
+            dailyList.add(vo);
+        }
+
+        // 5. 鍖洪棿绱鏀跺叆(澶嶇敤宸叉煡鐨勬湰鏈熸暟鎹�,鍒嗏啋鍏�,閬垮厤閲嶅鏌ヨ)
+        BigDecimal totalIncome = totalCents.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+        IncomeStatVO result = new IncomeStatVO();
+        result.setDailyList(dailyList);
+        result.setTotalIncome(totalIncome);
+
+        // 6. 鐜瘮:绱ч偦鍓嶄竴绛夐暱鍖洪棿(鏁翠綋骞崇Щ -N 澶�,N=鍖洪棿澶╂暟)銆傚姣旀湡鍙眹鎬婚噾棰�,涓嶉渶姣忔棩鏄庣粏
+        int spanDays = DateUtil.daysBetweenDates(start, end) + 1;
+        BigDecimal chainAmount = sumClosedMoney(
+                DateUtil.increaseDay(start, -spanDays), DateUtil.increaseDay(end, -spanDays));
+        result.setChainAmount(chainAmount);
+        result.setChainRate(growthRate(totalIncome, chainAmount));
+
+        // 7. 鍚屾瘮:鍘诲勾鍚屾湡鍚岄暱搴﹀尯闂�(骞崇Щ -1 骞�,increaseYear 鎸� Calendar 绮剧‘骞崇Щ,闂板勾涓嶅け鐪�)
+        BigDecimal yearOnYearAmount = sumClosedMoney(
+                DateUtil.increaseYear(start, -1), DateUtil.increaseYear(end, -1));
+        result.setYearOnYearAmount(yearOnYearAmount);
+        result.setYearOnYearRate(growthRate(totalIncome, yearOnYearAmount));
+
+        return result;
+    }
+
+    /**
+     * 姹囨�绘寚瀹氭椂娈靛唴宸茬粨绠楃杞︽娂閲戣鍗曠殑缁撶畻鏀跺叆(closeMoney 涔嬪拰,鍒嗏啋鍏�)銆�
+     * <p>渚涚疮璁℃敹鍏ャ�佺幆姣斻�佸悓姣斿鐢�,缁熶竴鏀跺叆鍙e緞;鍙� select closeMoney 鍒椾互鍑忓皯鏁版嵁浼犺緭銆�
+     *
+     * @param start 璧峰鏃堕棿(鍚�)
+     * @param end   缁撴潫鏃堕棿(鍚�)
+     * @return 鍖洪棿缁撶畻鏀跺叆(鍏�,2浣嶅皬鏁�);鏃犳暟鎹繑鍥� 0
+     */
+    private BigDecimal sumClosedMoney(Date start, Date end) {
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .select(Goodsorder::getCloseMoney)
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.FOUR)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+        BigDecimal sum = BigDecimal.ZERO;
+        for (Goodsorder o : orders) {
+            if (o.getCloseMoney() != null) {
+                sum = sum.add(o.getCloseMoney());
+            }
+        }
+        // 鍒嗏啋鍏�,2浣嶅皬鏁�
+        return sum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * 璁$畻澧為暱鐜�:(current - base) / base 脳 100,淇濈暀2浣嶅皬鏁般��
+     *
+     * @param current 鏈湡鍊�
+     * @param base    瀵规瘮鏈熷��
+     * @return 澧為暱鐜�(%);base 涓� 0 鎴� null 鏃惰繑鍥� null(鏃犳硶璁$畻,鍓嶇鏄剧ず"-")
+     */
+    private BigDecimal growthRate(BigDecimal current, BigDecimal base) {
+        if (base == null || base.compareTo(BigDecimal.ZERO) == 0) {
+            // 瀵规瘮鏈熸棤鏀跺叆,澧為暱鐜囨棤鎰忎箟
+            return null;
+        }
+        BigDecimal current0 = current == null ? BigDecimal.ZERO : current;
+        return current0.subtract(base)
+                .multiply(PERCENT_BASE)
+                .divide(base, 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    @Override
+    public OperationCenterVO operationCenter() {
+        Date now = new Date();
+        // 浠婃棩璧锋(鍚鐐�),鐢ㄤ簬"浠婃棩*"绯诲垪缁熻
+        Date todayStart = DateUtil.getStartOfDay(now);
+        Date todayEnd = DateUtil.getEndOfDay(now);
+
+        OperationCenterVO vo = new OperationCenterVO();
+        // 浠婃棩鏃ユ湡 + 鏄熸湡鍑�
+        vo.setToday(DateUtil.getShortDateStr(now));
+        vo.setWeekDay(DateUtil.getWeekOfDate(now));
+
+        // 浠婃棩璁㈠崟鎬绘暟:浠婃棩宸叉敮浠樿鍗�(payStatus=1),鍚獞琛屾娂閲�(type=0)涓庡椁愬崱(type=1)
+        vo.setTodayOrderCount((long) goodsorderMapper.selectCount(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getPayStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, todayStart)
+                        .le(Goodsorder::getPayDate, todayEnd)));
+
+        // 杩涜涓鍗曟暟閲�:楠戣涓�(type=0 鎶奸噾銆佸凡鏀粯鏈粨绠� status=1),瀹炴椂鍦ㄩ��,涓嶉檺鏃ユ湡
+        vo.setOngoingOrderCount((long) goodsorderMapper.selectCount(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)));
+
+        // 浠婃棩濂楅鏀跺叆(鍏�):浠婃棩濂楅鍗¤喘涔�(type=1銆佸凡鏀粯)鐨� money 涔嬪拰
+        vo.setPackageIncome(sumMoney(Constants.ONE, todayStart, todayEnd));
+
+        // 浠婃棩鎬绘敹鍏�(鍏�):涓庢敹鍏ョ粺璁″悓鍙e緞(type=0 鎶奸噾 + status=4 宸茬粨绠� 鐨� closeMoney),澶嶇敤
+        vo.setTotalIncome(sumClosedMoney(todayStart, todayEnd));
+
+        return vo;
+    }
+
+    /**
+     * 姹囨�绘寚瀹氳鍗曠被鍨嬪湪鏃舵鍐呭凡鏀粯璁㈠崟鐨勬敮浠橀噾棰�(money 涔嬪拰,鍒嗏啋鍏�)銆�
+     * <p>渚涜繍钀ヤ腑蹇�"浠婃棩濂楅鏀跺叆"绛夋寜 type 缁熻鏀粯閲戦浣跨敤銆�
+     *
+     * @param type  璁㈠崟绫诲瀷(0 绉熻溅鎶奸噾 / 1 濂楅鍗¤喘涔�)
+     * @param start 璧峰鏃堕棿(鍚�)
+     * @param end   缁撴潫鏃堕棿(鍚�)
+     * @return 鍖洪棿鏀粯閲戦(鍏�,2浣嶅皬鏁�);鏃犳暟鎹繑鍥� 0
+     */
+    private BigDecimal sumMoney(Integer type, Date start, Date end) {
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .select(Goodsorder::getMoney)
+                        .eq(Goodsorder::getType, type)
+                        .eq(Goodsorder::getPayStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+        BigDecimal sum = BigDecimal.ZERO;
+        for (Goodsorder o : orders) {
+            if (o.getMoney() != null) {
+                sum = sum.add(o.getMoney());
+            }
+        }
+        // 鍒嗏啋鍏�,2浣嶅皬鏁�
+        return sum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    @Override
+    public PageData<OperationOrderVO> operationOrderPage(PageWrap<OperationOrderQueryDTO> pageWrap) {
+        // 鍒嗛〉瀵硅薄
+        IPage<OperationOrderVO> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        OperationOrderQueryDTO model = pageWrap.getModel() == null
+                ? new OperationOrderQueryDTO() : pageWrap.getModel();
+
+        // 鈹�鈹� 姝ラ1:鍒嗛〉涓绘煡璇� 鈹�鈹�
+        // 涓昏〃 goodsorder left join member(鍙栨墜鏈哄彿)+ left join base_param(鍙栫粨绠楄溅鍨嬪悕);
+        // 璁㈠崟鐘舵��(status)鏄犲皠鍒� VO 鍐呴儴鎵胯浇瀛楁,渚涘洖濉尯鍒嗗彇鏁板垎鏀�;涓嶅啀甯﹂�愯鍙栨暟瀛愭煡璇€��
+        // 娉�:bikeType 绛涢�変粛鐢� inSql 瀛愭煡璇⑩�斺�斿畠鍙綔 WHERE 鏉′欢銆佹暣椤垫墽琛屼竴娆�,涓嶆槸閫愯鎶曞奖,鍙繚鐣欍��
+        MPJLambdaWrapper<Goodsorder> wrapper = new MPJLambdaWrapper<Goodsorder>()
+                // 涓昏〃瀛楁:璁㈠崟涓婚敭銆佺紪鍙枫�佺粨绠楁椂闂�
+                .select(Goodsorder::getId, Goodsorder::getCode, Goodsorder::getCloseDate)
+                // 璁㈠崟鐘舵��:鍐呴儴鎵胯浇瀛楁(@JsonIgnore,涓嶈繑鍥炲墠绔�),鐢ㄤ簬鍥炲~鍒嗘敮鍒ゆ柇
+                .selectAs(Goodsorder::getStatus, OperationOrderVO::getOrderStatus)
+                // 缁撶畻杞﹀瀷鍚�:宸茬粨绠楄鍗曠洿鎺� left join base_param 鍙�(goodsorder.param_id鈫抌ase_param.name)
+                .selectAs(BaseParam::getName, OperationOrderVO::getSettleParamName)
+                // 鐢ㄦ埛鎵嬫満鍙�:left join member
+                .selectAs(Member::getPhone, OperationOrderVO::getPhone)
+                .leftJoin(Member.class, Member::getId, Goodsorder::getMemberId)
+                .leftJoin(BaseParam.class, BaseParam::getId, Goodsorder::getParamId)
+                // 鍥哄畾鏉′欢:榛樿鍙煡鎶奸噾璁㈠崟(type=0)
+                .eq(Goodsorder::getType, Constants.ZERO)
+                .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                // 璁㈠崟鐘舵��:1杩涜涓� / 4宸插畬缁�(鍙��)
+                .eq(Objects.nonNull(model.getStatus()), Goodsorder::getStatus, model.getStatus())
+                // 鐢ㄦ埛鎵嬫満鍙�:妯$硦鍖归厤(鍙��)
+                .like(StringUtils.isNotBlank(model.getPhone()), Member::getPhone, model.getPhone())
+                // 璁㈠崟绫诲瀷:鎸夐獞琛岃褰� member_rides.type 绛涢��(鍙��)
+                .inSql(Objects.nonNull(model.getBikeType()), Goodsorder::getId,
+                        "select ordre_id from member_rides where isdeleted = 0 and type = " + model.getBikeType())
+                .orderByDesc(Goodsorder::getPayDate);
+        IPage<OperationOrderVO> result = goodsorderMapper.selectJoinPage(page, OperationOrderVO.class, wrapper);
+        List<OperationOrderVO> records = result.getRecords();
+        // 鏃犳暟鎹洿鎺ヨ繑鍥�,閬垮厤绌� in() 鏌ヨ
+        if (records.isEmpty()) {
+            return PageData.from(result);
+        }
+
+        // 鈹�鈹� 姝ラ2:鏀堕泦褰撳墠椤佃鍗� id 鈹�鈹�
+        List<String> orderIds = records.stream().map(OperationOrderVO::getId).collect(Collectors.toList());
+
+        // 鈹�鈹� 姝ラ3:涓�娆℃�ф壒閲忔煡楠戣璁板綍(鍚獞琛岃溅鍨嬪悕) 鈹�鈹�
+        // left join base_param 鐩存帴甯﹀嚭楠戣杞﹀瀷鍚�(member_rides.param_id鈫抌ase_param.name鈫扢emberRides.paramName);
+        // 鎸� create_date desc 鎺掑簭,鍐呭瓨鎸夎鍗曞垎缁勫彇姣忕粍绗竴鏉″嵆"鏈�杩戜竴鏉¢獞琛岃褰�"
+        List<MemberRides> rides = memberRidesJoinMapper.selectJoinList(MemberRides.class,
+                new MPJLambdaWrapper<MemberRides>()
+                        .select(MemberRides::getOrdreId, MemberRides::getType, MemberRides::getRentDate,
+                                MemberRides::getBikeCode)
+                        .selectAs(BaseParam::getName, MemberRides::getParamName)
+                        .leftJoin(BaseParam.class, BaseParam::getId, MemberRides::getParamId)
+                        .eq(MemberRides::getIsdeleted, Constants.ZERO)
+                        .in(MemberRides::getOrdreId, orderIds)
+                        .orderByDesc(MemberRides::getCreateDate)
+                        .orderByDesc(MemberRides::getRentDate));
+        Map<String, MemberRides> latestRideByOrder = new LinkedHashMap<>();
+        for (MemberRides r : rides) {
+            // putIfAbsent:宸叉寜 create_date desc 鎺掑簭,棣栨鍑虹幇鍗宠璁㈠崟鏈�杩戜竴鏉�
+            latestRideByOrder.putIfAbsent(r.getOrdreId(), r);
+        }
+
+        // 鈹�鈹� 鍥炲~ VO(鍏ㄧ▼浠� 2 娆℃煡璇�:鍒嗛〉 / 楠戣;杞﹀瀷鍚嶅潎鐢� join 甯﹀嚭,鏃犻渶鍗曠嫭鏌ヨ溅鍨嬪瓧鍏�) 鈹�鈹�
+        for (OperationOrderVO vo : records) {
+            MemberRides latest = latestRideByOrder.get(vo.getId());
+            // 杞﹀瀷鍚嶅垎鏀�:杩涜涓�(status=1)鍙栨渶杩戦獞琛岀殑杞﹀瀷鍚�,鍚﹀垯(鍚凡缁撶畻)鍙栬鍗曠粨绠楄溅鍨嬪悕
+            Integer orderStatus = vo.getOrderStatus();
+            boolean inProgress = orderStatus != null && orderStatus.equals(Constants.ONE);
+            if (latest != null) {
+                // 璁㈠崟绫诲瀷銆侀獞琛屽紑濮嬫椂闂淬�佽溅杈嗙紪鍙�:缁熶竴鍙栨渶杩戜竴鏉¢獞琛岃褰�
+                // (杩涜涓嵆"褰撳墠楠戣杞﹁締",宸插畬缁撳嵆"鏈�鍚庨獞琛岃溅杈�";bike_code = bikes.code)
+                vo.setBikeType(latest.getType());
+                vo.setRentDate(latest.getRentDate());
+                if (inProgress) {
+                    // 杩涜涓�:楠戣杞﹀瀷鍚�(join 宸插甫鍑�,瀛樹簬 MemberRides.paramName)
+                    vo.setParamName(latest.getParamName());
+                    vo.setBikeCode(latest.getBikeCode());
+                }
+            }
+            if (!inProgress) {
+                // 宸茬粨绠�:璁㈠崟缁撶畻杞﹀瀷鍚�(鍒嗛〉 join 宸插甫鍑�,瀛樹簬 settleParamName)
+                vo.setParamName(vo.getSettleParamName());
+            }
+        }
+
+        return PageData.from(result);
+    }
+
+    @Override
+    public OrderRidesDetailVO orderRidesDetail(String orderId) {
+        OrderRidesDetailVO result = new OrderRidesDetailVO();
+        // 璁㈠崟鍙蜂负绌�:鐩存帴杩斿洖绌虹粨鏋�(涓嶆姏寮傚父,鍓嶇鎸� hasTrack=false 鍏滃簳)
+        if (StringUtils.isBlank(orderId)) {
+            result.setHasTrack(false);
+            result.setRides(Collections.emptyList());
+            return result;
+        }
+
+        // 1. 鏌ヨ璁㈠崟涓嬪叏閮ㄩ獞琛岃褰�(鎸夊垱寤烘椂闂村崌搴�,杩樺師鍚屼竴璁㈠崟澶氭楠戣鐨勫厛鍚�)
+        List<MemberRides> ridesList = memberRidesJoinMapper.selectList(
+                new QueryWrapper<MemberRides>().lambda()
+                        .eq(MemberRides::getOrdreId, orderId)
+                        .eq(MemberRides::getIsdeleted, Constants.ZERO)
+                        .orderByAsc(MemberRides::getCreateDate));
+        if (ridesList.isEmpty()) {
+            // 璁㈠崟涓嬫棤楠戣璁板綍(鐞嗚涓嶅簲鍑虹幇,鍏滃簳杩斿洖绌�)
+            result.setHasTrack(false);
+            result.setRides(Collections.emptyList());
+            return result;
+        }
+
+        // 2. 杞﹁締绫诲瀷鍙栭鏉¢獞琛� type(0鑷杞�/1鐢佃溅):鍐冲畾鏄惁鏌ヨ建杩�
+        Integer bikeType = ridesList.get(0).getType();
+        result.setBikeType(bikeType);
+        result.setBikeTypeName(bikeTypeNameOf(bikeType));
+        boolean isEbike = bikeType != null && bikeType.equals(Constants.ONE);
+
+        // 3. 杞ㄨ抗棰勮浇(浠呯數杞�):涓�娆℃�ф煡鍑鸿璁㈠崟鎵�鏈夐獞琛岃建杩�,鎸� rides_id 鍒嗙粍銆佹寜涓婃姤鏃堕棿鍗囧簭,
+        //    閬垮厤閫愭潯楠戣 N 娆℃煡杞ㄨ抗;鑷杞�(type=0)璧� MQTT 鏃� GPS,璺宠繃銆�
+        Map<String, List<OrderRideTrackVO>> trackByRide = new HashMap<>();
+        if (isEbike) {
+            List<String> ridesIds = ridesList.stream()
+                    .map(MemberRides::getId).collect(Collectors.toList());
+            List<MemberRidesTrack> tracks = memberRidesTrackMapper.selectList(
+                    new QueryWrapper<MemberRidesTrack>().lambda()
+                            .select(MemberRidesTrack::getRidesId, MemberRidesTrack::getLongitude,
+                                    MemberRidesTrack::getLatitude, MemberRidesTrack::getReportTime)
+                            .eq(MemberRidesTrack::getIsdeleted, Constants.ZERO)
+                            .in(MemberRidesTrack::getRidesId, ridesIds)
+                            .orderByAsc(MemberRidesTrack::getReportTime));
+            for (MemberRidesTrack t : tracks) {
+                OrderRideTrackVO tv = new OrderRideTrackVO();
+                tv.setLongitude(t.getLongitude());
+                tv.setLatitude(t.getLatitude());
+                tv.setReportTime(t.getReportTime());
+                trackByRide.computeIfAbsent(t.getRidesId(), k -> new ArrayList<>()).add(tv);
+            }
+        }
+
+        // 4. 缁勮楠戣璁板綍鍒楄〃(姣忔潯鎸傚搴旇建杩圭偣;鑷杞︿竴寰嬬┖杞ㄨ抗)
+        List<OrderRideItemVO> rides = new ArrayList<>(ridesList.size());
+        for (MemberRides r : ridesList) {
+            OrderRideItemVO item = new OrderRideItemVO();
+            item.setRidesId(r.getId());
+            item.setStatus(r.getStatus());
+            item.setStatusName(rideStatusNameOf(r.getStatus()));
+            item.setRentDate(r.getRentDate());
+            item.setBackDate(r.getBackDate());
+            item.setBikeCode(r.getBikeCode());
+            item.setDuration(r.getDuration());
+            item.setBikeTypeName(bikeTypeNameOf(r.getType()));
+            item.setTracks(isEbike
+                    ? trackByRide.getOrDefault(r.getId(), Collections.emptyList())
+                    : Collections.emptyList());
+            rides.add(item);
+        }
+        result.setRides(rides);
+
+        // 5. 杞ㄨ抗鍙敤鎬� + 鑷杞︽棤杞ㄨ抗鎻愮ず
+        if (isEbike) {
+            result.setHasTrack(true);
+        } else {
+            result.setHasTrack(false);
+            result.setNoTrackMessage("璇ヨ鍗曚负鑷杞﹁鍗�,鏃犺溅杈嗚建杩�");
+        }
+        return result;
+    }
+
+    /**
+     * 楠戣鐘舵�� 鈫� 涓枃鍚嶃��
+     * <p>member_rides.status:0璇锋眰寮�閿佷腑 / 1楠戣涓� / 2宸茶繕杞� / 3寮�閿佸け璐� / 4涓存椂閿佽溅銆�
+     *
+     * @param status 楠戣鐘舵�佸師鏂�(鍙兘涓� null)
+     * @return 鐘舵�佷腑鏂囧悕(绌哄��/鏈煡鍙栧�艰繑鍥�"鏈煡")
+     */
+    private String rideStatusNameOf(Integer status) {
+        if (status == null) {
+            return "鏈煡";
+        }
+        switch (status) {
+            case 0:
+                return "璇锋眰寮�閿佷腑";
+            case 1:
+                return "楠戣涓�";
+            case 2:
+                return "宸茶繕杞�";
+            case 3:
+                return "寮�閿佸け璐�";
+            case 4:
+                return "涓存椂閿佽溅";
+            default:
+                return "鏈煡";
+        }
+    }
+
+    /**
+     * 杞﹁締绫诲瀷 鈫� 涓枃鍚嶃��
+     * <p>member_rides.type:0鑷杞� / 1鐢靛姩杞︺��
+     *
+     * @param type 杞﹁締绫诲瀷(鍙兘涓� null)
+     * @return 绫诲瀷涓枃鍚�(鏈煡鍙栧�艰繑鍥�"鏈煡")
+     */
+    private String bikeTypeNameOf(Integer type) {
+        if (type == null) {
+            return "鏈煡";
+        }
+        if (type.equals(Constants.ZERO)) {
+            return "鑷杞�";
+        }
+        if (type.equals(Constants.ONE)) {
+            return "鐢靛姩杞�";
+        }
+        return "鏈煡";
+    }
+
+    @Override
+    public IncomeStatVO incomeStat30() {
+        // 杩�30澶�(鍚粖澶╁叡30澶�),澶嶇敤 incomeStat 鏃㈡湁瀹炵幇涓庡彛寰�
+        BikeIncomeQueryDTO query = new BikeIncomeQueryDTO();
+        query.setDateType(3);
+        return incomeStat(query);
+    }
+
+    @Override
+    public BikeUsageStatVO bikeUsageStat() {
+        // status:0 绌洪棽 / 1 浣跨敤涓�;绂佺敤(3)涓嶈鍏�;type:0 鑷杞� / 1 鐢靛姩杞�
+        BikeUsageStatVO vo = new BikeUsageStatVO();
+        vo.setBikeIdle(countBikeByStatus(Constants.ZERO, Constants.ZERO));
+        vo.setBikeInUse(countBikeByStatus(Constants.ZERO, Constants.ONE));
+        vo.setEleBikeIdle(countBikeByStatus(Constants.ONE, Constants.ZERO));
+        vo.setEleBikeInUse(countBikeByStatus(Constants.ONE, Constants.ONE));
+        return vo;
+    }
+
+    /**
+     * 鎸夎溅杈嗙被鍨� + 鐘舵�佺粺璁℃湭鍒犻櫎杞﹁締鏁伴噺銆�
+     *
+     * @param type   杞﹁締绫诲瀷 0 鑷杞� / 1 鐢靛姩杞�
+     * @param status 杞﹁締鐘舵�� 0 绌洪棽 / 1 浣跨敤涓�
+     * @return 婊¤冻鏉′欢鐨勮溅杈嗘暟
+     */
+    private long countBikeByStatus(Integer type, Integer status) {
+        return bikesMapper.selectCount(
+                new QueryWrapper<Bikes>().lambda()
+                        .eq(Bikes::getType, type)
+                        .eq(Bikes::getStatus, status)
+                        .eq(Bikes::getIsdeleted, Constants.ZERO));
+    }
+
+    @Override
+    public PackageSourceStatVO packageSourceStat() {
+        // 鏈湀/鏈勾璧锋(pay_date 钀藉湪鍖洪棿鍐�);goodsorder type=1 濂楅鍗¤喘涔� + payStatus=1 宸叉敮浠�
+        Calendar cal = Calendar.getInstance();
+        Date monthStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.MONTH));
+        Date monthEnd = DateUtil.getEndOfDay(new Date());
+        Date yearStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.YEAR));
+        Date yearEnd = DateUtil.getEndOfDay(new Date());
+
+        PackageSourceStatVO vo = new PackageSourceStatVO();
+        vo.setMonth(countPackageSource(monthStart, monthEnd));
+        vo.setYear(countPackageSource(yearStart, yearEnd));
+        return vo;
+    }
+
+    /**
+     * 缁熻鎸囧畾鏃舵鍐呭椁愰攢鍞潵婧愭暟閲忋��
+     * <p>鎶栭煶鍏戞崲 = payWay 2,灏忕▼搴忚喘涔� = payWay 0(寰俊)銆�
+     *
+     * @param start 璧峰鏃堕棿(鍚�)
+     * @param end   缁撴潫鏃堕棿(鍚�)
+     * @return 鎶栭煶/灏忕▼搴忓椁愭暟閲�
+     */
+    private PackageSourceStatVO.PeriodCount countPackageSource(Date start, Date end) {
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .select(Goodsorder::getPayWay)
+                        .eq(Goodsorder::getType, Constants.ONE)
+                        .eq(Goodsorder::getPayStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+        long douyin = 0L;
+        long mini = 0L;
+        for (Goodsorder o : orders) {
+            if (o.getPayWay() == null) {
+                continue;
+            }
+            if (o.getPayWay() == PAY_WAY_DOUYIN) {
+                douyin++;
+            } else if (o.getPayWay() == Constants.ZERO) {
+                mini++;
+            }
+        }
+        PackageSourceStatVO.PeriodCount pc = new PackageSourceStatVO.PeriodCount();
+        pc.setDouyinCount(douyin);
+        pc.setMiniCount(mini);
+        return pc;
+    }
+
+    @Override
+    public DashboardVO dashboard() {
+        DashboardVO vo = new DashboardVO();
+
+        // 鍚勬椂娈佃捣姝�:鏈湀/鏄ㄦ棩/浠婃棩
+        Date now = new Date();
+        Calendar cal = Calendar.getInstance();
+        Date monthStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.MONTH));
+        Date monthEnd = DateUtil.getEndOfDay(now);
+        Date yesterdayStart = DateUtil.getStartOfDay(DateUtil.increaseDay(now, -1));
+        Date yesterdayEnd = DateUtil.getEndOfDay(DateUtil.increaseDay(now, -1));
+        Date todayStart = DateUtil.getStartOfDay(now);
+        Date todayEnd = DateUtil.getEndOfDay(now);
+
+        // 鏀剁泭(鍙e緞鍚� incomeStat:type=0 鎶奸噾 + status=4 宸茬粨绠� 鐨� closeMoney),澶嶇敤 sumClosedMoney
+        vo.setMonthIncome(sumClosedMoney(monthStart, monthEnd));
+        vo.setYesterdayIncome(sumClosedMoney(yesterdayStart, yesterdayEnd));
+        vo.setTodayIncome(sumClosedMoney(todayStart, todayEnd));
+
+        // 璁㈠崟鏁�:宸叉敮浠�(payStatus=1)璁㈠崟璁℃暟
+        vo.setMonthOrderCount(countPaidOrders(monthStart, monthEnd));
+        vo.setYesterdayOrderCount(countPaidOrders(yesterdayStart, yesterdayEnd));
+        vo.setTodayOrderCount(countPaidOrders(todayStart, todayEnd));
+
+        // 杞﹁締:浠呰繑鍥炴湭鍒犻櫎鎬绘暟
+        vo.setTotalBikeCount((long) bikesMapper.selectCount(
+                new QueryWrapper<Bikes>().lambda().eq(Bikes::getIsdeleted, Constants.ZERO)));
+
+        // 瀹㈡埛鏁�:鎬讳細鍛�=鏈垹闄ゅ叏閮�;浠婃棩/鏄ㄦ棩鏂板鎸� create_date 钀藉湪瀵瑰簲鍖洪棿(涓� overview 鍙e緞涓�鑷�)
+        vo.setTotalMemberCount((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda().eq(Member::getIsdeleted, Constants.ZERO)));
+        vo.setYesterdayNewMember((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda()
+                        .eq(Member::getIsdeleted, Constants.ZERO)
+                        .ge(Member::getCreateDate, yesterdayStart)
+                        .le(Member::getCreateDate, yesterdayEnd)));
+        vo.setTodayNewMember((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda()
+                        .eq(Member::getIsdeleted, Constants.ZERO)
+                        .ge(Member::getCreateDate, todayStart)));
+        return vo;
+    }
+
+    /**
+     * 缁熻鎸囧畾鏃舵鍐呭凡鏀粯璁㈠崟鏁伴噺(payStatus=1)銆�
+     *
+     * @param start 璧峰鏃堕棿(鍚�)
+     * @param end   缁撴潫鏃堕棿(鍚�)
+     * @return 宸叉敮浠樿鍗曟暟
+     */
+    private long countPaidOrders(Date start, Date end) {
+        return goodsorderMapper.selectCount(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getPayStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+    }
+
+    /**
+     * 鍙栨湰鏈�/鏈勾绗竴澶╃殑鏃堕棿鍘熷��(鏃跺垎绉掑綊闆跺墠鐨勬绉�),鐢ㄤ簬鏋勯�犲尯闂磋捣濮嬨��
+     * <p>鐢� Calendar 鎶娿�屽綋鏈�1鏃� 00:00:00.000銆嶆垨銆屽綋骞�1鏈�1鏃� 00:00:00.000銆嶅彇鍑恒��
+     *
+     * @param cal   鏃ュ巻(浼氳淇敼)
+     * @param field Calendar.MONTH 鏈湀绗竴澶� / Calendar.YEAR 鏈勾绗竴澶�
+     * @return 鍖洪棿璧峰鏃堕棿
+     */
+    private Date getFirstMs(Calendar cal, int field) {
+        cal.setTime(new Date());
+        if (field == Calendar.MONTH) {
+            cal.set(Calendar.DAY_OF_MONTH, 1);
+        } else {
+            cal.set(Calendar.MONTH, Calendar.JANUARY);
+            cal.set(Calendar.DAY_OF_MONTH, 1);
+        }
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+}

--
Gitblit v1.9.3