批量推送

This commit is contained in:
郭庆泰
2026-08-16 13:42:26 +08:00
parent c612d4297a
commit 0c093076a4
66 changed files with 4847 additions and 1627 deletions
@@ -18,13 +18,15 @@ import com.ruoyi.system.service.ISysUserService;
/**
* 业务登录 - 角色选择
* registerExecutor 完整实现 (2026-08-15 重构, 原 registerSupplier):
* registerExecutor / registerSponsor 完整实现 (2026-08-15 重构, 原 registerSupplier):
* 1. 校验短信验证码
* 2. 检查手机号/用户名是否已注册
* 3. 加密密码
* 4. 插入 sys_user (用户名=前端传入, role_type=executor, 主账号)
* 5. 插入 biz_org (executor 类型, business_nature/contact_phone 从表单)
* 注: 主账号不建 biz_person, 跟 sponsor 主账号保持一致
* 4. 插入 sys_user (用户名=前端传入, sys_user.role_type 写入 executor/sponsor, 主账号)
* 5. 插入 biz_org (对应类型, business_nature/contact_phone 从表单)
* 注: 主账号不建 biz_person
*
* 2026-08-16 重构: 删 biz_user_role_bind, 统一用 sys_user.role_type (见迁移脚本)
*/
@RestController
@RequestMapping("/business/auth")
@@ -101,7 +103,7 @@ public class BizAuthController extends BaseController {
user.setStatus("0");
userService.insertUser(user);
Long userId = user.getUserId();
// sys_user.role_type 字段需 = executor
// 业务角色统一写到 sys_user.role_type (2026-08-16 重构, 删 biz_user_role_bind)
userService.updateRoleType(userId, "executor");
// 5. 插入 biz_org (executor 类型, 主账号自己当 contact)
@@ -116,15 +118,78 @@ public class BizAuthController extends BaseController {
bizOrgService.insert(org);
Long orgId = org.getOrgId();
// 注: 主账号不建 biz_person (与 sponsor 主账号保持一致, 见 sponsor/Account.vue)
// 后续主账号在 [账号信息] 看到 [所属公司] 时, 后端可通过 sys_user.role_type 关联 biz_org 取 org_name
// 子账号 (biz_person.unit_type='sponsor' + user_id 关联 sys_user.parent_user_id) 由 [人员管理] 创建
return success("注册成功, 请等待审核").put("userId", userId).put("orgId", orgId);
}
/**
* 注册赞助方 (主账号), 流程与 executor 一致:
* 校验 → 查重 → 写 sys_user + role_type=sponsor → 写 biz_org (sponsor 类型)
*/
@PostMapping("/registerSponsor")
public AjaxResult registerSponsor(@RequestBody Map<String, Object> body) {
return success();
String username = (String) body.get("username");
String unitName = (String) body.get("unitName");
String businessNature = (String) body.get("businessNature");
String phone = (String) body.get("phone");
String code = (String) body.get("smsCode");
String password = (String) body.get("password");
String confirmPassword = (String) body.get("confirmPassword");
String uuid = (String) body.get("uuid");
// 1. 基础校验 (与 executor 同)
if (username == null || username.length() < 4 || username.length() > 20) return error("用户名长度 4-20 位");
if (!username.matches("^[A-Za-z0-9_]+$")) return error("用户名只能包含字母/数字/下划线");
if (unitName == null || unitName.isEmpty()) return error("企业名称不能为空");
if (businessNature == null || businessNature.isEmpty()) return error("企业性质不能为空");
if (phone == null || !phone.matches("^1\\d{10}$")) return error("手机号格式错误");
if (code == null || code.isEmpty()) return error("请输入短信验证码");
if (password == null || password.length() < 6 || password.length() > 20) return error("密码长度 6-20 位");
if (!password.equals(confirmPassword)) return error("两次密码输入不一致");
// 2. 校验短信验证码
SmsValidForm smsForm = new SmsValidForm();
smsForm.setPhone(phone);
smsForm.setSmsCode(code);
smsForm.setUuid(uuid);
try {
smsService.verifyCode(smsForm);
} catch (RuntimeException e) {
return error("验证码错误或已过期: " + e.getMessage());
}
// 3. 查重
if (userService.isPhoneRegistered(phone)) {
return error("该手机号已注册, 请直接登录");
}
SysUser nameCheck = new SysUser();
nameCheck.setUserName(username);
if (!userService.checkUserNameUnique(nameCheck)) {
return error("用户名已被占用: " + username);
}
// 4. 写 sys_user (role_type=sponsor)
SysUser user = new SysUser();
user.setUserName(username);
user.setNickName(unitName);
user.setPhonenumber(phone);
user.setPassword(passwordEncoder.encode(password));
user.setStatus("0");
userService.insertUser(user);
Long userId = user.getUserId();
userService.updateRoleType(userId, "sponsor");
// 5. 写 biz_org (sponsor 类型)
BizOrg org = new BizOrg();
org.setOrgId(null);
org.setOrgName(unitName);
org.setOrgType("sponsor");
org.setBusinessNature(businessNature);
org.setContactPhone(phone);
org.setContactName(unitName);
org.setStatus("0");
bizOrgService.insert(org);
Long orgId = org.getOrgId();
return success("注册成功, 请等待审核").put("userId", userId).put("orgId", orgId);
}
}
@@ -1,15 +1,22 @@
package com.ruoyi.business.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.business.domain.BizExpert;
import com.ruoyi.business.domain.vo.BizExpertExportVo;
import com.ruoyi.business.domain.vo.BizExpertImportVo;
import com.ruoyi.business.service.IBizExpertService;
/**
@@ -41,11 +48,16 @@ public class BizExpertController extends BaseController
{
return success(bizExpertService.getByUserId(SecurityUtils.getUserId()));
}
/**
* admin 创建专家: 同时创建 sys_user (用户名=手机号, 密码=手机号)
* 返回 SysUser (含明文 password 给前端 toast 用)
*/
@Log(title = "专家", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody BizExpert bizExpert)
{
return toAjax(bizExpertService.insert(bizExpert));
SysUser created = bizExpertService.insert(bizExpert);
return success(created);
}
@Log(title = "专家", businessType = BusinessType.UPDATE)
@PutMapping
@@ -53,6 +65,16 @@ public class BizExpertController extends BaseController
{
return toAjax(bizExpertService.updateByPrimaryKey(bizExpert));
}
/**
* 启用/禁用专家: 同步 biz_expert.status + sys_user.status
* status='Y' 正常, status='N' 禁用
*/
@Log(title = "专家启停", businessType = BusinessType.UPDATE)
@PutMapping("/{expertId}/status")
public AjaxResult updateStatus(@PathVariable String expertId, @RequestParam String status)
{
return toAjax(bizExpertService.updateStatus(expertId, status));
}
/**
* 个人专家信息更新 (走 userId 路由, 后端强制注入当前登录用户)
* 不依赖前端传 expertId; 找不到 expert 行时自动 insert
@@ -71,4 +93,67 @@ public class BizExpertController extends BaseController
{
return toAjax(bizExpertService.deleteByPrimaryKeys(ids));
}
/**
* 导出专家列表 (中文列头)
*/
@Log(title = "专家", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('business:expert:export')")
@PostMapping("/export")
public void export(HttpServletResponse response, BizExpert bizExpert)
{
List<BizExpert> list = bizExpertService.selectList(bizExpert);
// 转换为导出 VO (中文列头 + 审核状态/状态 可读映射)
List<BizExpertExportVo> exportList = new java.util.ArrayList<>(list.size());
for (BizExpert e : list) {
BizExpertExportVo v = new BizExpertExportVo();
v.setName(e.getName());
v.setPhone(e.getPhone());
v.setWorkUnit(e.getWorkUnit());
v.setDepartment(e.getDepartment());
v.setTitle(e.getTitle());
v.setRegion(e.getRegion());
v.setIdCard(e.getIdCard());
v.setBankCard(e.getBankCard());
v.setBankName(e.getBankName());
v.setBankProvince(e.getBankProvince());
v.setBankCity(e.getBankCity());
v.setBankAddress(e.getBankAddress());
v.setStatus(e.getStatus());
v.setAuditStatus(e.getAuditStatus());
v.setAuditBy(e.getAuditBy());
v.setAuditTime(e.getAuditTime());
v.setCreateTime(e.getCreateTime());
exportList.add(v);
}
ExcelUtil<BizExpertExportVo> util = new ExcelUtil<BizExpertExportVo>(BizExpertExportVo.class);
util.exportExcel(response, exportList, "专家数据");
}
/**
* 下载专家导入模板
*/
@GetMapping("/importTemplate")
public void importTemplate(HttpServletResponse response)
{
ExcelUtil<BizExpertImportVo> util = new ExcelUtil<BizExpertImportVo>(BizExpertImportVo.class);
util.importTemplateExcel(response, "专家数据");
}
/**
* 批量导入专家 (Excel), 参考 RuoYi 系统用户导入模式
* updateSupport=true: 同一手机号已存在 → 跳过 (按成功计)
* 返回 message 含成功/失败计数 + 失败明细
*/
@Log(title = "专家", businessType = BusinessType.IMPORT)
@PreAuthorize("@ss.hasPermi('business:expert:import')")
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelUtil<BizExpertImportVo> util = new ExcelUtil<BizExpertImportVo>(BizExpertImportVo.class);
List<BizExpertImportVo> importList = util.importExcel(file.getInputStream());
String operName = SecurityUtils.getUsername();
String message = bizExpertService.importExpert(importList, updateSupport, operName);
return success(message);
}
}
@@ -13,7 +13,7 @@ import com.ruoyi.business.service.IBizOrgService;
/**
* 公司Controller (赞助方 + 执行方 共用)
* GET /business/org/list?orgType=sponsor|execution
* GET /business/org/list?orgType=sponsor|executor
*/
@RestController
@RequestMapping("/business/org")
@@ -100,8 +100,8 @@ public class BizRegisterController extends BaseController {
expert.setTitle(doctorTitle);
expert.setPracticeCertUrl(licenseCertUrl);
expert.setTitleCertUrl(titleCertUrl);
expert.setAuditStatus("0");
expert.setStatus("0");
expert.setAuditStatus("1"); // BizAuditStatusEnum.PENDING = 待审核
expert.setStatus("Y"); // biz_expert.status: Y=正常 N=禁用
expertService.insert(expert);
return success("注册成功, 请等待审核").put("userId", userId);
@@ -7,7 +7,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
/**
* 公司对象 biz_org
* 用途: 赞助方(sponsor) + 执行方(execution) 共用
* 用途: 赞助方(sponsor) + 执行方(executor) 共用
* 重构说明: 原 biz_support_unit + biz_execution_unit 合并, 通过 org_type 区分
*/
public class BizOrg extends BaseEntity {
@@ -17,7 +17,7 @@ public class BizOrg extends BaseEntity {
/** org_name */
@Excel(name = "org_name")
private String orgName;
/** org_type: sponsor赞助方 / execution执行方 */
/** org_type: sponsor赞助方 / executor执行方 */
@Excel(name = "org_type")
private String orgType;
/** 企业性质 私营/国营/中外合资/外资/其他 */
@@ -52,7 +52,7 @@ public class BizProject extends BaseEntity {
/** org_name (赞助方/执行方, 由 org_type 区分) */
@Excel(name = "org_name")
private String orgName;
/** org_type: sponsor 赞助方 / execution 执行方 */
/** org_type: sponsor 赞助方 / executor 执行方 */
@Excel(name = "org_type")
private String orgType;
/** project_form */
@@ -0,0 +1,103 @@
package com.ruoyi.business.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
/**
* 专家导出 VO (中文列头, 用户友好)
*
* <p>仅用于 Excel 导出, 不参与业务逻辑.
*
* @author guoju
*/
public class BizExpertExportVo {
@Excel(name = "姓名", sort = 1)
private String name;
@Excel(name = "手机号", sort = 2)
private String phone;
@Excel(name = "工作单位", sort = 3)
private String workUnit;
@Excel(name = "科室", sort = 4)
private String department;
@Excel(name = "职称", sort = 5)
private String title;
@Excel(name = "地区", sort = 6)
private String region;
@Excel(name = "身份证件号码", sort = 7)
private String idCard;
@Excel(name = "银行卡号", sort = 8)
private String bankCard;
@Excel(name = "银行名称", sort = 9)
private String bankName;
@Excel(name = "开户行省份", sort = 10)
private String bankProvince;
@Excel(name = "开户行城市", sort = 11)
private String bankCity;
@Excel(name = "开户行地址", sort = 12)
private String bankAddress;
@Excel(name = "状态", sort = 13, readConverterExp = "Y=正常,N=禁用")
private String status;
@Excel(name = "审核状态", sort = 14, readConverterExp = "0=未提交,1=待审核,2=通过,3=拒绝")
private String auditStatus;
@Excel(name = "审核人", sort = 15)
private String auditBy;
@Excel(name = "审核时间", sort = 16, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date auditTime;
@Excel(name = "创建时间", sort = 17, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getWorkUnit() { return workUnit; }
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public String getIdCard() { return idCard; }
public void setIdCard(String idCard) { this.idCard = idCard; }
public String getBankCard() { return bankCard; }
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
public String getBankName() { return bankName; }
public void setBankName(String bankName) { this.bankName = bankName; }
public String getBankProvince() { return bankProvince; }
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
public String getBankCity() { return bankCity; }
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
public String getBankAddress() { return bankAddress; }
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getAuditStatus() { return auditStatus; }
public void setAuditStatus(String auditStatus) { this.auditStatus = auditStatus; }
public String getAuditBy() { return auditBy; }
public void setAuditBy(String auditBy) { this.auditBy = auditBy; }
public Date getAuditTime() { return auditTime; }
public void setAuditTime(Date auditTime) { this.auditTime = auditTime; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
}
@@ -0,0 +1,117 @@
package com.ruoyi.business.domain.vo;
import com.ruoyi.common.annotation.Excel;
/**
* 专家导入 VO
*
* <p>仅用于 Excel 批量导入 / 模板下载 / 导出, 不参与业务逻辑.
* 字段顺序与 Excel 列一致, 调整时同步修改模板下载体验.
*
* @author guoju
*/
public class BizExpertImportVo {
/** 姓名 */
@Excel(name = "姓名", sort = 1)
private String name;
/** 手机号 */
@Excel(name = "手机号", sort = 2)
private String phone;
/** 工作单位 */
@Excel(name = "工作单位", sort = 3)
private String workUnit;
/** 科室 */
@Excel(name = "科室", sort = 4)
private String department;
/** 职称 */
@Excel(name = "职称", sort = 5)
private String title;
/** 地区 */
@Excel(name = "地区", sort = 6)
private String region;
/** 身份证件号码 */
@Excel(name = "身份证件号码", sort = 7)
private String idCard;
/** 身份证件照片 正面URL */
@Excel(name = "身份证正面URL", sort = 8)
private String idCardFrontUrl;
/** 身份证件照片 反面URL */
@Excel(name = "身份证反面URL", sort = 9)
private String idCardBackUrl;
/** 执业医师证书 URL */
@Excel(name = "执业医师证书URL", sort = 10)
private String practiceCertUrl;
/** 职称证明 URL */
@Excel(name = "职称证明URL", sort = 11)
private String titleCertUrl;
/** 银行卡号 */
@Excel(name = "银行卡号", sort = 12)
private String bankCard;
/** 银行名称 */
@Excel(name = "银行名称", sort = 13)
private String bankName;
/** 开户行省份 */
@Excel(name = "开户行省份", sort = 14)
private String bankProvince;
/** 开户行城市 */
@Excel(name = "开户行城市", sort = 15)
private String bankCity;
/** 开户行地址 */
@Excel(name = "开户行地址", sort = 16)
private String bankAddress;
/** 状态 (Y=正常 N=禁用, 留空默认 Y) */
@Excel(name = "状态", sort = 17, readConverterExp = "Y=正常,N=禁用")
private String status;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getWorkUnit() { return workUnit; }
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public String getIdCard() { return idCard; }
public void setIdCard(String idCard) { this.idCard = idCard; }
public String getIdCardFrontUrl() { return idCardFrontUrl; }
public void setIdCardFrontUrl(String idCardFrontUrl) { this.idCardFrontUrl = idCardFrontUrl; }
public String getIdCardBackUrl() { return idCardBackUrl; }
public void setIdCardBackUrl(String idCardBackUrl) { this.idCardBackUrl = idCardBackUrl; }
public String getPracticeCertUrl() { return practiceCertUrl; }
public void setPracticeCertUrl(String practiceCertUrl) { this.practiceCertUrl = practiceCertUrl; }
public String getTitleCertUrl() { return titleCertUrl; }
public void setTitleCertUrl(String titleCertUrl) { this.titleCertUrl = titleCertUrl; }
public String getBankCard() { return bankCard; }
public void setBankCard(String bankCard) { this.bankCard = bankCard; }
public String getBankName() { return bankName; }
public void setBankName(String bankName) { this.bankName = bankName; }
public String getBankProvince() { return bankProvince; }
public void setBankProvince(String bankProvince) { this.bankProvince = bankProvince; }
public String getBankCity() { return bankCity; }
public void setBankCity(String bankCity) { this.bankCity = bankCity; }
public String getBankAddress() { return bankAddress; }
public void setBankAddress(String bankAddress) { this.bankAddress = bankAddress; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
@@ -2,6 +2,8 @@ package com.ruoyi.business.service;
import java.util.List;
import com.ruoyi.business.domain.BizExpert;
import com.ruoyi.business.domain.vo.BizExpertImportVo;
import com.ruoyi.common.core.domain.entity.SysUser;
/**
* 专家Service接口
@@ -11,10 +13,26 @@ public interface IBizExpertService
BizExpert getById(String expertId);
BizExpert getByUserId(Long userId);
List<BizExpert> selectList(BizExpert entity);
int insert(BizExpert entity);
/**
* 新建专家: 同时创建 sys_user (用户名=手机号, 密码=手机号, role_type=doctor)
* 若 sys_user.username(=phone) 已存在, 抛 ServiceException("该手机号已注册")
* 返回 SysUser 含 username + 明文 password (仅本次返回,前端 toast 显示后即丢)
*/
SysUser insert(BizExpert entity);
int updateByPrimaryKey(BizExpert entity);
/**
* 启用/禁用专家: 同步更新 biz_expert.status + sys_user.status
* status='Y' 正常, status='N' 禁用
*/
int updateStatus(String expertId, String status);
/** 按 userId 更新或新建 (upsert) */
int updateProfileByUserId(BizExpert entity);
int deleteByPrimaryKey(String expertId);
int deleteByPrimaryKeys(String[] expertId);
/**
* 批量导入专家: 每行调用 insert, updateSupport=true 时跳过已存在手机号(视为成功)
* 返回 String 含成功/失败计数 + 失败明细 (前端 toast 显示)
*/
String importExpert(List<BizExpertImportVo> importList, boolean updateSupport, String operName);
}
@@ -4,8 +4,13 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.business.domain.BizExpert;
import com.ruoyi.business.domain.vo.BizExpertImportVo;
import com.ruoyi.business.mapper.BizExpertMapper;
import com.ruoyi.business.service.IBizExpertService;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.service.ISysUserService;
@Service
public class BizExpertServiceImpl implements IBizExpertService
@@ -13,6 +18,9 @@ public class BizExpertServiceImpl implements IBizExpertService
@Autowired
private BizExpertMapper bizExpertMapper;
@Autowired
private ISysUserService sysUserService;
@Override
public BizExpert getById(String expertId)
{ return bizExpertMapper.selectByPrimaryKey(expertId); }
@@ -22,11 +30,86 @@ public class BizExpertServiceImpl implements IBizExpertService
@Override
public List<BizExpert> selectList(BizExpert entity)
{ return bizExpertMapper.selectList(entity); }
/**
* admin 创建专家: 同时创建 sys_user (用户名=手机号, 密码=手机号, role_type=doctor)
* 1. 校验 phone 没注册过 (抛 ServiceException)
* 2. 创建 sys_user + bcrypt 加密密码
* 3. 设置 role_type=doctor (与公开注册一致)
* 4. 创建 biz_expert 绑定 user_id
* 5. 返回 SysUser 含明文 password (前端 toast 用完即丢)
*/
@Override
public int insert(BizExpert entity) { com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "expertId"); return bizExpertMapper.insert(entity); }
public SysUser insert(BizExpert entity) {
String phone = entity.getPhone();
if (phone == null || phone.isEmpty()) {
throw new ServiceException("手机号不能为空");
}
// 0. 校验 phone 唯一 (查 sys_user, 若 username=phone 已存在即重复)
if (sysUserService.isPhoneRegistered(phone)) {
throw new ServiceException("该手机号已注册,请直接登录");
}
// 1. 创建 sys_user (用户名=phone, 密码=phone)
SysUser newUser = new SysUser();
newUser.setUserName(phone);
newUser.setNickName(entity.getName());
newUser.setPhonenumber(phone);
newUser.setPassword(SecurityUtils.encryptPassword(phone));
newUser.setStatus("0");
newUser.setDelFlag("0");
newUser.setCreateBy(SecurityUtils.getUsername());
sysUserService.insertUser(newUser);
Long userId = newUser.getUserId();
// 2. role_type = doctor (跟公开注册一致,DB 默认 executor, 专家需 doctor)
sysUserService.updateRoleType(userId, "doctor");
// 3. 创建 biz_expert 绑定 user_id
entity.setUserId(userId);
com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "expertId");
bizExpertMapper.insert(entity);
// 4. 把明文密码回填 SysUser (仅本次返回,前端 toast 显示)
newUser.setPassword(phone);
return newUser;
}
@Override
public int updateByPrimaryKey(BizExpert entity)
{ return bizExpertMapper.updateByPrimaryKey(entity); }
/**
* 启用/禁用专家: 同步 biz_expert.status + sys_user.status
* biz_expert.status: 'Y'=正常 'N'=禁用
* sys_user.status: '0'=正常 '1'=停用 (RuoYi 框架约定, 同步时转换)
*/
@Override
public int updateStatus(String expertId, String status) {
if (status == null || (!"Y".equals(status) && !"N".equals(status))) {
throw new ServiceException("status 必须是 'Y'(正常) 或 'N'(禁用)");
}
BizExpert existed = bizExpertMapper.selectByPrimaryKey(expertId);
if (existed == null) {
throw new ServiceException("专家不存在");
}
// 1. 更新 biz_expert.status
BizExpert update = new BizExpert();
update.setExpertId(expertId);
update.setStatus(status);
update.setUpdateBy(SecurityUtils.getUsername());
int n = bizExpertMapper.updateByPrimaryKey(update);
// 2. 同步 sys_user.status (登录账号启停, 'Y'→'0', 'N'→'1')
if (n > 0 && existed.getUserId() != null) {
SysUser u = new SysUser();
u.setUserId(existed.getUserId());
u.setStatus("Y".equals(status) ? "0" : "1");
u.setUpdateBy(SecurityUtils.getUsername());
sysUserService.updateUser(u);
}
return n;
}
@Override
public int updateProfileByUserId(BizExpert entity)
{
@@ -42,4 +125,107 @@ public class BizExpertServiceImpl implements IBizExpertService
@Override
public int deleteByPrimaryKeys(String[] expertId)
{ return bizExpertMapper.deleteByPrimaryKeys(expertId); }
/**
* 批量导入专家:
* 1. 每行 必填校验 (姓名/手机号/工作单位/科室/职称, 手机号正则 1[0-9]{10})
* 2. status 留空 → Y (正常); 非法值 → 抛错
* 3. sys_user 同步创建 (与单条 insert 共用 sys_userService.insertUser)
* 4. updateSupport=true 时: 同一手机号已存在 → 跳过 (按成功计)
* 5. 单行失败不中断, 错误信息累计
*/
@Override
public String importExpert(List<BizExpertImportVo> importList, boolean updateSupport, String operName) {
if (importList == null || importList.isEmpty()) {
throw new ServiceException("导入数据不能为空");
}
int successNum = 0;
int failureNum = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
for (int i = 0; i < importList.size(); i++) {
BizExpertImportVo vo = importList.get(i);
int rowNo = i + 2; // Excel 行号 (1=表头)
try {
// 必填校验
if (vo.getName() == null || vo.getName().trim().isEmpty()) {
throw new ServiceException("姓名不能为空");
}
if (vo.getPhone() == null || vo.getPhone().trim().isEmpty()) {
throw new ServiceException("手机号不能为空");
}
if (!vo.getPhone().matches("^1[0-9]\\d{9}$")) {
throw new ServiceException("手机号格式不正确");
}
if (vo.getWorkUnit() == null || vo.getWorkUnit().trim().isEmpty()) {
throw new ServiceException("工作单位不能为空");
}
if (vo.getDepartment() == null || vo.getDepartment().trim().isEmpty()) {
throw new ServiceException("科室不能为空");
}
if (vo.getTitle() == null || vo.getTitle().trim().isEmpty()) {
throw new ServiceException("职称不能为空");
}
// status 校验, 留空 → Y
String status = vo.getStatus();
if (status == null || status.trim().isEmpty()) {
status = "Y";
} else if (!"Y".equals(status) && !"N".equals(status)) {
throw new ServiceException("状态只能是 Y(正常) 或 N(禁用)");
}
// 同一手机号已存在 → updateSupport=true 跳过, 否则报错
if (sysUserService.isPhoneRegistered(vo.getPhone())) {
if (updateSupport) {
successNum++;
successMsg.append("<br/>" + successNum + "、手机号 " + vo.getPhone() + " 已存在, 已跳过");
continue;
}
throw new ServiceException("手机号 " + vo.getPhone() + " 已存在");
}
// 复用单条 insert 逻辑 (含 sys_user 创建)
BizExpert entity = new BizExpert();
entity.setName(vo.getName());
entity.setPhone(vo.getPhone());
entity.setWorkUnit(vo.getWorkUnit());
entity.setDepartment(vo.getDepartment());
entity.setTitle(vo.getTitle());
entity.setRegion(vo.getRegion());
entity.setIdCard(vo.getIdCard());
entity.setIdCardFrontUrl(vo.getIdCardFrontUrl());
entity.setIdCardBackUrl(vo.getIdCardBackUrl());
entity.setPracticeCertUrl(vo.getPracticeCertUrl());
entity.setTitleCertUrl(vo.getTitleCertUrl());
entity.setBankCard(vo.getBankCard());
entity.setBankName(vo.getBankName());
entity.setBankProvince(vo.getBankProvince());
entity.setBankCity(vo.getBankCity());
entity.setBankAddress(vo.getBankAddress());
entity.setStatus(status);
// admin 批量导入默认通过审核 (与单条 admin 创建一致)
entity.setAuditStatus("2");
entity.setAuditBy(operName);
entity.setAuditTime(new java.util.Date());
entity.setCreateBy(operName);
insert(entity);
successNum++;
successMsg.append("<br/>" + successNum + "、账号 " + vo.getPhone() + " 导入成功");
} catch (Exception e) {
failureNum++;
String msg = e.getMessage();
if (msg == null) msg = e.getClass().getSimpleName();
failureMsg.append("<br/>" + failureNum + "、第 " + rowNo + " 行: " + msg);
}
}
if (failureNum > 0) {
failureMsg.insert(0, "很抱歉, 部分导入失败, 共 " + failureNum + " 条:");
}
if (successNum > 0) {
successMsg.insert(0, "恭喜您, 数据已全部导入成功!共 " + successNum + " 条, 数据如下:");
}
return successMsg.toString() + failureMsg.toString();
}
}
@@ -29,7 +29,7 @@
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectFields">
select expert_id, user_id, name, phone, region, id_card, work_unit, department, title, practice_cert_url, title_cert_url, bank_card, bank_name, bank_province, bank_city, bank_address, id_card_front_url, id_card_back_url, audit_status, audit_by, audit_time, create_by, create_time, update_by, update_time
select expert_id, user_id, name, phone, region, id_card, work_unit, department, title, practice_cert_url, title_cert_url, bank_card, bank_name, bank_province, bank_city, bank_address, id_card_front_url, id_card_back_url, audit_status, audit_by, audit_time, status, create_by, create_time, update_by, update_time
from biz_expert
</sql>
<select id="selectByUserId" resultMap="BizExpertResult" parameterType="Long">
@@ -197,7 +197,7 @@
<if test="auditStatus != null and auditStatus != ''">audit_status = #{auditStatus},</if>
<if test="auditOpinion != null and auditOpinion != ''">audit_opinion = #{auditOpinion},</if>
<if test="auditBy != null and auditBy != ''">audit_by = #{auditBy},</if>
<if test="auditTime != null and auditTime != ''">audit_time = #{auditTime},</if>
<if test="auditTime != null">audit_time = #{auditTime},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="name != null">name = #{name},</if>
<if test="phone != null">phone = #{phone},</if>