package com.example.caseData.service; import cn.hutool.crypto.SecureUtil; import cn.hutool.http.HttpUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.example.caseData.config.EnvConfig; import com.example.caseData.dao.*; import com.example.caseData.dto.publicDto.LoginDto; import com.example.caseData.exception.BusinessException; import com.example.caseData.extend.aliyun.Oss; import com.example.caseData.extend.app.Hospital.GetHospitalByUuidResponse; import com.example.caseData.extend.app.Hospital.Hospital; import com.example.caseData.extend.app.Score.Score; import com.example.caseData.extend.app.UserInfo.GetUserInfoResponse; import com.example.caseData.extend.app.UserInfo.UserInfo; import com.example.caseData.extend.weChat.WxMaServiceUtils; import com.example.caseData.model.*; import com.example.caseData.utils.JwtUtil; import com.example.caseData.utils.Replace; import com.example.caseData.utils.Sha256Util; import com.example.caseData.utils.StringToInt; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.time.LocalDateTime; import java.time.Year; import java.time.format.DateTimeFormatter; import java.util.*; import java.time.LocalDate; import java.util.Random; import static com.baomidou.mybatisplus.extension.toolkit.Db.save; @Slf4j @Service public class UserService { @Resource private UserDao userDao; @Resource private UserInfo userInfo; @Resource private BasicHospitalDao basicHospitalDao; @Resource private CaseClinicalDoctorDao caseClinicalDoctorDao; @Resource private CaseClinicalDoctorCertDao caseClinicalDoctorCertDao; @Resource private CaseClinicalArticleDao caseClinicalArticleDao; @Resource private CaseClinicalVideoDao caseClinicalVideoDao; @Resource private CaseClinicalRecordScoreDao caseClinicalRecordScoreDao; @Resource private Hospital hospital; @Resource private JwtUtil jwtUtil; @Resource private Score score; @Resource private EnvConfig envConfig; @Resource private WxMaServiceUtils wxMaServiceUtils; @Resource private CertService certService; @Resource private StatsCaseClinicalDao statsCaseClinicalDao; /** * 用户登陆-手机号 * @return UserModel */ @Transactional public LoginDto UserLoginWithMobile(String phone) throws BusinessException { // 获取app用户数据 GetUserInfoResponse result = userInfo.getUserInfoByMobile(phone); UserModel user = GetAppUserInfo(result); // 生成jwt String token = jwtUtil.createToken(String.valueOf(user.getUserId())); // 获取对应医生数据 LambdaQueryWrapper caseClinicalDoctorWrapper = new LambdaQueryWrapper<>(); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getDoctorIden, user.getUserIden()); CaseClinicalDoctorModel caseClinicalDoctor = caseClinicalDoctorDao.selectOne(caseClinicalDoctorWrapper); // 处理返回值 LoginDto g = new LoginDto(); g.setUserId(String.valueOf(user.getUserId())); g.setUserName(user.getUserName()); g.setAvatar(Replace.addOssDomain(user.getAvatar())); g.setToken(token); if (caseClinicalDoctor != null) { g.setDoctorId(String.valueOf(caseClinicalDoctor.getDoctorId())); } return g; } /** * 用户登陆-app * @param appToken 1 */ @Transactional public LoginDto UserLoginWithApp(String appToken) throws BusinessException { GetUserInfoResponse result = userInfo.getUserInfoByToken(appToken); UserModel user = GetAppUserInfo(result); // 生成jwt String token = jwtUtil.createToken(String.valueOf(user.getUserId())); // 获取对应医生数据 LambdaQueryWrapper caseClinicalDoctorWrapper = new LambdaQueryWrapper<>(); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getDoctorIden, user.getUserIden()); CaseClinicalDoctorModel caseClinicalDoctor = caseClinicalDoctorDao.selectOne(caseClinicalDoctorWrapper); // 处理返回值 LoginDto g = new LoginDto(); g.setUserId(String.valueOf(user.getUserId())); g.setUserName(user.getUserName()); g.setAvatar(Replace.addOssDomain(user.getAvatar())); g.setToken(token); if (caseClinicalDoctor != null) { g.setDoctorId(String.valueOf(caseClinicalDoctor.getDoctorId())); } return g; } /** * 获取app用户数据 * @return UserModel */ public UserModel GetAppUserInfo(GetUserInfoResponse r) throws BusinessException { GetUserInfoResponse.ResponsData data = r.getData(); // 查询数据库用户信息 LambdaQueryWrapper userModelWrapper = new LambdaQueryWrapper<>(); userModelWrapper.eq(UserModel::getUserIden, r.getData().getUuid()); UserModel user = userDao.selectOne(userModelWrapper); if (user == null){ // 构造 UserModel 对象 user = new UserModel(); user.setUserIden(data.getUuid()); user.setUserName(data.getRealname()); user.setUserMobile(data.getMobile()); user.setMobileEncryption(SecureUtil.md5(data.getMobile())); user.setStatus(1); user.setRegisterSource(2); user.setOpenId(""); user.setUnionId(""); user.setSex(0); if (data.getPhoto() != null) { String ossPath = handleUserImage(data.getImg_host() + data.getPhoto()); user.setAvatar(ossPath); }else{ user.setAvatar(""); } user.setDepartmentName(data.getOfficeName()); user.setTitle(StringToInt.DoctorTitleToInt(data.getPositionName())); user.setAddress(data.getProvName()); int res = userDao.insert(user); if (res <= 0){ throw new BusinessException("操作失败"); } }else{ if (!Objects.equals(user.getUserName(), data.getRealname())){ user.setUserName(data.getRealname()); } if (!Objects.equals(user.getUserMobile(), data.getMobile())){ user.setUserMobile(data.getMobile()); user.setMobileEncryption(SecureUtil.md5(data.getMobile())); } if (!Objects.equals(user.getTitle(), StringToInt.DoctorTitleToInt(data.getPositionName()))){ user.setTitle(StringToInt.DoctorTitleToInt(data.getPositionName())); } if (!Objects.equals(user.getDepartmentName(), data.getOfficeName())){ user.setDepartmentName(data.getOfficeName()); } if (!Objects.equals(user.getAddress(), data.getProvName())){ user.setAddress(data.getProvName()); } if (data.getPhoto() != null) { try { String ossPath = handleUserImage(data.getImg_host() + data.getPhoto()); user.setAvatar(ossPath); }catch (Exception e){ // 不处理 } } userDao.updateById(user); } // 获取app医院数据 BasicHospitalModel basicHospital = GetAppHospital(data.getHospitalUuid()); // 修改用户所属医院 if (!Objects.equals(user.getHospitalId(), basicHospital.getHospitalId())){ user.setHospitalId(basicHospital.getHospitalId()); userDao.updateById(user); } // 获取对应医生数据 LambdaQueryWrapper caseClinicalDoctorWrapper = new LambdaQueryWrapper<>(); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getDoctorIden, user.getUserIden()); CaseClinicalDoctorModel caseClinicalDoctor = caseClinicalDoctorDao.selectOne(caseClinicalDoctorWrapper); if (caseClinicalDoctor == null){ // 二次匹配:通过姓名-医院获取医生信息 caseClinicalDoctorWrapper = new LambdaQueryWrapper<>(); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getDoctorName, user.getUserName()); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getHospitalId, basicHospital.getHospitalId()); caseClinicalDoctor = caseClinicalDoctorDao.selectOne(caseClinicalDoctorWrapper); } if (caseClinicalDoctor == null){ CaseClinicalDoctorModel c = new CaseClinicalDoctorModel(); c.setDoctorName(user.getUserName()); c.setDoctorIden(user.getUserIden()); c.setHospitalId(user.getHospitalId()); c.setAvatar(user.getAvatar()); int res = caseClinicalDoctorDao.insert(c); if (res <= 0){ throw new BusinessException("操作失败"); } }else{ if (!Objects.equals(caseClinicalDoctor.getDoctorName(), data.getRealname())){ caseClinicalDoctor.setDoctorName(data.getRealname()); } if (!Objects.equals(basicHospital.getHospitalId(), caseClinicalDoctor.getHospitalId())){ caseClinicalDoctor.setHospitalId(basicHospital.getHospitalId()); } if (!Objects.equals(user.getAvatar(), caseClinicalDoctor.getAvatar())){ caseClinicalDoctor.setAvatar(user.getAvatar()); } if (!Objects.equals(user.getUserIden(), caseClinicalDoctor.getDoctorIden())){ caseClinicalDoctor.setDoctorIden(user.getUserIden()); } caseClinicalDoctorDao.updateById(caseClinicalDoctor); } return user; } /** * 获取app医院数据 * @param hospitalIden app医院唯一标识 * @return BasicHospitalModel */ public BasicHospitalModel GetAppHospital(String hospitalIden) throws BusinessException { // 查询数据库医院信息 LambdaQueryWrapper basicHospitalWrapper = new LambdaQueryWrapper<>(); basicHospitalWrapper.eq(BasicHospitalModel::getHospitalIden, hospitalIden); BasicHospitalModel basicHospital = basicHospitalDao.selectOne(basicHospitalWrapper); if (basicHospital == null){ // 请求接口获取数据 GetHospitalByUuidResponse result = hospital.getHospitalByUuid(hospitalIden); GetHospitalByUuidResponse.GetHospitalByUuidData data = result.getData(); // 构造 UserModel 对象 basicHospital = new BasicHospitalModel(); basicHospital.setHospitalIden(hospitalIden); basicHospital.setHospitalName(data.getName()); basicHospital.setSource(2); basicHospital.setHospitalLevel(data.getLevel()); basicHospital.setDoctorNumber(data.getExpert_num()); basicHospital.setProvince(data.getProv_name()); basicHospital.setCity(data.getCity_name()); save(basicHospital); } return basicHospital; } /** * 获取app医生数据 * @return UserModel */ public CaseClinicalDoctorModel GetAppCaseClinicalDoctor(GetUserInfoResponse r) throws BusinessException { GetUserInfoResponse.ResponsData data = r.getData(); // 获取app医院数据 BasicHospitalModel basicHospital = GetAppHospital(data.getHospitalUuid()); // 获取对应医生数据 LambdaQueryWrapper caseClinicalDoctorWrapper = new LambdaQueryWrapper<>(); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getDoctorIden, data.getUuid()); CaseClinicalDoctorModel caseClinicalDoctor = caseClinicalDoctorDao.selectOne(caseClinicalDoctorWrapper); if (caseClinicalDoctor == null){ // 二次匹配:通过姓名-医院获取医生信息 caseClinicalDoctorWrapper = new LambdaQueryWrapper<>(); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getDoctorName, data.getRealname()); caseClinicalDoctorWrapper.eq(CaseClinicalDoctorModel::getHospitalId, basicHospital.getHospitalId()); caseClinicalDoctor = caseClinicalDoctorDao.selectOne(caseClinicalDoctorWrapper); } if (caseClinicalDoctor == null){ caseClinicalDoctor = new CaseClinicalDoctorModel(); caseClinicalDoctor.setDoctorName(data.getRealname()); caseClinicalDoctor.setDoctorIden(data.getUuid()); caseClinicalDoctor.setHospitalId(basicHospital.getHospitalId()); if (data.getPhoto() != null) { String ossPath = handleUserImage(data.getImg_host() + data.getPhoto()); caseClinicalDoctor.setAvatar(ossPath); }else{ caseClinicalDoctor.setAvatar(""); } int res = caseClinicalDoctorDao.insert(caseClinicalDoctor); if (res <= 0){ throw new BusinessException("操作失败"); } }else{ if (!Objects.equals(caseClinicalDoctor.getDoctorName(), data.getRealname())){ caseClinicalDoctor.setDoctorName(data.getRealname()); } if (!Objects.equals(basicHospital.getHospitalId(), caseClinicalDoctor.getHospitalId())){ caseClinicalDoctor.setHospitalId(basicHospital.getHospitalId()); } if (!Objects.equals(data.getUuid(), caseClinicalDoctor.getDoctorIden())){ caseClinicalDoctor.setDoctorIden(data.getUuid()); } if (data.getPhoto() != null) { try { String ossPath = handleUserImage(data.getImg_host() + data.getPhoto()); caseClinicalDoctor.setAvatar(ossPath); }catch (Exception e){ // 不处理 } } caseClinicalDoctorDao.updateById(caseClinicalDoctor); } return caseClinicalDoctor; } public Map getAppUser(String mobile) { try { HashMap params = new HashMap<>(); params.put("mobile", mobile); params.put("platform", "case"); params.put("timestamp", System.currentTimeMillis() / 1000); String signature = Sha256Util.getSign(params, "zd8V2LYD4achjFZrbHgD2PuzKuthDCVx"); params.put("signature", signature); String result = HttpUtil.post("https://wx.igandan.com/hcp/getInfo", params); log.info("11111"); log.info(result); log.info("2222"); // 解析JSON ObjectMapper objectMapper = new ObjectMapper(); Map resultMap = objectMapper.readValue(result, Map.class); return resultMap; } catch (Exception e) { throw new BusinessException(e.getMessage()); } } /** * 发放积分 * @param type 类型(1:文章 2:视频 3:病例交流) * @throws BusinessException */ public boolean ReportUserScore(String id,Integer type,String userId,Integer point) throws BusinessException{ // 获取积分发放记录 // 获取今天的日期,并设置时间为 00:00:00 LocalDateTime startOfToday = LocalDate.now().atStartOfDay(); // 例如:2025-07-30 00:00:00 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", userId) .ge("created_at", startOfToday) // created_at >= 今天 00:00:00 .orderByDesc("created_at"); // 最新的在前 List caseClinicalRecordScores = caseClinicalRecordScoreDao.selectList(queryWrapper); if (caseClinicalRecordScores.size() >= 3){ return true; } // 获取用户数据 UserModel user = userDao.selectById(userId); if (user == null){ throw new BusinessException("操作失败"); } // 发放积分 score.ReportUserScore(user.getUserIden(),5,"病例库-病例互动"); // 添加打赏记录 CaseClinicalRecordScoreModel data = new CaseClinicalRecordScoreModel(); data.setUserId(Long.valueOf(userId)); data.setScore(point); data.setId(Long.valueOf(id)); data.setUserName(user.getUserName()); data.setNodeName("病例库-病例互动"); data.setType(type); data.setScoreType(5); caseClinicalRecordScoreDao.insert(data); return true; } // 获取发放积分次数 public Integer GetReportUserScore(String userId) throws BusinessException{ // 获取积分发放记录 // 获取今天的日期,并设置时间为 00:00:00 LocalDateTime startOfToday = LocalDate.now().atStartOfDay(); // 例如:2025-07-30 00:00:00 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", userId) .ge("created_at", startOfToday) // created_at >= 今天 00:00:00 .orderByDesc("created_at"); // 最新的在前 List caseClinicalRecordScores = caseClinicalRecordScoreDao.selectList(queryWrapper); return caseClinicalRecordScores.size(); } /** * 处理用户头像:下载远程头像并上传至 OSS * * @param wxAvatarUrl 微信头像地址 * @return OSS 路径(以 / 开头),如 /user/avatar/202507301530123456.png */ public String handleUserImage(String wxAvatarUrl) { if (wxAvatarUrl == null || wxAvatarUrl.isEmpty()) { return null; } HttpURLConnection conn = null; try { // 1. 下载远程图片 URL url = new URL(wxAvatarUrl); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(10000); conn.setRequestMethod("GET"); if (conn.getResponseCode() != 200) { throw new BusinessException("图片处理失败"); } try (InputStream input = conn.getInputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int len; while ((len = input.read(buffer)) != -1) { output.write(buffer, 0, len); } byte[] imageBytes = output.toByteArray(); if (imageBytes.length == 0) { throw new BusinessException("图片处理失败"); } // 2. 生成 OSS 路径 String dateTimeStr = LocalDateTime.now() .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); int randomSuffix = new Random().nextInt(9000) + 1000; String ossPath = ""; if (Objects.equals(envConfig.getActive(), "dev")){ ossPath = "dev/static/images/" + dateTimeStr + randomSuffix + ".png"; }else{ ossPath = "prod/static/images/" + dateTimeStr + randomSuffix + ".png"; } // 3. 上传 OSS boolean success = Oss.putObject(ossPath, imageBytes); if (!success) { throw new BusinessException("图片处理失败"); } return "/" + ossPath; } } catch (Exception e) { // 头像处理失败 System.out.println(e.getMessage()); return ""; } finally { if (conn != null) { conn.disconnect(); } } } /** * 生成用户分享二维码-文章/视频 * @param id 文章/视频id * @param type 类型 1:文章 2:视频 */ @Transactional public byte[] CreateUserCaseClinicalUnlimitedQrcode(String id,Integer type) throws BusinessException{ String scene = ""; String page = "pages/detail/detail"; if (type == 1){ scene = "?id=" + id + "&type=1"; }else if (type == 2){ scene = "?id=" + id + "&type=2"; }else{ throw new BusinessException("生成二维码类型错误"); } try { // 生成二维码 byte[] fileByte = wxMaServiceUtils.getUnlimitedQrcode(scene,page); // 上传oss String fileName = ""; String no = ""; if (type == 1){ no = "a" + id; }else { no = "v" + id; } if (Objects.equals(envConfig.getActive(), "dev")){ fileName = "dev/static/images/" + no + ".png"; }else{ fileName = "prod/static/images/" + no + ".png"; } boolean res = Oss.putObject(fileName, fileByte); if (!res) { throw new BusinessException("生成失败"); } if (type == 1){ // 修改文章证书 CaseClinicalArticleModel caseClinicalArticle = caseClinicalArticleDao.selectById(Long.valueOf(id)); if (caseClinicalArticle == null){ throw new BusinessException("生成失败"); } caseClinicalArticle.setShareQrcode("/" + fileName); caseClinicalArticleDao.updateById(caseClinicalArticle); }else{ // 修改视频证书 CaseClinicalVideoModel caseClinicalVideo = caseClinicalVideoDao.selectById(Long.valueOf(id)); if (caseClinicalVideo == null){ throw new BusinessException("生成失败"); } caseClinicalVideo.setShareQrcode("/" + fileName); caseClinicalVideoDao.updateById(caseClinicalVideo); } return fileByte; } catch (Exception e) { throw new BusinessException(e.getMessage()); } } /** * 生成用户证书-文章/视频 * @param id 文章/视频id * @param type 类型 1:文章 2:视频 */ @Transactional public boolean CreateUserCert(String id,Integer type,String doctorId, byte[] qrCodeByte, byte[] avatarByte) throws BusinessException{ try { String title = ""; // 标题 if (type == 1){ // 获取文章数据 CaseClinicalArticleModel caseClinicalArticle = caseClinicalArticleDao.selectById(Long.valueOf(id)); if (caseClinicalArticle == null){ throw new BusinessException("生成证书错误"); } title = caseClinicalArticle.getArticleTitle(); }else if (type == 2){ CaseClinicalVideoModel caseClinicalVideo = caseClinicalVideoDao.selectById(Long.valueOf(id)); if (caseClinicalVideo == null){ throw new BusinessException("生成证书错误"); } title = caseClinicalVideo.getVideoTitle(); }else{ throw new BusinessException("生成证书错误"); } // 获取医生数据 CaseClinicalDoctorModel caseClinicalDoctor = caseClinicalDoctorDao.selectById(doctorId); if (caseClinicalDoctor == null){ throw new BusinessException("生成证书错误"); } // 获取证书文件 LambdaQueryWrapper certWrapper = new LambdaQueryWrapper<>(); certWrapper.eq(CaseClinicalDoctorCertModel::getDoctorId, doctorId); certWrapper.eq(CaseClinicalDoctorCertModel::getId, id); certWrapper.eq(CaseClinicalDoctorCertModel::getType, type); CaseClinicalDoctorCertModel caseClinicalDoctorCert = caseClinicalDoctorCertDao.selectOne(certWrapper); if (caseClinicalDoctorCert != null){ return true; } // 获取统计数据 StatsCaseClinicalModel statsCaseClinical = statsCaseClinicalDao.selectById(1); if (statsCaseClinical == null){ throw new BusinessException("生成证书错误"); } // 生成证书 String certificateNo = "证书编号:GDXZALK" + String.valueOf(Year.now().getValue()) + String.format("%06d", (statsCaseClinical.getArticleNum() + statsCaseClinical.getVideoNum())); String ossPath = certService.createCert( avatarByte, "static/cert/seal.png", certificateNo, caseClinicalDoctor.getDoctorName(), "您的案例《" + title + "》经评议,被肝胆相照临床病例库收录,特发此证。", qrCodeByte, doctorId ); // 新增证书文件 caseClinicalDoctorCert = new CaseClinicalDoctorCertModel(); caseClinicalDoctorCert.setDoctorId(Long.valueOf(doctorId)); caseClinicalDoctorCert.setId(Long.valueOf(id)); caseClinicalDoctorCert.setType(type); caseClinicalDoctorCert.setCertImage(ossPath); caseClinicalDoctorCertDao.insert(caseClinicalDoctorCert); return true; } catch (Exception e) { throw new BusinessException(e.getMessage()); } } }