liujie
昨天 c983a0a398504697a777ffae48e59961b5ee2bd7
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
package com.ruoyi.user.service.impl;
 
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.user.domain.AccountAddress;
import com.ruoyi.user.domain.AccountUser;
import com.ruoyi.user.domain.vo.AccountUserDetailVo;
import com.ruoyi.user.domain.vo.MemberOptionVo;
import com.ruoyi.user.mapper.AccountAddressMapper;
import com.ruoyi.user.mapper.AccountAgreementMapper;
import com.ruoyi.user.mapper.AccountCertificationMapper;
import com.ruoyi.user.mapper.AccountUserMapper;
import com.ruoyi.user.mapper.AccountWarehouseMapper;
import com.ruoyi.user.service.IAccountUserService;
 
/**
 * 会员用户 业务层实现
 *
 * @author ruoyi
 */
@Service
public class AccountUserServiceImpl implements IAccountUserService
{
    @Autowired
    private AccountUserMapper accountUserMapper;
 
    @Autowired
    private AccountCertificationMapper accountCertificationMapper;
 
    @Autowired
    private AccountAddressMapper accountAddressMapper;
 
    @Autowired
    private AccountAgreementMapper accountAgreementMapper;
 
    @Autowired
    private AccountWarehouseMapper accountWarehouseMapper;
 
    @Override
    public AccountUser selectAccountUserById(Long userId)
    {
        return accountUserMapper.selectAccountUserById(userId);
    }
 
    @Override
    public List<AccountUser> selectAccountUserList(AccountUser accountUser)
    {
        return accountUserMapper.selectAccountUserList(accountUser);
    }
 
    @Override
    public boolean checkUsernameUnique(AccountUser accountUser)
    {
        Long userId = StringUtils.isNull(accountUser.getUserId()) ? -1L : accountUser.getUserId();
        AccountUser info = accountUserMapper.selectAccountUserByUsername(accountUser.getUserName());
        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
        {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }
 
    @Override
    public boolean checkPhoneUnique(AccountUser accountUser)
    {
        // 手机号非必填,为空时不做唯一性校验,否则会与库里其它空手机号记录相撞
        if (StringUtils.isEmpty(accountUser.getPhone()))
        {
            return UserConstants.UNIQUE;
        }
        Long userId = StringUtils.isNull(accountUser.getUserId()) ? -1L : accountUser.getUserId();
        AccountUser info = accountUserMapper.selectAccountUserByPhone(accountUser.getPhone());
        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
        {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }
 
    @Override
    public int insertAccountUser(AccountUser accountUser)
    {
        accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
        return accountUserMapper.insertAccountUser(accountUser);
    }
 
    @Override
    public int updateAccountUser(AccountUser accountUser)
    {
        // 不为空才加密;为空时保持 null,mapper 的 <if> 会跳过该列,原密码不受影响
        if (StringUtils.isNotEmpty(accountUser.getPassword()))
        {
            accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
        }
        return accountUserMapper.updateAccountUser(accountUser);
    }
 
    @Override
    public int deleteAccountUserByIds(Long[] userIds)
    {
        return accountUserMapper.deleteAccountUserByIds(userIds);
    }
 
    @Override
    public AccountUserDetailVo selectAccountUserDetail(Long userId)
    {
        AccountUserDetailVo detail = new AccountUserDetailVo();
        detail.setUser(accountUserMapper.selectAccountUserVoById(userId));
        detail.setCertification(accountCertificationMapper.selectCertificationByUserId(userId));
        detail.setAddresses(accountAddressMapper.selectAddressByUserId(userId));
        detail.setAgreements(accountAgreementMapper.selectAgreementByUserId(userId));
        detail.setWarehouses(accountWarehouseMapper.selectWarehouseByUserId(userId));
        return detail;
    }
 
    @Override
    public int updateAccountUserRelation(AccountUser accountUser)
    {
        AccountUser old = accountUserMapper.selectAccountUserById(accountUser.getUserId());
        if (StringUtils.isNull(old))
        {
            return 0;
        }
        validateSupplier(old, accountUser.getSupplierId());
        // 绑定时间由服务端推导:换成另一个人的记为当前时间,解绑则清空,
        // 关系没动则保留原值——否则每次保存都会把绑定时间刷成今天。
        Date now = DateUtils.getNowDate();
        accountUser.setReferrerBindTime(
            resolveBindTime(old.getReferrerId(), accountUser.getReferrerId(), old.getReferrerBindTime(), now));
        accountUser.setSupplierBindTime(
            resolveBindTime(old.getSupplierId(), accountUser.getSupplierId(), old.getSupplierBindTime(), now));
        return accountUserMapper.updateUserRelation(accountUser);
    }
 
    @Override
    public List<MemberOptionVo> searchSupplierCandidates(Long userId, String keyword)
    {
        // 空关键字不回结果:候选是等级高于该会员的全量集合,放开等于把大半张会员表倒进下拉。
        // 与 AccountCityCompanyServiceImpl.searchL4Member 是同一口径(那边搜 L4,这边搜等级高于该会员)。
        if (StringUtils.isEmpty(keyword))
        {
            return Collections.emptyList();
        }
        AccountUser member = accountUserMapper.selectAccountUserById(userId);
        if (StringUtils.isNull(member))
        {
            throw new ServiceException("会员不存在");
        }
        // 严格高于该会员的等级集合;该会员已是最高等级时为空,必须提前返回——
        // 空集合会让候选 SQL 拼出 in () 的语法错误。
        List<String> levels = AccountUser.levelsAbove(member.getSystemLevel());
        if (levels.isEmpty())
        {
            return Collections.emptyList();
        }
        String kw = keyword.trim();
        // 纯数字时补一个 keywordId 走等值匹配(会员编号),非数字则不参与该列比较——
        // 写成 user_id like 或 cast(user_id as char) 都会让主键索引用不上。同 searchL4Member。
        Long keywordId = null;
        try
        {
            keywordId = Long.valueOf(kw);
        }
        catch (NumberFormatException ignored)
        {
            // 非纯数字:只走手机号/昵称/姓名模糊匹配
        }
        return accountUserMapper.selectSupplierCandidates(userId, levels, kw, keywordId);
    }
 
    /**
     * 校验新供货人的等级必须严格高于该会员(C < L1 < L2 < L3 < L4)。
     *
     * 这是提交时的第二道闸:弹窗只列出合法候选,但 /account/user/relation 是公开入口,
     * 绕过页面直接打接口也得拦住。解除供货(传 null)不需要校验。
     *
     * 与旧值相同时直接放行:存量关系是「历来没有等级限制」时期写下的(例如 L4 的供货人是 C),
     * 不该因为这次没换人被卡死——那是另一件事,按本项目的规矩也不在这里顺手修历史数据。
     */
    private void validateSupplier(AccountUser old, Long newSupplierId)
    {
        if (newSupplierId == null || newSupplierId.equals(old.getSupplierId()))
        {
            return;
        }
        // selectAccountUserById 的 where 自带 del_flag = '0',查不到即「不存在或已删除」
        AccountUser supplier = accountUserMapper.selectAccountUserById(newSupplierId);
        if (StringUtils.isNull(supplier))
        {
            throw new ServiceException("新供货人不存在");
        }
        if (!AccountUser.isHigherLevel(supplier.getSystemLevel(), old.getSystemLevel()))
        {
            throw new ServiceException("新供货人的系统等级必须高于该会员(该会员:" + old.getSystemLevel()
                + ",所选:" + supplier.getSystemLevel() + ")");
        }
    }
 
    @Override
    public int changeStatus(AccountUser accountUser)
    {
        // 本接口只服务列表页的「禁用/启用」,故目标状态只允许「正常/禁用」两态。
        // 注销中、已注销必须由注销审核流程驱动(见 AccountCancelApplyServiceImpl):
        // 若这里放开,「已注销」的会员一次普通请求就能被改回「正常」,等于绕过审核。
        String target = accountUser.getStatus();
        if (!AccountUser.STATUS_NORMAL.equals(target) && !AccountUser.STATUS_DISABLED.equals(target))
        {
            throw new ServiceException("账户状态只能改为「正常」或「禁用」");
        }
        AccountUser old = accountUserMapper.selectAccountUserById(accountUser.getUserId());
        if (StringUtils.isNull(old))
        {
            throw new ServiceException("会员不存在");
        }
        // 反向也要拦:注销中/已注销的会员不允许从列表页改回正常或禁用,否则审核结论形同虚设
        if (AccountUser.STATUS_CANCELING.equals(old.getStatus()) || AccountUser.STATUS_CANCELED.equals(old.getStatus()))
        {
            throw new ServiceException("该会员正在注销或已注销,账户状态只能由注销审核流程变更");
        }
        return accountUserMapper.updateUserStatus(accountUser);
    }
 
    @Override
    @Transactional
    public int updateAccountAddress(AccountAddress accountAddress)
    {
        // 置为默认前先清掉同会员其它行的默认标记:MySQL 没有部分唯一索引,
        // "每会员至多一条 is_default='1'"只能靠这两条语句在同一事务内保证。
        if ("1".equals(accountAddress.getIsDefault()))
        {
            accountAddressMapper.clearDefaultByUserId(accountAddress.getUserId());
        }
        int rows = accountAddressMapper.updateAddress(accountAddress);
        // updateAddress 的 where 带 user_id,id 与 userId 对不上时会更新 0 行。
        // 此时上面的默认标记已被清掉,必须抛异常回滚,
        // 否则该会员会落到"一个默认地址都没有"的状态。
        if (rows == 0)
        {
            throw new ServiceException("收货地址不存在或不属于该会员");
        }
        return rows;
    }
 
    @Override
    public int deleteAccountAddressByIds(Long[] ids)
    {
        return accountAddressMapper.deleteAddressByIds(ids);
    }
 
 
    private Date resolveBindTime(Long oldId, Long newId, Date oldTime, Date now)
    {
        if (newId == null)
        {
            return null;
        }
        // 关系未变时沿用原绑定时间;oldTime 为空(历史数据没记)则补当前时间
        if (Objects.equals(oldId, newId) && oldTime != null)
        {
            return oldTime;
        }
        return now;
    }
}