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:
|
* 那条通用语句按 <if> 拼列,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() + "」已被占用");
|
}
|
}
|
}
|