2026-06-24 f4bd1f3c89d906131495a0aca5aaf82966378510
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
package cn.iocoder.yudao.module.trade.service.brokerage;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO;
import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
 
import java.time.LocalDateTime;
import java.util.*;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMapByFilter;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
/**
 * 分销用户 Service 实现类
 *
 * @author owen
 */
@Service
@Validated
public class BrokerageUserServiceImpl implements BrokerageUserService {
 
    @Resource
    private BrokerageUserMapper brokerageUserMapper;
 
    @Resource
    private TradeConfigService tradeConfigService;
 
    @Resource
    private MemberUserApi memberUserApi;
 
    @Override
    public BrokerageUserDO getBrokerageUser(Long id) {
        return brokerageUserMapper.selectById(id);
    }
 
    @Override
    public PageResult<BrokerageUserDO> getBrokerageUserPage(BrokerageUserPageReqVO pageReqVO) {
        List<Long> childIds = getChildUserIdsByLevel(pageReqVO.getBindUserId(), pageReqVO.getLevel());
        // 有”绑定用户编号“查询条件时,没有查到下级会员,直接返回空
        if (pageReqVO.getBindUserId() != null && CollUtil.isEmpty(childIds)) {
            return PageResult.empty();
        }
        return brokerageUserMapper.selectPage(pageReqVO, childIds);
    }
 
    @Override
    public void updateBrokerageUserId(Long id, Long bindUserId) {
        // 校验存在
        BrokerageUserDO brokerageUser = validateBrokerageUserExists(id);
        // 绑定关系未发生变化
        if (Objects.equals(brokerageUser.getBindUserId(), bindUserId)) {
            return;
        }
 
        // 情况一:清除推广员
        if (bindUserId == null) {
            // 清除推广员
            brokerageUserMapper.updateBindUserIdAndBindUserTimeToNull(id);
            return;
        }
 
        // 情况二:修改推广员
        validateCanBindUser(brokerageUser, bindUserId);
        brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(id)));
    }
 
    @Override
    public void updateBrokerageUserEnabled(Long id, Boolean enabled) {
        // 校验存在
        validateBrokerageUserExists(id);
        if (BooleanUtil.isTrue(enabled)) {
            // 开通推广资格
            brokerageUserMapper.updateById(new BrokerageUserDO().setId(id)
                    .setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now()));
        } else {
            // 取消推广资格
            brokerageUserMapper.updateEnabledFalseAndBrokerageTimeToNull(id);
        }
    }
 
    private BrokerageUserDO validateBrokerageUserExists(Long id) {
        BrokerageUserDO brokerageUserDO = brokerageUserMapper.selectById(id);
        if (brokerageUserDO == null) {
            throw exception(BROKERAGE_USER_NOT_EXISTS);
        }
        return brokerageUserDO;
    }
 
    @Override
    public BrokerageUserDO getBindBrokerageUser(Long id) {
        return Optional.ofNullable(id)
                .map(this::getBrokerageUser)
                .map(BrokerageUserDO::getBindUserId)
                .map(this::getBrokerageUser)
                .orElse(null);
    }
 
    @Override
    public BrokerageUserDO getOrCreateBrokerageUser(Long id) {
        BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(id);
        // 特殊:人人分销的情况下,如果分销人为空则创建分销人
        if (brokerageUser == null && ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(),
                tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) {
            brokerageUser = new BrokerageUserDO().setId(id).setBrokerageEnabled(true).setBrokeragePrice(0)
                    .setBrokerageTime(LocalDateTime.now()).setFrozenPrice(0);
            brokerageUserMapper.insert(brokerageUser);
        }
        return brokerageUser;
    }
 
    @Override
    public boolean updateUserPrice(Long id, Integer price) {
        if (price > 0) {
            brokerageUserMapper.updatePriceIncr(id, price);
        } else if (price < 0) {
            return brokerageUserMapper.updatePriceDecr(id, price) > 0;
        }
        return true;
    }
 
    @Override
    public void updateUserFrozenPrice(Long id, Integer frozenPrice) {
        if (frozenPrice > 0) {
            brokerageUserMapper.updateFrozenPriceIncr(id, frozenPrice);
        } else if (frozenPrice < 0) {
            brokerageUserMapper.updateFrozenPriceDecr(id, frozenPrice);
        }
    }
 
    @Override
    public void updateFrozenPriceDecrAndPriceIncr(Long id, Integer frozenPrice) {
        Assert.isTrue(frozenPrice < 0);
        int updateRows = brokerageUserMapper.updateFrozenPriceDecrAndPriceIncr(id, frozenPrice);
        if (updateRows == 0) {
            throw exception(BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH);
        }
    }
 
    @Override
    public Long getBrokerageUserCountByBindUserId(Long bindUserId, Integer level) {
        List<Long> childIds = getChildUserIdsByLevel(bindUserId, level);
        return (long) CollUtil.size(childIds);
    }
 
    @Override
    public boolean bindBrokerageUser(Long userId, Long bindUserId) {
        // 1. 获得分销用户
        boolean isNewBrokerageUser = false;
        BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(userId);
        if (brokerageUser == null) { // 分销用户不存在的情况:1. 新注册;2. 旧数据;3. 分销功能关闭后又打开
            isNewBrokerageUser = true;
            brokerageUser = new BrokerageUserDO().setId(userId).setBrokerageEnabled(false).setBrokeragePrice(0).setFrozenPrice(0);
        }
 
        // 2.1 校验是否能绑定用户
        boolean validated = isUserCanBind(brokerageUser);
        if (!validated) {
            return false;
        }
        // 2.3 校验能否绑定
        validateCanBindUser(brokerageUser, bindUserId);
        // 2.3 绑定用户
        if (isNewBrokerageUser) {
            Integer enabledCondition = tradeConfigService.getTradeConfig().getBrokerageEnabledCondition();
            if (BrokerageEnabledConditionEnum.ALL.getCondition().equals(enabledCondition)) { // 人人分销:用户默认就有分销资格
                brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now());
            } else {
                brokerageUser.setBrokerageEnabled(false).setBrokerageTime(LocalDateTime.now());
            }
            brokerageUserMapper.insert(fillBindUserData(bindUserId, brokerageUser));
        } else {
            brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(userId)));
        }
        return true;
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBrokerageUser(BrokerageUserCreateReqVO createReqVO) {
        // 1.1 校验分销用户是否已存在
        BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(createReqVO.getUserId());
        if (brokerageUser != null) {
            throw exception(BROKERAGE_CREATE_USER_EXISTS);
        }
        // 1.2 校验是否能绑定用户
        brokerageUser = BeanUtils.toBean(createReqVO, BrokerageUserDO.class).setId(createReqVO.getUserId())
                .setBrokerageTime(LocalDateTime.now());
        validateCanBindUser(brokerageUser, createReqVO.getBindUserId());
 
        // 2. 创建分销人
        brokerageUserMapper.insert(brokerageUser);
        return brokerageUser.getId();
    }
 
    /**
     * 补全绑定用户的字段
     *
     * @param bindUserId    绑定的用户编号
     * @param brokerageUser update 对象
     * @return 补全后的 update 对象
     */
    private BrokerageUserDO fillBindUserData(Long bindUserId, BrokerageUserDO brokerageUser) {
        return brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now());
    }
 
    @Override
    public Boolean getUserBrokerageEnabled(Long userId) {
        // 全局分销功能是否开启
        TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
        if (tradeConfig == null || BooleanUtil.isFalse(tradeConfig.getBrokerageEnabled())) {
            return false;
        }
 
        // 用户是否有分销资格
        return Optional.ofNullable(getBrokerageUser(userId))
                .map(BrokerageUserDO::getBrokerageEnabled)
                .orElse(false);
    }
 
    @Override
    public PageResult<AppBrokerageUserRankByUserCountRespVO> getBrokerageUserRankPageByUserCount(AppBrokerageUserRankPageReqVO pageReqVO) {
        IPage<AppBrokerageUserRankByUserCountRespVO> pageResult = brokerageUserMapper.selectCountPageGroupByBindUserId(MyBatisUtils.buildPage(pageReqVO),
                ArrayUtil.get(pageReqVO.getTimes(), 0), ArrayUtil.get(pageReqVO.getTimes(), 1));
        return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
    }
 
    @Override
    public PageResult<AppBrokerageUserChildSummaryRespVO> getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId) {
        // 1.1 查询下级用户编号列表
        List<Long> childIds = getChildUserIdsByLevel(userId, pageReqVO.getLevel());
        if (CollUtil.isEmpty(childIds)) {
            return PageResult.empty();
        }
        // 1.2 根据昵称过滤下级用户
        List<MemberUserRespDTO> users = memberUserApi.getUserList(childIds);
        Map<Long, MemberUserRespDTO> userMap = convertMapByFilter(users,
                user -> StrUtil.contains(user.getNickname(), pageReqVO.getNickname()),
                MemberUserRespDTO::getId);
        if (CollUtil.isEmpty(userMap)) {
            return PageResult.empty();
        }
 
        // 2. 分页查询
        IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
                MyBatisUtils.buildPage(pageReqVO), BrokerageRecordBizTypeEnum.ORDER.getType(),
                BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), userMap.keySet(), pageReqVO.getSortingField()
        );
 
        // 3. 拼接数据并返回
        BrokerageUserConvert.INSTANCE.copyTo(pageResult.getRecords(), userMap);
        return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
    }
 
    private boolean isUserCanBind(BrokerageUserDO user) {
        // 校验分销功能是否启用
        TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
        if (tradeConfig == null || !BooleanUtil.isTrue(tradeConfig.getBrokerageEnabled())) {
            return false;
        }
 
        // 校验分销关系绑定模式
        if (BrokerageBindModeEnum.REGISTER.getMode().equals(tradeConfig.getBrokerageBindMode())) {
            // 判断是否为新用户:注册时间在 30 秒内的,都算新用户
            if (!isNewRegisterUser(user.getId())) {
                throw exception(BROKERAGE_BIND_MODE_REGISTER); // 只有在注册时可以绑定
            }
        } else if (BrokerageBindModeEnum.ANYTIME.getMode().equals(tradeConfig.getBrokerageBindMode())) {
            if (user.getBindUserId() != null) {
                throw exception(BROKERAGE_BIND_OVERRIDE); // 已绑定了推广人
            }
        }
        return true;
    }
 
    /**
     * 判断是否为新用户
     * <p>
     * 标准:注册时间在 30 秒内的,都算新用户
     * <p>
     * 疑问:为什么通过这样的方式实现?
     * 回答:因为注册在 member 模块,希望它和 trade 模块解耦,所以只能用这种约定的逻辑。
     *
     * @param userId 用户编号
     * @return 是否新用户
     */
    private boolean isNewRegisterUser(Long userId) {
        MemberUserRespDTO user = memberUserApi.getUser(userId);
        return user != null && LocalDateTimeUtils.afterNow(user.getCreateTime().plusSeconds(30));
    }
 
    private void validateCanBindUser(BrokerageUserDO user, Long bindUserId) {
        // 1.1 校验推广人是否存在
        MemberUserRespDTO bindUserInfo = memberUserApi.getUser(bindUserId);
        if (bindUserInfo == null) {
            throw exception(BROKERAGE_USER_NOT_EXISTS);
        }
        // 1.2 校验要绑定的用户有无推广资格
        BrokerageUserDO bindUser = getOrCreateBrokerageUser(bindUserId);
        if (bindUser == null || BooleanUtil.isFalse(bindUser.getBrokerageEnabled())) {
            throw exception(BROKERAGE_BIND_USER_NOT_ENABLED);
        }
 
        // 2. 校验绑定自己
        if (Objects.equals(user.getId(), bindUserId)) {
            throw exception(BROKERAGE_BIND_SELF);
        }
 
        // 3. 下级不能绑定自己的上级
        for (int i = 0; i <= Short.MAX_VALUE; i++) {
            if (Objects.equals(bindUser.getBindUserId(), user.getId())) {
                throw exception(BROKERAGE_BIND_LOOP);
            }
            bindUser = getBrokerageUser(bindUser.getBindUserId());
            // 找到根节点,结束循环
            if (bindUser == null || bindUser.getBindUserId() == null) {
                break;
            }
        }
    }
 
    /**
     * 根据绑定用户编号,获得下级用户编号列表
     *
     * @param bindUserId 绑定用户编号
     * @param level      下级用户的层级。
     *                   如果 level 为空,则查询 1+2 两个层级
     * @return 下级用户编号列表
     */
    private List<Long> getChildUserIdsByLevel(Long bindUserId, Integer level) {
        if (bindUserId == null) {
            return Collections.emptyList();
        }
        // 先查第 1 级
        List<Long> bindUserIds = brokerageUserMapper.selectIdListByBindUserIdIn(Collections.singleton(bindUserId));
        if (CollUtil.isEmpty(bindUserIds)) {
            return Collections.emptyList();
        }
 
        // 情况一:level 为空,查询所有级别
        if (level == null) {
            // 再查第 2 级,并合并结果
            bindUserIds.addAll(brokerageUserMapper.selectIdListByBindUserIdIn(bindUserIds));
            return bindUserIds;
        }
        // 情况二:level 为 1,只查询第 1 级
        if (level == 1) {
            return bindUserIds;
        }
        // 情况三:level 为 1,只查询第 2 级
        if (level == 2) {
            return brokerageUserMapper.selectIdListByBindUserIdIn(bindUserIds);
        }
        throw exception(BROKERAGE_USER_LEVEL_NOT_SUPPORT);
    }
 
}