diff --git a/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java index c4bf05e..14013c9 100644 --- a/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java +++ b/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -27,6 +27,7 @@ import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.vo.SysUserExtendVo; import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysPostService; import com.ruoyi.system.service.ISysRoleService; @@ -58,10 +59,10 @@ public class SysUserController extends BaseController */ @PreAuthorize("@ss.hasPermi('system:user:list')") @GetMapping("/list") - public TableDataInfo list(SysUser user) + public TableDataInfo list(SysUserExtendVo vo) { startPage(); - List list = userService.selectUserList(user); + List list = userService.selectUserExtendList(vo); return getDataTable(list); } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java index aa1078a..ff078e3 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java @@ -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 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); } } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExpertController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExpertController.java index e926f80..5e4d0d9 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExpertController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExpertController.java @@ -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 list = bizExpertService.selectList(bizExpert); + // 转换为导出 VO (中文列头 + 审核状态/状态 可读映射) + List 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 util = new ExcelUtil(BizExpertExportVo.class); + util.exportExcel(response, exportList, "专家数据"); + } + + /** + * 下载专家导入模板 + */ + @GetMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(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 util = new ExcelUtil(BizExpertImportVo.class); + List importList = util.importExcel(file.getInputStream()); + String operName = SecurityUtils.getUsername(); + String message = bizExpertService.importExpert(importList, updateSupport, operName); + return success(message); + } } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizOrgController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizOrgController.java index 24434b4..048b029 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizOrgController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizOrgController.java @@ -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") diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizRegisterController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizRegisterController.java index e7a8e72..da86630 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizRegisterController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizRegisterController.java @@ -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); diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizOrg.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizOrg.java index 89276ee..c63e6ad 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizOrg.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizOrg.java @@ -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; /** 企业性质 私营/国营/中外合资/外资/其他 */ diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java index 49b56d7..e731c5f 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java @@ -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 */ diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizExpertExportVo.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizExpertExportVo.java new file mode 100644 index 0000000..7fd0104 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizExpertExportVo.java @@ -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 (中文列头, 用户友好) + * + *

仅用于 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; } +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizExpertImportVo.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizExpertImportVo.java new file mode 100644 index 0000000..6ac7808 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/vo/BizExpertImportVo.java @@ -0,0 +1,117 @@ +package com.ruoyi.business.domain.vo; + +import com.ruoyi.common.annotation.Excel; + +/** + * 专家导入 VO + * + *

仅用于 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; } +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizExpertService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizExpertService.java index 814ec30..fe45f13 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizExpertService.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizExpertService.java @@ -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 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 importList, boolean updateSupport, String operName); } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizExpertServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizExpertServiceImpl.java index 49ef70c..7ea5860 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizExpertServiceImpl.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizExpertServiceImpl.java @@ -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 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 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("
" + 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("
" + successNum + "、账号 " + vo.getPhone() + " 导入成功"); + } catch (Exception e) { + failureNum++; + String msg = e.getMessage(); + if (msg == null) msg = e.getClass().getSimpleName(); + failureMsg.append("
" + failureNum + "、第 " + rowNo + " 行: " + msg); + } + } + if (failureNum > 0) { + failureMsg.insert(0, "很抱歉, 部分导入失败, 共 " + failureNum + " 条:"); + } + if (successNum > 0) { + successMsg.insert(0, "恭喜您, 数据已全部导入成功!共 " + successNum + " 条, 数据如下:"); + } + return successMsg.toString() + failureMsg.toString(); + } } diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizExpertMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizExpertMapper.xml index fae1603..36ef9ad 100644 --- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizExpertMapper.xml +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizExpertMapper.xml @@ -29,7 +29,7 @@ - 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