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
275
package com.ruoyi.user.service.impl;
 
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.user.domain.AccountProvider;
import com.ruoyi.user.domain.AccountUser;
import com.ruoyi.user.domain.vo.AccountProviderForm;
import com.ruoyi.user.domain.vo.AccountProviderVo;
import com.ruoyi.user.mapper.AccountProviderMapper;
import com.ruoyi.user.mapper.AccountUserMapper;
import com.ruoyi.user.service.IAccountProviderService;
import com.ruoyi.user.service.IAccountUserService;
 
/**
 * 服务商 业务层实现
 *
 * 为什么校验全在这里、而不是像会员模块那样放在 Controller:
 * 会员模块的校验(用户名/手机号查重、密码非空)能放 Controller,是因为它只写 account_user 一张表;
 * 而服务商建档要在一个事务里写三处(account_user、account_provider、再回写 provider_flag),
 * 且明细的接口不暴露给外部,校验放服务层才能保证「无论谁调都不会绕过去」。
 * 更关键的是 AccountUserServiceImpl.checkPhoneUnique 对空手机号直接返回「唯一」,
 * 这里是服务商建档,手机号必填,那道口子必须自己堵。
 *
 * @author ruoyi
 */
@Service
public class AccountProviderServiceImpl implements IAccountProviderService
{
    /** 新建服务商账号的主体类型:企业 */
    private static final String SUBJECT_TYPE_ENTERPRISE = "3";
 
    /** 新建服务商账号的认证状态:未认证(服务商资质走本模块的公司资料,不走会员的实名认证流程) */
    private static final String AUTH_STATUS_NONE = "0";
 
    /**
     * 登录密码长度下限/上限。
     * 上下限与 AccountUser.getPassword 上那条 @Size 的**文案**(5~20)对齐——
     * 那条注解实际写的是 min = 0,是既有的文案与实现不一致;服务商是新建账号,
     * 按文案执行更合理,且上限 20 远小于 BCrypt 的 72 字节截断点。
     */
    private static final int PASSWORD_MIN_LENGTH = 5;
 
    private static final int PASSWORD_MAX_LENGTH = 20;
 
    @Autowired
    private AccountProviderMapper providerMapper;
 
    @Autowired
    private AccountUserMapper accountUserMapper;
 
    @Autowired
    private IAccountUserService accountUserService;
 
    @Override
    public List<AccountProviderVo> selectProviderList(AccountProviderVo provider)
    {
        return providerMapper.selectProviderList(provider);
    }
 
    @Override
    public AccountProviderVo selectProviderById(Long id)
    {
        return providerMapper.selectProviderById(id);
    }
 
    /**
     * 新增服务商。
     *
     * 一个事务里三件事,顺序不能换:先建账号拿到自增 userId,配置行才有 user_id 可写,
     * 标识列也才有对象可打。中间任何一步抛异常都要整体回滚——
     * 否则会留下「账号建了但没配置行」(登录得进去却没有服务商身份)的脏数据。
     */
    @Override
    @Transactional
    public int insertProvider(AccountProviderForm form)
    {
        validateProfile(form);
        validateAccount(form, true);
 
        AccountUser accountUser = new AccountUser();
        accountUser.setUserName(form.getUserName());
        // 明文到这里为止:insertAccountUser 内部立即 BCrypt 加密
        accountUser.setPassword(form.getPassword());
        accountUser.setPhone(form.getContactPhone());
        // 昵称种成联系人,让这行在会员列表里可读(会员列表按昵称/账号展示)
        accountUser.setNickname(form.getContactName());
        accountUser.setCity(form.getRegion());
        accountUser.setSubjectType(SUBJECT_TYPE_ENTERPRISE);
        accountUser.setAuthStatus(AUTH_STATUS_NONE);
        accountUser.setStatus(AccountUser.STATUS_NORMAL);
        accountUser.setCreateBy(SecurityUtils.getUsername());
        // 刻意不设 systemLevel:等级是权限,建档就送 L4 会让服务商灌满城市公司档案表单的
        // 「关联用户」下拉池(selectL4MemberOptions 的准入条件就是 system_level='L4'),
        // 且 L4 是等级体系最高档,等于建档即白送顶级权限。落库默认 'C'。
        accountUserService.insertAccountUser(accountUser);
 
        form.setUserId(accountUser.getUserId());
        if (StringUtils.isEmpty(form.getStatus()))
        {
            form.setStatus(AccountProvider.STATUS_NORMAL);
        }
        form.setCreateBy(SecurityUtils.getUsername());
        int rows = providerMapper.insertProvider(form);
 
        markAsProvider(accountUser.getUserId());
        return rows;
    }
 
    /**
     * 修改服务商。
     *
     * user_id(服务商与账号的绑定)不可改:请求体里带了也不认,一律以库里的为准。
     * 要换账号就是另一个服务商,不是「改一个字段」。
     */
    @Override
    @Transactional
    public int updateProvider(AccountProviderForm form)
    {
        if (StringUtils.isNull(form.getId()))
        {
            throw new ServiceException("服务商编号不能为空");
        }
        // 先确认存在:update 的 where 带 del_flag = '0',改到已删除的行会返回 0 行,
        // 与「没改动任何字段」无法区分,故先显式查一次
        AccountProviderVo current = providerMapper.selectProviderById(form.getId());
        if (StringUtils.isNull(current))
        {
            throw new ServiceException("服务商不存在");
        }
        form.setUserId(current.getUserId());
 
        validateProfile(form);
        validateAccount(form, false);
 
        // 账号行可能已被逻辑删除(DELETE /account/user/{ids} 只置删除标志,服务商行会留下悬空 user_id)。
        // 此时改登录账号/手机号无从落库(updateAccountUser 的 where 带 del_flag='0',会静默改 0 行),
        // 直接说明白比留个假成功好
        AccountUser account = accountUserMapper.selectAccountUserById(current.getUserId());
        if (StringUtils.isNull(account))
        {
            throw new ServiceException("服务商账号不存在或已删除,无法修改");
        }
        String operator = SecurityUtils.getUsername();
        form.setUpdateBy(operator);
 
        AccountUser accountUser = new AccountUser();
        accountUser.setUserId(current.getUserId());
        accountUser.setUserName(form.getUserName());
        accountUser.setPhone(form.getContactPhone());
        // 留空即不改密码:updateAccountUser 只对非空密码加密,mapper 的 <if> 会跳过空值
        accountUser.setPassword(form.getPassword());
        // 注册地区与会员账号的所属城市同口径(建档时两个字段写的是同一个值)。
        // 改了地区就跟着改城市,否则同一个服务商在「服务商列表」和「会员列表」里会显示成两个城市。
        // region 为空时不动 city:那表示运营把地区清空了,不代表要把会员的城市也抹掉。
        if (StringUtils.isNotEmpty(form.getRegion()))
        {
            accountUser.setCity(form.getRegion());
        }
        accountUser.setUpdateBy(operator);
        accountUserService.updateAccountUser(accountUser);
 
        // 昵称不同步:昵称只在建档时由联系人种下,界面上「管理人」取的是 contact_name,不依赖昵称
        return providerMapper.updateProvider(form);
    }
 
    @Override
    public int changeStatus(AccountProvider provider)
    {
        String target = provider.getStatus();
        if (!AccountProvider.STATUS_NORMAL.equals(target) && !AccountProvider.STATUS_DISABLED.equals(target))
        {
            throw new ServiceException("服务商状态只能改为「正常」或「禁用」");
        }
        if (StringUtils.isNull(providerMapper.selectProviderById(provider.getId())))
        {
            throw new ServiceException("服务商不存在");
        }
        // 只改 account_provider.status:account_user.status 是会员账户状态,
        // 在这里连带禁用会让这个服务商在会员列表里也变成「禁用」,
        // 甚至可能把他从 C 端登录里踢出去——两件事必须分开
        provider.setUpdateBy(SecurityUtils.getUsername());
        return providerMapper.updateProvider(provider);
    }
 
    /**
     * 打服务商标识。
     *
     * 走 AccountUserMapper.updateUserProviderFlag,而不是 AccountUser 的通用 update:
     * 那条通用语句按 &lt;if&gt; 拼列,provider_flag 一旦挂上去,
     * POST/PUT /account/user 的请求体里带个 providerFlag=1 就能把任意会员标成服务商。
     * (认证审核模块回写 auth_status 用的也是这套专用语句。)
     */
    private void markAsProvider(Long userId)
    {
        AccountUser flag = new AccountUser();
        flag.setUserId(userId);
        flag.setProviderFlag(AccountProvider.PROVIDER_FLAG_YES);
        flag.setUpdateBy(SecurityUtils.getUsername());
        accountUserMapper.updateUserProviderFlag(flag);
    }
 
    /**
     * 公司资料校验。三项必填:公司名称、联系人、手机号码
     * (信用代码/企业法人/注册地区/注册日期/企业地址/营业执照选填)。
     */
    private void validateProfile(AccountProvider provider)
    {
        if (StringUtils.isEmpty(provider.getCompanyName()))
        {
            throw new ServiceException("公司名称不能为空");
        }
        if (StringUtils.isEmpty(provider.getContactName()))
        {
            throw new ServiceException("联系人不能为空");
        }
        if (StringUtils.isEmpty(provider.getContactPhone()))
        {
            throw new ServiceException("手机号码不能为空");
        }
    }
 
    /**
     * 账号校验:登录账号必填且唯一、手机号必填且唯一(手机号同时是账号的联系方式,
     * 冲突会让会员列表出现两个同样号码的人,故与会员侧口径一致按唯一处理)、
     * 密码新增必填、重复密码必须一致。
     *
     * @param isInsert 新增时密码必填;修改时留空即不改
     */
    private void validateAccount(AccountProviderForm form, boolean isInsert)
    {
        String userName = form.getUserName();
        if (StringUtils.isEmpty(userName))
        {
            throw new ServiceException("登录账号不能为空");
        }
 
        String password = form.getPassword();
        if (isInsert && StringUtils.isEmpty(password))
        {
            throw new ServiceException("登录密码不能为空");
        }
        if (StringUtils.isNotEmpty(password))
        {
            if (password.length() < PASSWORD_MIN_LENGTH || password.length() > PASSWORD_MAX_LENGTH)
            {
                throw new ServiceException("登录密码长度必须在" + PASSWORD_MIN_LENGTH + "到"
                    + PASSWORD_MAX_LENGTH + "个字符之间");
            }
            if (!password.equals(form.getConfirmPassword()))
            {
                throw new ServiceException("两次输入的登录密码不一致");
            }
        }
 
        // 查重时带上 userId 即为排除自身(修改场景);新增时 userId 为空,任何同名的都算冲突
        AccountUser ref = new AccountUser();
        ref.setUserId(form.getUserId());
        ref.setUserName(userName);
        if (!accountUserService.checkUsernameUnique(ref))
        {
            throw new ServiceException("登录账号「" + userName + "」已存在");
        }
        ref.setPhone(form.getContactPhone());
        // 手机号非空已在校验上方保证,故这里的唯一性检查不会被 checkPhoneUnique 的空值放行绕过
        if (!accountUserService.checkPhoneUnique(ref))
        {
            throw new ServiceException("手机号码「" + form.getContactPhone() + "」已被占用");
        }
    }
}