Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
177e0e4d97 | ||
|
|
f74a2322e4 | ||
|
|
822cdbf539 | ||
|
|
d75f21c17e | ||
|
|
ffee8611fe | ||
|
|
0e8f5e7b14 | ||
|
|
115407026f | ||
|
|
9f545952e4 | ||
|
|
1988e031e5 | ||
|
|
44b0f6ca2b | ||
|
|
ae866f0791 | ||
|
|
4610ac4737 | ||
|
|
6d6920a2b3 | ||
|
|
c7041fa69e | ||
|
|
370f5e0089 | ||
|
|
a47f27967e | ||
|
|
6c08f5f141 | ||
|
|
690f688c6a | ||
|
|
63cc815022 | ||
|
|
8f00d07377 | ||
|
|
2e90ec71cc | ||
|
|
a43806c4e9 | ||
|
|
237df3ef38 | ||
|
|
bd9fece99c | ||
|
|
47cebd3392 | ||
|
|
61e049cc47 | ||
|
|
ad03177e50 | ||
|
|
3db3301fe3 | ||
|
|
fa9a186a5e | ||
|
|
3cea926a1e | ||
|
|
fe82afd934 | ||
|
|
96242b5c00 | ||
|
|
291f6a4eaa |
@@ -0,0 +1,10 @@
|
||||
-- QH_WAT_MONITOR_DATA 新增 EVENT_ID(事件ID)和 LOCATE_TIME(定位时间)字段
|
||||
-- 数据库:118-DM 达梦数据库,SQL 全大写规范
|
||||
ALTER TABLE "QH_WAT_MONITOR_DATA" ADD "EVENT_ID" VARCHAR(64) DEFAULT NULL;
|
||||
ALTER TABLE "QH_WAT_MONITOR_DATA" ADD "LOCATE_TIME" DATETIME DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN "QH_WAT_MONITOR_DATA"."EVENT_ID" IS '事件ID';
|
||||
COMMENT ON COLUMN "QH_WAT_MONITOR_DATA"."LOCATE_TIME" IS '定位时间';
|
||||
|
||||
CREATE INDEX "IDX_EVENT_ID" ON "QH_WAT_MONITOR_DATA"("EVENT_ID");
|
||||
CREATE INDEX "IDX_WATCH_NO_EVENT_ID" ON "QH_WAT_MONITOR_DATA"("WATCH_NO", "EVENT_ID");
|
||||
+1
-1
@@ -99,7 +99,7 @@ public enum AnYanApiEnum {
|
||||
/**
|
||||
* 18.查询关爱联络员信息及急救人员查询接口
|
||||
*/
|
||||
QUERY_EMPLOYEE_CARE_PERSON("/aygc-znhpt-sys/0/api/empCareEmp/findPageByIdNo", "", HttpMethod.POST),
|
||||
QUERY_EMPLOYEE_CARE_PERSON("/aygc-znhpt-sys/0/api/empCareEmp/findPage", "", HttpMethod.POST),
|
||||
|
||||
/**
|
||||
* 19.批量更新员工关键信息接口
|
||||
|
||||
+3
@@ -1,5 +1,6 @@
|
||||
package com.renkang.anyan.model.dto;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -34,4 +35,6 @@ public class CareUserDetailsParam {
|
||||
|
||||
@Schema(title = "pageType")
|
||||
private String pageType = "plus";
|
||||
|
||||
private int year = DateUtil.thisYear();
|
||||
}
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.consultation.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.renkang.consultation.entity.ConPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -11,6 +12,10 @@ public class AccountDTO extends ConPage {
|
||||
@Schema(title = "专家姓名")
|
||||
private String doctorName;
|
||||
|
||||
public void setDoctorName(String doctorName) {
|
||||
this.doctorName = doctorName != null ? HtmlUtil.unescape(doctorName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "医院选择")
|
||||
private String hospitalName;
|
||||
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.consultation.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.renkang.consultation.entity.ConPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -10,6 +11,10 @@ public class ConDoctorDTO extends ConPage {
|
||||
|
||||
@Schema(title = "专家姓名")
|
||||
private String doctorName;
|
||||
|
||||
public void setDoctorName(String doctorName) {
|
||||
this.doctorName = doctorName != null ? HtmlUtil.unescape(doctorName) : null;
|
||||
}
|
||||
@Schema(title = "专家编号")
|
||||
private String doctorNo;
|
||||
|
||||
|
||||
+8
@@ -1,5 +1,6 @@
|
||||
package com.renkang.consultation.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.renkang.consultation.entity.ConPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -17,6 +18,9 @@ public class ConEvaluateDTO extends ConPage {
|
||||
|
||||
@Schema(title = "咨询人")
|
||||
private String userName;
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName != null ? HtmlUtil.unescape(userName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "二级单位id")
|
||||
private String sysOrgSecondId;
|
||||
@@ -27,6 +31,10 @@ public class ConEvaluateDTO extends ConPage {
|
||||
@Schema(title = "咨询专家")
|
||||
private String expertName;
|
||||
|
||||
public void setExpertName(String expertName) {
|
||||
this.expertName = expertName != null ? HtmlUtil.unescape(expertName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "专家医院")
|
||||
private String expertHospital;
|
||||
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.consultation.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.renkang.consultation.entity.ConPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -10,6 +11,10 @@ public class ConHealthInfoAnswerDTO extends ConPage {
|
||||
@Schema(title = "员工姓名")
|
||||
private String fromName;
|
||||
|
||||
public void setFromName(String fromName) {
|
||||
this.fromName = fromName != null ? HtmlUtil.unescape(fromName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "手机号")
|
||||
private String phone;
|
||||
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.consultation.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.renkang.consultation.entity.ConPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -11,6 +12,10 @@ public class ConHelperDTO extends ConPage {
|
||||
@Schema(title = "全科医生姓名")
|
||||
private String userName;
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName != null ? HtmlUtil.unescape(userName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "状态")
|
||||
private Integer status;
|
||||
|
||||
|
||||
+9
@@ -1,5 +1,6 @@
|
||||
package com.renkang.consultation.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.renkang.consultation.entity.ConPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -17,6 +18,10 @@ public class ConSessionDTO extends ConPage {
|
||||
@Schema(title = "员工姓名")
|
||||
private String fromName;
|
||||
|
||||
public void setFromName(String fromName) {
|
||||
this.fromName = fromName != null ? HtmlUtil.unescape(fromName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "员工id")
|
||||
private String fromAccount;
|
||||
|
||||
@@ -26,6 +31,10 @@ public class ConSessionDTO extends ConPage {
|
||||
@Schema(title = "专家姓名")
|
||||
private String doctorName;
|
||||
|
||||
public void setDoctorName(String doctorName) {
|
||||
this.doctorName = doctorName != null ? HtmlUtil.unescape(doctorName) : null;
|
||||
}
|
||||
|
||||
@Schema(title = "咨询状态(0待确认 1接收预约 2拒绝预约 3已过期 4已取消 5咨询中 6专家咨询结束 7员工咨询结束 8超时自动结束)")
|
||||
private String contentStatus;
|
||||
|
||||
|
||||
+6
@@ -480,4 +480,10 @@ public class ConSessionApiController {
|
||||
return conSessionApiService.insertUserAndHelperSessionNew();
|
||||
}
|
||||
|
||||
@Operation(summary = "超时关闭视频咨询", description = "超时关闭视频咨询")
|
||||
@GetMapping("/endVideoSession")
|
||||
public Result<String> endVideoSession() {
|
||||
return Result.ok(conSessionApiService.endVideoSession());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -115,4 +115,6 @@ public interface ConSessionApiService {
|
||||
|
||||
UserSessionCount countForUser();
|
||||
|
||||
String endVideoSession();
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -2114,4 +2114,8 @@ public class ConSessionApiServiceImpl implements ConSessionApiService {
|
||||
return conSessionMapper.selectCount(wrapper).intValue();
|
||||
}
|
||||
|
||||
public String endVideoSession() {
|
||||
return conSessionMapper.endVideoSession();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
@@ -294,4 +294,13 @@ public interface ConSessionMapper extends BaseMapper<ConSession> , MPJBaseMapper
|
||||
@Param("doctorjob")String doctorjob,
|
||||
@Param("contentType")String contentType,
|
||||
@Param("toAccount")String toAccount);
|
||||
|
||||
/**
|
||||
* 音视频咨询过期处理:预约日期已过的视频咨询,有通话记录则改为待评价,无通话记录则改为取消
|
||||
* content_status: 4=待评价,6=取消
|
||||
*/
|
||||
@Update("UPDATE con_session SET content_status = CASE WHEN session_time IS NOT NULL THEN 4 ELSE 6 END " +
|
||||
"WHERE content_type = 2 AND CURDATE() > session_date " +
|
||||
"AND content_status NOT IN (4, 5, 6, 7, 8) AND del_flag = 0 AND status = 1")
|
||||
String endVideoSession();
|
||||
}
|
||||
|
||||
-4
@@ -3,7 +3,6 @@ package com.renkang.consultation.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -249,9 +248,6 @@ public class ConDoctorServiceImpl extends ServiceImpl<ConDoctorMapper, ConDoctor
|
||||
if (StringUtils.hasLength(dto.getHospitalName())) {
|
||||
conDoctor.setResourceId(dto.getHospitalName());
|
||||
}
|
||||
if (StrUtil.isNotEmpty(dto.getDoctorName())){
|
||||
conDoctor.setDoctorName(HtmlUtil.unescape(dto.getDoctorName()));
|
||||
}
|
||||
IPage<ConDoctor> conDoctorIPage = conDoctorMapper.queryPageList(page, conDoctor);
|
||||
List<ConDoctor> conDoctorList = conDoctorIPage.getRecords();
|
||||
if (CollectionUtils.isEmpty(conDoctorList)) {
|
||||
|
||||
+11
-2
@@ -7,6 +7,7 @@ import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.context.XxlJobHelper;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.config.shiro.ClientSignThreadLocal;
|
||||
import org.parboiled.common.StringUtils;
|
||||
@@ -36,10 +37,10 @@ public class SyncScheduleJob {
|
||||
String expireHour = "168";
|
||||
|
||||
String param = XxlJobHelper.getJobParam();
|
||||
if(StringUtils.isNotEmpty(param)){
|
||||
if (StringUtils.isNotEmpty(param)) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(param);
|
||||
String expireHourRequest = jsonObject.getString("expireHour");
|
||||
if(StringUtils.isNotEmpty(expireHourRequest)){
|
||||
if (StringUtils.isNotEmpty(expireHourRequest)) {
|
||||
expireHour = jsonObject.getString("expireHour");
|
||||
}
|
||||
}
|
||||
@@ -53,4 +54,12 @@ public class SyncScheduleJob {
|
||||
return ReturnT.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@XxlJob(value = "endVideoSession")
|
||||
public Result<String> endVideoSession() {
|
||||
// 20260327 新增 音视频咨询过期处理 规则为:超过预约时间,比如预约的是 12月9日和专家视频沟通,但超过时间一天,即为过期,需要关闭咨询
|
||||
conSessionApiController.endVideoSession();
|
||||
return Result.ok("success");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.emergency.entity.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -7,6 +8,10 @@ import lombok.Data;
|
||||
public class CallLogDTO {
|
||||
@Schema(description = "姓名")
|
||||
private String realName;
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||
}
|
||||
@Schema(description = "呼叫中心")
|
||||
private String centerId;
|
||||
@Schema(description = "坐席工号")
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.emergency.entity.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -21,6 +22,10 @@ public class PhoneListDTO {
|
||||
private String phoneType;
|
||||
@Schema(description = "姓名")
|
||||
private String realName;
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||
}
|
||||
/**
|
||||
* 0:全部 1:女 2:男
|
||||
*/
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.emergency.entity.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -9,6 +10,10 @@ public class TblQimoNoticeDTO {
|
||||
private String seatId;
|
||||
@Schema(title = "姓名")
|
||||
private String realName;
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||
}
|
||||
@Schema(title = "所属应急中心")
|
||||
private String centerId;
|
||||
@Schema(title = "orgCode")
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
package com.renkang.emergency.controller;
|
||||
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.renkang.emergency.entity.EmergencyProfessionUser;
|
||||
import com.renkang.emergency.service.EmergencyProfessionUserService;
|
||||
@@ -40,6 +41,7 @@ public class EmergencyProfessionUserController {
|
||||
@RequestParam(name = "type", defaultValue = "5") Integer type,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||
name = HtmlUtil.unescape(name);
|
||||
IPage<EmergencyProfessionUser> pageList = emergencyProfessionUserService.customPage(name, centerId, type, pageNo, pageSize);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
+4
@@ -6,6 +6,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -58,6 +59,9 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
|
||||
@Override
|
||||
public IPage<Order> pageCustom(Page<Order> page, Order order, HttpServletRequest req) {
|
||||
if(StrUtil.isNotBlank(order.getSalvageUserName())){
|
||||
order.setSalvageUserName(HtmlUtil.unescape(order.getSalvageUserName()));
|
||||
}
|
||||
String salvageUserName = order.getSalvageUserName();
|
||||
String initiatorUserName = order.getInitiatorUserName();
|
||||
Order orderCopy = new Order();
|
||||
|
||||
+4
@@ -22,6 +22,10 @@ public class WatchH5IndexItemNewVo {
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date dataDate;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date dataDateNew;
|
||||
/**
|
||||
* 注意 当wdType=sleep时此字段返回长睡时长
|
||||
*/
|
||||
|
||||
+4
@@ -1,5 +1,6 @@
|
||||
package com.renkang.watch.bean.request;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@@ -19,6 +20,9 @@ public class AbnormalEventFilter {
|
||||
private String departOrgCode;
|
||||
// 用户名称
|
||||
private String userName;
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName != null ? HtmlUtil.unescape(userName) : null;
|
||||
}
|
||||
// 工具编码
|
||||
private String watchNo;
|
||||
//员工编号
|
||||
|
||||
+14
@@ -52,4 +52,18 @@ public class AbnormalEvent extends BaseUser {
|
||||
private Date bindDate;
|
||||
@Schema(title = "重点指标用户(0:非重点指标用户 1:大病异常 2:慢病异常 3:指标异常)")
|
||||
private List<WatchKeyUser> keyUserList;
|
||||
/**
|
||||
* 事件ID(用于告警事件与GPS位置更新关联的唯一标识)
|
||||
*/
|
||||
@Excel(name = "事件ID", width = 32, orderNum = "12")
|
||||
@Schema(title = "事件ID")
|
||||
private String eventId;
|
||||
/**
|
||||
* 定位时间(手表定时上报的GPS定位时间)
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Excel(name = "定位时间", format = "yyyy-MM-dd HH:mm:ss", width = 20, orderNum = "13")
|
||||
@Schema(title = "定位时间")
|
||||
private Date locateTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.renkang.watch.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author TianZi
|
||||
* @date 2023/11/7 14:13
|
||||
*/
|
||||
@Data
|
||||
public class BindDeviceDTO {
|
||||
|
||||
@Schema(title = "手表编号")
|
||||
private String watchNo;
|
||||
}
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package com.renkang.watch.dto;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -18,6 +19,10 @@ public class CalorieDataByUserDTO {
|
||||
private Integer pageSize = 10;
|
||||
@Schema(title = "用户姓名")
|
||||
private String realName;
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||
}
|
||||
@Schema(title = "工号")
|
||||
private String workNo;
|
||||
@Schema(title = "用户id组")
|
||||
|
||||
+14
@@ -151,6 +151,20 @@ public class WatchMonitorData implements Serializable {
|
||||
@Excel(name = "部门编码", width = 15)
|
||||
@Schema(title = "部门编码")
|
||||
private java.lang.String orgCode;
|
||||
/**
|
||||
* 事件ID(用于告警事件与GPS位置更新关联的唯一标识)
|
||||
*/
|
||||
@Excel(name = "事件ID", width = 32)
|
||||
@Schema(title = "事件ID")
|
||||
private java.lang.String eventId;
|
||||
/**
|
||||
* 定位时间(手表上报的GPS定位时间,定时上报场景使用)
|
||||
*/
|
||||
@Excel(name = "定位时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@Schema(title = "定位时间")
|
||||
private java.util.Date locateTime;
|
||||
|
||||
@Schema(title = "员工编号")
|
||||
@TableField(exist = false)
|
||||
|
||||
+51
-2
@@ -1,22 +1,71 @@
|
||||
package com.renkang.watch.vo.UserData.res;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.renkang.watch.WatchH5IndexItemNewVo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/** 用户统计数据
|
||||
/**
|
||||
* 用户统计数据
|
||||
*
|
||||
* @author stan
|
||||
* @since 2024-11-04 16:33
|
||||
*/
|
||||
@Data
|
||||
public class UserStat {
|
||||
/**
|
||||
* 数据类型:0=心率 1=血氧 2=压力 3=体温 4=睡眠
|
||||
*/
|
||||
private String type;
|
||||
private BigDecimal latest;
|
||||
private BigDecimal max;
|
||||
private BigDecimal min;
|
||||
private BigDecimal avg;
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date latestTime;
|
||||
|
||||
public UserStat() {
|
||||
}
|
||||
|
||||
public UserStat(WatchH5IndexItemNewVo watchH5IndexItemNewVo) {
|
||||
switch (watchH5IndexItemNewVo.getWdType()) {
|
||||
case "sleep":
|
||||
this.type = "4";
|
||||
break;
|
||||
case "heart_rate":
|
||||
this.type = "0";
|
||||
break;
|
||||
case "stress":
|
||||
this.type = "2";
|
||||
break;
|
||||
case "spo2":
|
||||
this.type = "1";
|
||||
break;
|
||||
case "temperature":
|
||||
this.type = "3";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.latest = parseSafe(watchH5IndexItemNewVo.getDataValue());
|
||||
this.max = parseSafe(watchH5IndexItemNewVo.getDataMax());
|
||||
this.min = parseSafe(watchH5IndexItemNewVo.getDataMin());
|
||||
this.avg = parseSafe(watchH5IndexItemNewVo.getDataAvg());
|
||||
this.latestTime = watchH5IndexItemNewVo.getDataDateNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全解析字符串为 BigDecimal,入参为 null 或空串时返回 null
|
||||
*
|
||||
* @param value 待解析的字符串数值
|
||||
* @return 解析结果,无效输入返回 null
|
||||
*/
|
||||
private static BigDecimal parseSafe(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return BigDecimal.valueOf(Double.parseDouble(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,15 @@ public class WatchNotifyVo implements Serializable {
|
||||
private Date warnTime;
|
||||
|
||||
private String gpsErrorMsg;
|
||||
|
||||
/**
|
||||
* 事件ID(用于告警事件与GPS位置更新关联的唯一标识)
|
||||
*/
|
||||
private String eventId;
|
||||
|
||||
/**
|
||||
* 定位时间(手表上报的GPS定位时间,定时上报场景使用)
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date locateTime;
|
||||
}
|
||||
|
||||
+12
-10
@@ -18,9 +18,14 @@ public class DeptStatData {
|
||||
/**
|
||||
* 已分配设备数
|
||||
*/
|
||||
@Excel(name = "分配设备数", width = 15,orderNum = "4")
|
||||
@Excel(name = "已分配设备数", width = 15,orderNum = "4")
|
||||
private Integer assignedDeviceNums;
|
||||
|
||||
/**
|
||||
* 未分配设备数(入库设备数 - 已分配设备数,仅一、二级部门有值)
|
||||
*/
|
||||
@Excel(name = "未分配设备数", width = 15,orderNum = "5")
|
||||
private Integer undistributedDeviceNums;
|
||||
|
||||
@Excel(name = "入库设备数", width = 15,orderNum = "3")
|
||||
private Integer totalAssignedDeviceNums;
|
||||
@@ -28,25 +33,22 @@ public class DeptStatData {
|
||||
/**
|
||||
* 佩戴人员数
|
||||
*/
|
||||
@Excel(name = "佩戴人员数", width = 15,orderNum = "5")
|
||||
@Excel(name = "佩戴人员数", width = 15,orderNum = "6")
|
||||
private Integer assignedUserNums;
|
||||
/**
|
||||
* 未佩戴人员数
|
||||
*/
|
||||
@Excel(name = "未佩戴人员数", width = 15,orderNum = "6")
|
||||
@Excel(name = "未佩戴人员数", width = 15,orderNum = "7")
|
||||
private Integer undistributedUserNums;
|
||||
|
||||
@Excel(name = "心率异常人数", width = 15,orderNum = "7")
|
||||
@Excel(name = "心率异常人数", width = 15,orderNum = "8")
|
||||
private Integer heartRateNums;
|
||||
@Excel(name = "血氧异常人数", width = 15,orderNum = "8")
|
||||
@Excel(name = "血氧异常人数", width = 15,orderNum = "9")
|
||||
private Integer spo2Nums;
|
||||
@Excel(name = "压力异常人数", width = 15,orderNum = "9")
|
||||
@Excel(name = "压力异常人数", width = 15,orderNum = "10")
|
||||
private Integer stressNums;
|
||||
@Excel(name = "体温异常人数", width = 15,orderNum = "10")
|
||||
@Excel(name = "体温异常人数", width = 15,orderNum = "11")
|
||||
private Integer tempNums;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+11
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@@ -41,6 +42,16 @@ public class WatchDataApiController {
|
||||
return Result.ok(watchDataApiService.userStatResult(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户手表数据(批量返回)
|
||||
*
|
||||
* @return data
|
||||
*/
|
||||
@GetMapping("userWatchDataAll")
|
||||
public Result<List<UserStat>> userStatResultAll(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
|
||||
return Result.ok(watchDataApiService.userStatResultAll(date));
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户此刻是否拥有手表
|
||||
*
|
||||
|
||||
+9
@@ -11,6 +11,7 @@ import com.renkang.watch.vo.UserData.res.sleep.WatchDataSleepRes;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface WatchDataApiService {
|
||||
@@ -43,6 +44,14 @@ public interface WatchDataApiService {
|
||||
|
||||
UserStat userStatResult(String type);
|
||||
|
||||
/**
|
||||
* 查询当前登录用户所有健康指标统计数据(按指定日期过滤)
|
||||
*
|
||||
* @param date 查询日期,为 null 时返回最新一条数据
|
||||
* @return 各指标统计结果列表
|
||||
*/
|
||||
List<UserStat> userStatResultAll(Date date);
|
||||
|
||||
Boolean userHaveWatch(String userId);
|
||||
|
||||
UserWatchInfo findWatchInfo(String userId);
|
||||
|
||||
+236
-32
@@ -7,6 +7,8 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import com.renkang.watch.WatchH5IndexItemNewVo;
|
||||
import com.renkang.watch.api.bean.response.UserWatchInfo;
|
||||
import com.renkang.watch.api.service.WatchDataApiService;
|
||||
import com.renkang.watch.dto.WatchDataWorkoutDTO;
|
||||
@@ -24,31 +26,34 @@ import com.renkang.watch.vo.UserData.res.bodyTemperature.BodyTempStat;
|
||||
import com.renkang.watch.vo.UserData.res.bodyTemperature.BodyTempStatRes;
|
||||
import com.renkang.watch.vo.UserData.res.heartRate.WatchDataHeartRateRes;
|
||||
import com.renkang.watch.vo.UserData.res.sleep.WatchDataSleepRes;
|
||||
import com.renkang.watch.vo.WatchH5IndexItemVo;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.global.GlobalUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private WatchDataWorkoutMapper watchDataWorkoutMapper;
|
||||
|
||||
|
||||
@Autowired
|
||||
private WatchStatUserInfoDayHeartRateMapper watchStatUserInfoDayHeartRateMapper;
|
||||
|
||||
|
||||
@Autowired
|
||||
private WatchDataHeartRateMapper watchDataHeartRateMapper;
|
||||
|
||||
@@ -60,9 +65,6 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
@Autowired
|
||||
private WatchStatUserInfoDaySpo2ServiceImpl watchStatUserInfoDaySpo2ServiceImpl;
|
||||
|
||||
@Autowired
|
||||
private WatchStatUserInfoDaySleepMapper watchStatUserInfoDaySleepMapper;
|
||||
|
||||
@Autowired
|
||||
private WatchDataSleepNewMinuteMapper watchDataSleepNewMinuteMapper;
|
||||
|
||||
@@ -96,7 +98,6 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
@Autowired
|
||||
private IWatchDataTemperatureService watchDataTemperatureService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private IWatchDataStressService watchDataStressService;
|
||||
@Autowired
|
||||
@@ -106,12 +107,47 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
@Autowired
|
||||
private WatchUserDataMapper watchUserDataMapper;
|
||||
@Autowired
|
||||
private WatchStatUserInfoDayHeartRateMapper watchStatUserInfoDayHeartRateMapper;
|
||||
@Autowired
|
||||
private WatchStatUserInfoDaySleepMapper watchStatUserInfoDaySleepMapper;
|
||||
@Autowired
|
||||
private WatchStatUserInfoDaySpo2Mapper watchStatUserInfoDaySpo2Mapper;
|
||||
@Autowired
|
||||
private WatchStatUserInfoDayStressMapper watchStatUserInfoDayStressMapper;
|
||||
@Autowired
|
||||
private WatchStatUserInfoDayTempMapper watchStatUserInfoDayTempMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
/** 用户统计全量数据 Redis 缓存 key 前缀 */
|
||||
private static final String USER_STAT_ALL_CACHE_KEY = "watch:userStatAll:";
|
||||
|
||||
private AsyncTaskExecutor asyncTaskExecutor;
|
||||
|
||||
public AsyncTaskExecutor getAsyncTaskExecutor() {
|
||||
if (asyncTaskExecutor == null) {
|
||||
initAsyncExecutor();
|
||||
}
|
||||
return asyncTaskExecutor;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
initAsyncExecutor();
|
||||
}
|
||||
|
||||
private void initAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
|
||||
taskExecutor.setCorePoolSize(150);
|
||||
taskExecutor.setMaxPoolSize(500);
|
||||
taskExecutor.setQueueCapacity(500);
|
||||
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
taskExecutor.setThreadNamePrefix("watch-latest-pool1-");
|
||||
taskExecutor.initialize();
|
||||
asyncTaskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Map<String,Object>> selectSportCostListApi() {
|
||||
LoginUser loginUser = GlobalUtils.getLoginUser();
|
||||
@@ -834,20 +870,171 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
String userId = GlobalUtils.getLoginUser().getId();
|
||||
switch (type) {
|
||||
case "0":
|
||||
return handleHeartRate(userId);
|
||||
return handleHeartRate(userId,null);
|
||||
case "1":
|
||||
return handleBloodOxygen(userId);
|
||||
return handleBloodOxygen(userId,null);
|
||||
case "2":
|
||||
return handleStress(userId);
|
||||
return handleStress(userId,null);
|
||||
case "3":
|
||||
return handleBodyTemperature(userId);
|
||||
return handleBodyTemperature(userId,null);
|
||||
case "4":
|
||||
return handleSleep(userId);
|
||||
return handleSleep(userId,null);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserStat> userStatResultAll(Date date) {
|
||||
// 在主线程提前获取 userId,避免子线程无法访问 ThreadLocal 中的登录信息
|
||||
String userId = GlobalUtils.getLoginUser().getId();
|
||||
// 有时间参数时缓存 key 携带日期,避免与无参缓存冲突
|
||||
String cacheKey = USER_STAT_ALL_CACHE_KEY + userId + (date != null ? "_" + date.getTime() : "");
|
||||
|
||||
// 命中 Redis 缓存直接返回
|
||||
Object cached = redisUtil.get(cacheKey);
|
||||
if (cached != null) {
|
||||
return (List<UserStat>) cached;
|
||||
}
|
||||
|
||||
// 构建「数据类型 → 异步查询 Future」映射,消除 switch-case 冗余
|
||||
Map<String, CompletableFuture<WatchH5IndexItemNewVo>> futureMap = new HashMap<>();
|
||||
futureMap.put("sleep", CompletableFuture.supplyAsync(() -> latestSleepData(userId), getAsyncTaskExecutor()));
|
||||
futureMap.put("heart_rate", CompletableFuture.supplyAsync(() -> latestHeartRateData(userId), getAsyncTaskExecutor()));
|
||||
futureMap.put("spo2", CompletableFuture.supplyAsync(() -> latestSpo2Data(userId), getAsyncTaskExecutor()));
|
||||
futureMap.put("stress", CompletableFuture.supplyAsync(() -> latestStressData(userId), getAsyncTaskExecutor()));
|
||||
futureMap.put("temperature", CompletableFuture.supplyAsync(() -> latestTempData(userId), getAsyncTaskExecutor()));
|
||||
|
||||
// 等待全部异步查询完成
|
||||
CompletableFuture.allOf(futureMap.values().toArray(new CompletableFuture[0])).join();
|
||||
|
||||
// 转为顺序流遍历,避免并行流写入 ArrayList 的线程安全问题
|
||||
List<WatchH5IndexItemNewVo> list = new ArrayList<>();
|
||||
emptyItems().sequential().forEach(itemVo -> {
|
||||
CompletableFuture<WatchH5IndexItemNewVo> future = futureMap.get(itemVo.getWdType());
|
||||
// steps 等暂无对应查询的类型直接跳过
|
||||
if (future == null) {
|
||||
return;
|
||||
}
|
||||
WatchH5IndexItemNewVo data = future.join();
|
||||
if (data == null) {
|
||||
data = new WatchH5IndexItemNewVo();
|
||||
}
|
||||
// 将模板字段(wdType、wdTypeName、阈值)合并到结果对象
|
||||
data.fieldCopy(itemVo);
|
||||
// 睡眠数据特殊处理:dataValue 为空时补 "0"
|
||||
if ("sleep".equals(itemVo.getWdType()) && StrUtil.isEmpty(data.getDataValue())) {
|
||||
data.setDataValue("0");
|
||||
}
|
||||
list.add(data);
|
||||
});
|
||||
|
||||
List<UserStat> result = list.stream().map(UserStat::new).collect(Collectors.toList());
|
||||
|
||||
// 写入 Redis 缓存,TTL=3分钟(180秒)
|
||||
redisUtil.set(cacheKey, result, 180);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Stream<WatchH5IndexItemVo> emptyItems() {
|
||||
//睡眠
|
||||
WatchH5IndexItemVo sleepVo = new WatchH5IndexItemVo();
|
||||
sleepVo.setWdType("sleep");
|
||||
//步数
|
||||
WatchH5IndexItemVo stepsVo = new WatchH5IndexItemVo();
|
||||
stepsVo.setWdType("steps");
|
||||
//心率
|
||||
WatchH5IndexItemVo hearRateVo = new WatchH5IndexItemVo();
|
||||
hearRateVo.setWdType("heart_rate");
|
||||
//压力
|
||||
WatchH5IndexItemVo stressVo = new WatchH5IndexItemVo();
|
||||
stressVo.setWdType("stress");
|
||||
//血氧饱和度
|
||||
WatchH5IndexItemVo spo2Vo = new WatchH5IndexItemVo();
|
||||
spo2Vo.setWdType("spo2");
|
||||
//体温
|
||||
WatchH5IndexItemVo tempVo = new WatchH5IndexItemVo();
|
||||
tempVo.setWdType("temperature");
|
||||
List<WatchH5IndexItemVo> list = Arrays.asList(sleepVo, stepsVo, hearRateVo, stressVo, spo2Vo, tempVo);
|
||||
return list.parallelStream()
|
||||
.peek(itemVo -> {
|
||||
itemVo.setWarnMin("0.0");
|
||||
itemVo.setWarnMax("0.0");
|
||||
});
|
||||
}
|
||||
|
||||
private WatchH5IndexItemNewVo latestSleepData(String userId) {
|
||||
MPJLambdaWrapper<WatchStatUserInfoDaySleep> wrapper = new MPJLambdaWrapper<>();
|
||||
//因为睡眠详情页取的是长睡时长作为当天的睡眠时间 , 所以这里保持一致并没有取当天的睡眠总时长
|
||||
wrapper.selectAs(WatchStatUserInfoDaySleep::getLongDurationTotal,WatchH5IndexItemNewVo::getDataValue);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySleep::getDeepSleepTimes,WatchH5IndexItemNewVo::getDataMax);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySleep::getLightSleepTimes,WatchH5IndexItemNewVo::getDataMin);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySleep::getSleepAvg,WatchH5IndexItemNewVo::getDataAvg);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySleep::getLastUploadTime,WatchH5IndexItemNewVo::getDataDateNew);
|
||||
wrapper.eq(WatchStatUserInfoDaySleep::getUserId, userId);
|
||||
wrapper.orderByDesc(WatchStatUserInfoDaySleep::getDataDate);
|
||||
wrapper.last("limit 1");
|
||||
return watchStatUserInfoDaySleepMapper.selectJoinOne(WatchH5IndexItemNewVo.class,wrapper);
|
||||
}
|
||||
|
||||
private WatchH5IndexItemNewVo latestHeartRateData(String userId) {
|
||||
MPJLambdaWrapper<WatchStatUserInfoDayHeartRate> wrapper = new MPJLambdaWrapper<>();
|
||||
// 必须是watch_stat_user_info_day_heart_rate表中new_data字段启用后才能这样查
|
||||
wrapper.selectAs(WatchStatUserInfoDayHeartRate::getNewValue,WatchH5IndexItemNewVo::getDataValue);
|
||||
wrapper.selectAs(WatchStatUserInfoDayHeartRate::getMaxValue,WatchH5IndexItemNewVo::getDataMax);
|
||||
wrapper.selectAs(WatchStatUserInfoDayHeartRate::getMinValue,WatchH5IndexItemNewVo::getDataMin);
|
||||
wrapper.selectAs(WatchStatUserInfoDayHeartRate::getAvgValue,WatchH5IndexItemNewVo::getDataAvg);
|
||||
wrapper.selectAs(WatchStatUserInfoDayHeartRate::getLastUploadTime,WatchH5IndexItemNewVo::getDataDateNew);
|
||||
wrapper.eq(WatchStatUserInfoDayHeartRate::getUserId, userId);
|
||||
wrapper.orderByDesc(WatchStatUserInfoDayHeartRate::getDataDate);
|
||||
wrapper.last("limit 1");
|
||||
return watchStatUserInfoDayHeartRateMapper.selectJoinOne(WatchH5IndexItemNewVo.class,wrapper);
|
||||
}
|
||||
|
||||
private WatchH5IndexItemNewVo latestSpo2Data(String userId) {
|
||||
MPJLambdaWrapper<WatchStatUserInfoDaySpo2> wrapper = new MPJLambdaWrapper<>();
|
||||
// 必须是watch_stat_user_info_day_spo2表中new_data字段启用后才能这样查
|
||||
wrapper.selectAs(WatchStatUserInfoDaySpo2::getNewValue,WatchH5IndexItemNewVo::getDataValue);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySpo2::getMaxValue,WatchH5IndexItemNewVo::getDataMax);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySpo2::getMinValue,WatchH5IndexItemNewVo::getDataMin);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySpo2::getAvgValue,WatchH5IndexItemNewVo::getDataAvg);
|
||||
wrapper.selectAs(WatchStatUserInfoDaySpo2::getLastUploadTime,WatchH5IndexItemNewVo::getDataDateNew);
|
||||
wrapper.eq(WatchStatUserInfoDaySpo2::getUserId, userId);
|
||||
wrapper.orderByDesc(WatchStatUserInfoDaySpo2::getDataDate);
|
||||
wrapper.last("limit 1");
|
||||
return watchStatUserInfoDaySpo2Mapper.selectJoinOne(WatchH5IndexItemNewVo.class,wrapper);
|
||||
}
|
||||
|
||||
private WatchH5IndexItemNewVo latestStressData(String userId) {
|
||||
MPJLambdaWrapper<WatchStatUserInfoDayStress> wrapper = new MPJLambdaWrapper<>();
|
||||
// 必须是watch_stat_user_info_day_stress表中new_data字段启用后才能这样查
|
||||
wrapper.selectAs(WatchStatUserInfoDayStress::getNewValue,WatchH5IndexItemNewVo::getDataValue);
|
||||
wrapper.selectAs(WatchStatUserInfoDayStress::getMaxValue,WatchH5IndexItemNewVo::getDataMax);
|
||||
wrapper.selectAs(WatchStatUserInfoDayStress::getMinValue,WatchH5IndexItemNewVo::getDataMin);
|
||||
wrapper.selectAs(WatchStatUserInfoDayStress::getAvgValue,WatchH5IndexItemNewVo::getDataAvg);
|
||||
wrapper.selectAs(WatchStatUserInfoDayStress::getLastUploadTime,WatchH5IndexItemNewVo::getDataDateNew);
|
||||
wrapper.eq(WatchStatUserInfoDayStress::getUserId, userId);
|
||||
wrapper.orderByDesc(WatchStatUserInfoDayStress::getDataDate);
|
||||
wrapper.last("limit 1");
|
||||
return watchStatUserInfoDayStressMapper.selectJoinOne(WatchH5IndexItemNewVo.class,wrapper);
|
||||
}
|
||||
|
||||
private WatchH5IndexItemNewVo latestTempData(String userId) {
|
||||
MPJLambdaWrapper<WatchStatUserInfoDayTemp> wrapper = new MPJLambdaWrapper<>();
|
||||
// 必须是watch_stat_user_info_day_temp表中new_data字段启用后才能这样查
|
||||
wrapper.selectAs(WatchStatUserInfoDayTemp::getNewValue,WatchH5IndexItemNewVo::getDataValue);
|
||||
wrapper.selectAs(WatchStatUserInfoDayTemp::getMaxValue,WatchH5IndexItemNewVo::getDataMax);
|
||||
wrapper.selectAs(WatchStatUserInfoDayTemp::getMinValue,WatchH5IndexItemNewVo::getDataMin);
|
||||
wrapper.selectAs(WatchStatUserInfoDayTemp::getAvgValue,WatchH5IndexItemNewVo::getDataAvg);
|
||||
wrapper.selectAs(WatchStatUserInfoDayTemp::getLastUploadTime,WatchH5IndexItemNewVo::getDataDateNew);
|
||||
wrapper.eq(WatchStatUserInfoDayTemp::getUserId, userId);
|
||||
wrapper.orderByDesc(WatchStatUserInfoDayTemp::getDataDate);
|
||||
wrapper.last("limit 1");
|
||||
return watchStatUserInfoDayTempMapper.selectJoinOne(WatchH5IndexItemNewVo.class,wrapper);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean userHaveWatch(String userId) {
|
||||
if (StrUtil.isBlank(userId)) {
|
||||
@@ -885,7 +1072,7 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
}
|
||||
|
||||
|
||||
private UserStat handleSleep(String userId) {
|
||||
private UserStat handleSleep(String userId, Date date) {
|
||||
LambdaQueryWrapper<WatchStatUserInfoDaySleep> queryWrapper = Wrappers.lambdaQuery(WatchStatUserInfoDaySleep.class)
|
||||
.select(WatchStatUserInfoDaySleep::getId,
|
||||
WatchStatUserInfoDaySleep::getDeepSleepTimes,
|
||||
@@ -893,13 +1080,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
WatchStatUserInfoDaySleep::getDeepLightTimes,
|
||||
WatchStatUserInfoDaySleep::getDataDate)
|
||||
.eq(WatchStatUserInfoDaySleep::getUserId, userId)
|
||||
.orderByDesc(WatchStatUserInfoDaySleep::getDataDate)
|
||||
// 传了日期则精确匹配,否则取最新一条
|
||||
.eq(date != null, WatchStatUserInfoDaySleep::getDataDate, date)
|
||||
.orderByDesc(date == null, WatchStatUserInfoDaySleep::getDataDate)
|
||||
.last("limit 1");
|
||||
|
||||
WatchStatUserInfoDaySleep data = watchStatUserInfoDaySleepMapper.selectOne(queryWrapper);
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
return new UserStat();
|
||||
}
|
||||
|
||||
UserStat stat = new UserStat();
|
||||
@@ -915,15 +1104,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
return stat;
|
||||
}
|
||||
|
||||
private UserStat handleBodyTemperature(String userId) {
|
||||
private UserStat handleBodyTemperature(String userId, Date date) {
|
||||
int type = 5;
|
||||
int scale = 1;
|
||||
// 最新数据
|
||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||
if (userStat == null) {
|
||||
return null;
|
||||
return new UserStat();
|
||||
}
|
||||
LambdaQueryWrapper<WatchStatUserInfoDayTemp> queryWrapper = getQueryWrapper(userId);
|
||||
LambdaQueryWrapper<WatchStatUserInfoDayTemp> queryWrapper = getQueryWrapper(userId, date);
|
||||
WatchStatUserInfoDayTemp data = watchStatUserInfoDayTempMapper.selectOne(queryWrapper);
|
||||
if (data == null) {
|
||||
return userStat;
|
||||
@@ -933,15 +1122,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
return userStat;
|
||||
}
|
||||
|
||||
private UserStat handleStress(String userId) {
|
||||
private UserStat handleStress(String userId, Date date) {
|
||||
int type = 3;
|
||||
int scale = 0;
|
||||
// 最新数据
|
||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||
if (userStat == null) {
|
||||
return null;
|
||||
return new UserStat();
|
||||
}
|
||||
LambdaQueryWrapper<WatchStatUserInfoDayStress> queryWrapper = getQueryWrapper(userId);
|
||||
LambdaQueryWrapper<WatchStatUserInfoDayStress> queryWrapper = getQueryWrapper(userId, date);
|
||||
WatchStatUserInfoDayStress data = watchStatUserInfoDayStressMapper.selectOne(queryWrapper);
|
||||
if (data == null) {
|
||||
return userStat;
|
||||
@@ -951,15 +1140,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
return userStat;
|
||||
}
|
||||
|
||||
private UserStat handleBloodOxygen(String userId) {
|
||||
private UserStat handleBloodOxygen(String userId, Date date) {
|
||||
int type = 2;
|
||||
int scale = 0;
|
||||
// 最新数据
|
||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||
if (userStat == null) {
|
||||
return null;
|
||||
return new UserStat();
|
||||
}
|
||||
LambdaQueryWrapper<WatchStatUserInfoDaySpo2> queryWrapper = getQueryWrapper(userId);
|
||||
LambdaQueryWrapper<WatchStatUserInfoDaySpo2> queryWrapper = getQueryWrapper(userId, date);
|
||||
WatchStatUserInfoDaySpo2 data = watchStatUserInfoDaySpo2Mapper.selectOne(queryWrapper);
|
||||
if (data == null) {
|
||||
return userStat;
|
||||
@@ -969,15 +1158,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
return userStat;
|
||||
}
|
||||
|
||||
private UserStat handleHeartRate(String userId) {
|
||||
private UserStat handleHeartRate(String userId, Date date) {
|
||||
int type = 1;
|
||||
int scale = 0;
|
||||
// 最新数据
|
||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||
if (userStat == null) {
|
||||
return null;
|
||||
return new UserStat();
|
||||
}
|
||||
LambdaQueryWrapper<WatchStatUserInfoDayHeartRate> queryWrapper = getQueryWrapper(userId);
|
||||
LambdaQueryWrapper<WatchStatUserInfoDayHeartRate> queryWrapper = getQueryWrapper(userId, date);
|
||||
WatchStatUserInfoDayHeartRate data = watchStatUserInfoDayHeartRateMapper.selectOne(queryWrapper);
|
||||
if (data == null) {
|
||||
return userStat;
|
||||
@@ -1029,10 +1218,12 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
return stat;
|
||||
}
|
||||
|
||||
private <T> LambdaQueryWrapper<T> getQueryWrapper(String userId) {
|
||||
private <T> LambdaQueryWrapper<T> getQueryWrapper(String userId, Date date) {
|
||||
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("user_id", userId)
|
||||
.orderByDesc("data_date")
|
||||
// 传了日期则精确匹配,否则取最新一条
|
||||
.eq(date != null, "data_date", date)
|
||||
.orderByDesc(date == null, "data_date")
|
||||
.last("limit 1");
|
||||
return queryWrapper.lambda();
|
||||
}
|
||||
@@ -1051,4 +1242,17 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||
return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置统计数据类型标识,stat 为 null 时直接返回 null
|
||||
*
|
||||
* @param stat UserStat 对象
|
||||
* @param type 类型编码:0=心率 1=血氧 2=压力 3=体温 4=睡眠
|
||||
*/
|
||||
private UserStat setType(UserStat stat, String type) {
|
||||
if (stat != null) {
|
||||
stat.setType(type);
|
||||
}
|
||||
return stat;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -336,8 +336,9 @@ public class AsyncImportService {
|
||||
updatedWatchDevice.setBindDate(watchDevice.getBindDate()==null?new Date():watchDevice.getBindDate());
|
||||
updatedWatchDevice.setUpdateDate(new Date());
|
||||
updatedWatchDevice.setUpdateBy(sysUser.getId());
|
||||
updatedWatchDevice.setDeptId(device.getDeptId());
|
||||
updatedWatchDevice.setOrgCode(device.getOrgCode());
|
||||
// 这里修改成以用户的部门为准,更新手表的所属部门
|
||||
updatedWatchDevice.setDeptId(sysCache.getDepartIdByOrgCode(user.getOrgCode()));
|
||||
updatedWatchDevice.setOrgCode(user.getOrgCode());
|
||||
updateList.add(updatedWatchDevice);
|
||||
}
|
||||
info1.setHandleMsg(index + "/" + count);
|
||||
|
||||
+15
@@ -38,6 +38,21 @@ public class WatchApiController {
|
||||
return watchMonitorDataService.notice(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新告警事件GPS位置信息接口
|
||||
* 根据 watchNo + eventId 联合定位已有告警数据;
|
||||
* 仅更新GPS相关字段,不推送大屏与短信;
|
||||
* 内部会调用高德接口进行GPS84→GCJ02坐标转换与地址反查。
|
||||
*
|
||||
* @param json 入参(watchNo、eventId、lon、lat、address、locateTime、gpsErrorMsg)
|
||||
* @return 处理结果
|
||||
*/
|
||||
@PostMapping("/updateMonitorGps")
|
||||
@Operation(summary = "更新告警事件GPS位置信息")
|
||||
public AjaxJson updateMonitorGps(@RequestBody JSONObject json) {
|
||||
return watchMonitorDataService.updateMonitorGps(json);
|
||||
}
|
||||
|
||||
@GetMapping("/setData")
|
||||
public AjaxJson setData() {
|
||||
AjaxJson ajaxJson = new AjaxJson();
|
||||
|
||||
+2
-4
@@ -11,10 +11,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -327,4 +324,5 @@ public class WatchController {
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
-7
@@ -185,6 +185,12 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
||||
return watchDeviceService.bindUser(dto);
|
||||
}
|
||||
|
||||
@Operation(summary = "移动端绑定设备(手表)", description = "移动端绑定设备(手表)")
|
||||
@GetMapping("/app/bindDevice")
|
||||
public Result bindDevice(@RequestParam(name = "watchNo") String watchNo) throws ParseException {
|
||||
return watchDeviceService.bindDevice(watchNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑用户
|
||||
*
|
||||
@@ -194,10 +200,18 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
||||
@Operation(summary = "解绑用户", description = "解绑用户")
|
||||
@PostMapping(value = "/unbundleUser")
|
||||
@RequiresPermissions("watch:watch_device:unbindUser")
|
||||
public Result unbundleUser(@RequestBody() BindUserDTO dto) throws ParseException {
|
||||
public Result unbundleUser(@RequestBody() BindUserDTO dto) {
|
||||
return watchDeviceService.unbundleUser(dto);
|
||||
}
|
||||
|
||||
@Operation(summary = "移动端解绑设备(手表)", description = "移动端解绑设备(手表)")
|
||||
@GetMapping("/app/unbundleDevice")
|
||||
public Result unbundleDevice(@RequestParam(name = "watchNo") String watchNo) {
|
||||
//移动端暂不允许解绑手表
|
||||
return Result.error("请联系健康管理员解绑");
|
||||
// return watchDeviceService.unbundleDevice(watchNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上传频次
|
||||
*
|
||||
@@ -343,6 +357,10 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
WatchDevice watchDevice = watchDeviceService.getById(id);
|
||||
// 已绑定用户的手表不允许删除
|
||||
if (StringUtils.hasLength(watchDevice.getBindUserId())) {
|
||||
return Result.error("该手表已绑定用户,请先解绑后再删除!");
|
||||
}
|
||||
watchDeviceService.removeById(id);
|
||||
// 删除开关
|
||||
LambdaQueryWrapper<WatchDeviceSwitch> queryWrapper = new LambdaQueryWrapper<>();
|
||||
@@ -422,6 +440,8 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
||||
}
|
||||
if (!StringUtils.hasLength(deptId) || "undefined".equals(deptId)) {
|
||||
throw new JeecgBootException("部门不能为空");
|
||||
//可为空,空则算入新疆油田公司
|
||||
// deptId = sysCache.getDepartIdByOrgCode("A01");
|
||||
}
|
||||
String sheetName = "设备导入";
|
||||
String fileName = sheetName + "_" + DateUtils.getDate("yyyyMMddHHmmss");
|
||||
@@ -687,12 +707,12 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
||||
@Operation(summary = "获取时间范围的走跑数据", description = "获取时间范围的走跑数据")
|
||||
@PostMapping("getSdcDataMapByUserIds")
|
||||
public Map<String,List<WatchStatUserInfoDaySdc>> getSdcDataMapByUserIds(@RequestBody List<String> userIds,
|
||||
@RequestParam(name = "startTime")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime,
|
||||
@RequestParam(name = "endTime")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime) {
|
||||
@RequestParam(name = "startTime")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime,
|
||||
@RequestParam(name = "endTime")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime) {
|
||||
return watchDeviceService.getSdcDataMapByUserIds(userIds, startTime, endTime);
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -43,12 +43,16 @@ public interface IWatchDeviceService extends IService<WatchDevice> {
|
||||
*/
|
||||
Result bindUser(BindUserDTO dto) throws ParseException;
|
||||
|
||||
Result bindDevice(String watchNo) throws ParseException;
|
||||
|
||||
/**
|
||||
* 绑定用户
|
||||
* @param dto
|
||||
* @return
|
||||
*/
|
||||
Result unbundleUser(BindUserDTO dto) throws ParseException;
|
||||
Result unbundleUser(BindUserDTO dto);
|
||||
|
||||
Result unbundleDevice(String watchNo);
|
||||
|
||||
/**
|
||||
* 工具开关
|
||||
|
||||
+11
@@ -38,6 +38,17 @@ public interface IWatchMonitorDataService extends IService<WatchMonitorData> {
|
||||
*/
|
||||
AjaxJson notice(JSONObject jsonObject);
|
||||
|
||||
/**
|
||||
* 更新告警事件GPS位置信息
|
||||
* 根据 watchNo + eventId 联合定位已有告警数据;
|
||||
* 缺失任一定位字段则忽略;已存在GPS信息则忽略;
|
||||
* 仅更新数据,不推送大屏与短信。
|
||||
*
|
||||
* @param jsonObject 入参(含 watchNo、eventId、lon、lat、address、locateTime、gpsErrorMsg 等)
|
||||
* @return 处理结果
|
||||
*/
|
||||
AjaxJson updateMonitorGps(JSONObject jsonObject);
|
||||
|
||||
/**
|
||||
* 处理历史数据 orgCode为空的问题
|
||||
* @return
|
||||
|
||||
+105
-4
@@ -595,11 +595,32 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
filter.setOrgCodeLeaf("A99A99A99");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门统计数据(分页)
|
||||
*
|
||||
* @param filter 查询过滤条件,包含部门编码、时间范围、分页参数等
|
||||
* @return 分页的部门统计数据列表
|
||||
*/
|
||||
@Override
|
||||
public IPage<DeptStatData> getDeptData(DepartStatsFilter filter) {
|
||||
return getDeptData(filter, true).pagedData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门统计数据的核心实现
|
||||
* <p>
|
||||
* 执行流程:
|
||||
* 1. 权限校验,补全过滤条件
|
||||
* 2. 查询各部门设备总数 Map 和绑定/佩戴数 Map
|
||||
* 3. 根据过滤条件确定最终需要展示的部门列表
|
||||
* 4. 查询各部门异常预警数量
|
||||
* 5. 按需分页,将部门列表转换为统计 DTO
|
||||
* </p>
|
||||
*
|
||||
* @param filter 查询过滤条件
|
||||
* @param paged 是否需要分页,true=分页查询,false=全量查询(用于导出等场景)
|
||||
* @return 包含统计数据和中间结果的传输对象
|
||||
*/
|
||||
private DepartStatTransfer getDeptData(DepartStatsFilter filter, boolean paged) {
|
||||
authCheck(filter);
|
||||
DepartStatTransfer transfer = new DepartStatTransfer()
|
||||
@@ -639,12 +660,29 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return transfer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个部门实体转换为统计 DTO
|
||||
* <p>
|
||||
* orgCode 编码规则:3位一层,如 A01=一级,A01A01=二级,A01A01A01=三级
|
||||
* 区分三种视图:
|
||||
* - 一级部门(orgCode 长度为 3):展示自身信息,叶子名称置为 "-",取自身设备总数
|
||||
* - 二级部门(orgCode 长度为 6):同一级,展示自身信息,叶子名称置为 "-",取自身设备总数
|
||||
* - 三级部门(orgCode 长度为 9):展示所属上级名称 + 自身名称,绑定/佩戴数取自身数据
|
||||
* 当 allSecond=true(全量视图)时,绑定/佩戴数始终取各部门自身数据
|
||||
* </p>
|
||||
*
|
||||
* @param depart 部门实体
|
||||
* @param transfer 包含所有统计中间数据的传输对象
|
||||
* @return 填充完毕的部门统计 DTO
|
||||
*/
|
||||
private DeptStatData transferData(SysDepart depart, DepartStatTransfer transfer) {
|
||||
DeptStatData deptStatData = new DeptStatData();
|
||||
String orgCode = null != depart ? depart.getOrgCode() : null;
|
||||
boolean allSecond = StrUtil.isEmpty(transfer.filter().getOrgCode());
|
||||
boolean isFirst = StrUtil.isNotEmpty(orgCode) && orgCode.length() == 3;
|
||||
boolean isSecond = StrUtil.isNotEmpty(orgCode) && orgCode.length() == 6;
|
||||
if (isSecond) {
|
||||
if (isFirst || isSecond) {
|
||||
// 一级/二级部门行:展示自身信息,叶子名称占位
|
||||
deptStatData.setOrgCode(orgCode);
|
||||
deptStatData.setOrgCodeLeaf(orgCode);
|
||||
deptStatData.setOrgCodeName(depart.getDepartName());
|
||||
@@ -655,6 +693,7 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
.getDeviceCount()
|
||||
);
|
||||
} else {
|
||||
// 三级部门行:展示所属上级名称 + 自身名称
|
||||
deptStatData.setOrgCode(Optional.ofNullable(transfer.secondDepart()).map(SysDepart::getOrgCode).orElse(""));
|
||||
deptStatData.setOrgCodeLeaf(orgCode);
|
||||
deptStatData.setOrgCodeName(Optional.ofNullable(transfer.secondDepart()).map(SysDepart::getDepartName).orElse(""));
|
||||
@@ -662,15 +701,21 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
}
|
||||
fillWatchNums(
|
||||
deptStatData,
|
||||
(!allSecond && isSecond)
|
||||
(!allSecond && (isFirst || isSecond))
|
||||
?
|
||||
transfer.secondDepartBindAndWearNum()
|
||||
:
|
||||
transfer.departBindAndWearNumMap().getOrDefault(orgCode, new DepartDeviceNum())
|
||||
);
|
||||
// 未分配设备数 = 入库设备数 - 已分配设备数,仅一、二级部门有入库数据
|
||||
if (deptStatData.getTotalAssignedDeviceNums() != null) {
|
||||
deptStatData.setUndistributedDeviceNums(
|
||||
deptStatData.getTotalAssignedDeviceNums() - deptStatData.getAssignedDeviceNums()
|
||||
);
|
||||
}
|
||||
fillWarningNums(
|
||||
deptStatData,
|
||||
(!allSecond && isSecond)
|
||||
(!allSecond && (isFirst || isSecond))
|
||||
?
|
||||
transfer.secondDepartAbnormalCount()
|
||||
:
|
||||
@@ -679,12 +724,30 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return deptStatData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充手表绑定与佩戴相关数量
|
||||
* <p>
|
||||
* assignedDeviceNums = 已绑定设备数
|
||||
* assignedUserNums = 已佩戴人数
|
||||
* undistributedUserNums = 已绑定但未佩戴数(bindCount - wearCount)
|
||||
* </p>
|
||||
*/
|
||||
private void fillWatchNums(DeptStatData deptStatData, DepartDeviceNum departDeviceNum) {
|
||||
deptStatData.setAssignedDeviceNums(Optional.ofNullable(departDeviceNum.getBindCount()).orElse(0));
|
||||
deptStatData.setAssignedUserNums(Optional.ofNullable(departDeviceNum.getWearCount()).orElse(0));
|
||||
deptStatData.setUndistributedUserNums(deptStatData.getAssignedDeviceNums() - deptStatData.getAssignedUserNums());
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充各健康指标的预警数量
|
||||
* <p>
|
||||
* 从 abnormalCountMap 中按事件类型取值:
|
||||
* stress → 压力预警数
|
||||
* heart_rate → 心率预警数
|
||||
* spo2 → 血氧预警数
|
||||
* temperature_anomalies → 体温预警数
|
||||
* </p>
|
||||
*/
|
||||
private void fillWarningNums(DeptStatData deptStatData, Map<String, AbnormalCount> abnormalCountMap) {
|
||||
deptStatData.setStressNums(Optional.ofNullable(abnormalCountMap.get("stress")).map(AbnormalCount::getWarningCount).orElse(0));
|
||||
deptStatData.setHeartRateNums(Optional.ofNullable(abnormalCountMap.get("heart_rate")).map(AbnormalCount::getWarningCount).orElse(0));
|
||||
@@ -722,6 +785,10 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return sysCache.getDepartByOrgCode(filter.getOrgCodeLeaf());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询各部门的设备总数 Map(orgCode → 设备数)
|
||||
* 当过滤条件指定了三级部门(orgCodeLeaf 不为空)时,设备总数无需展示,直接返回空 Map
|
||||
*/
|
||||
private Map<String, DepartDeviceNum> getDepartDeviceNumMap(DepartStatsFilter filter) {
|
||||
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf())) {
|
||||
return Collections.emptyMap();
|
||||
@@ -732,6 +799,9 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
.collect(Collectors.toMap(DepartDeviceNum::getOrgCode, Function.identity()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询各部门的设备绑定数与佩戴数 Map(orgCode → 绑定/佩戴数)
|
||||
*/
|
||||
private Map<String, DepartDeviceNum> getDepartBindAndWearNumMap(DepartStatsFilter filter) {
|
||||
return watchStatisticsMapper.deviceBindAndWearNums(filter)
|
||||
.stream()
|
||||
@@ -739,6 +809,10 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
.collect(Collectors.toMap(DepartDeviceNum::getOrgCode, Function.identity()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询各部门各事件类型的异常预警数量,结构为双层 Map:
|
||||
* orgCode → (eventType → AbnormalCount)
|
||||
*/
|
||||
private Map<String, Map<String, AbnormalCount>> getDepartAbnormalCountMap(DepartStatsFilter filter) {
|
||||
return watchStatisticsMapper.abnormalWarningCount(filter)
|
||||
.stream()
|
||||
@@ -746,6 +820,12 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
.collect(Collectors.groupingBy(AbnormalCount::getOrgCode, Collectors.toMap(AbnormalCount::getEventType, Function.identity())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总二级部门下所有三级部门的绑定/佩戴数,用于二级部门行的数据展示
|
||||
* <p>
|
||||
* 仅在"指定了二级部门但未指定三级部门"时有效,其他情况返回 null
|
||||
* </p>
|
||||
*/
|
||||
private DepartDeviceNum getSecondDepartBindAndWearNum(DepartStatsFilter filter,
|
||||
Map<String, DepartDeviceNum> departBindAndWearNumMap) {
|
||||
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf()) || StrUtil.isEmpty(filter.getOrgCode())) {
|
||||
@@ -764,6 +844,13 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return n1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总二级部门下所有三级部门的异常预警数,用于二级部门行的数据展示
|
||||
* <p>
|
||||
* 仅在"指定了二级部门但未指定三级部门"时有效,其他情况返回 null
|
||||
* 合并时按 eventType 累加 warningCount
|
||||
* </p>
|
||||
*/
|
||||
private Map<String, AbnormalCount> getSecondDepartAbnormalCount(DepartStatsFilter filter,
|
||||
Map<String, Map<String, AbnormalCount>> departAbnormalCountMap) {
|
||||
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf()) || StrUtil.isEmpty(filter.getOrgCode())) {
|
||||
@@ -778,6 +865,14 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return n1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据过滤条件确定最终需要展示的部门列表,分三种情况:
|
||||
* <ul>
|
||||
* <li>指定三级部门(orgCodeLeaf 不为空):仅返回该三级部门(无绑定数据则返回空)</li>
|
||||
* <li>指定二级部门(orgCode 不为空):返回该部门本身 + 其下有绑定数据的子部门,按层级(orgCode 长度)升序后再按排序字段排序</li>
|
||||
* <li>全量(orgCode 为空):返回所有有设备数据的部门,同样按层级升序后再按排序字段排序,确保一级部门排在最前</li>
|
||||
* </ul>
|
||||
*/
|
||||
private List<SysDepart> getFinalDeparts(DepartStatsFilter filter,
|
||||
Map<String, DepartDeviceNum> departDeviceNumMap,
|
||||
Map<String, DepartDeviceNum> departBindAndWearNumMap) {
|
||||
@@ -804,7 +899,10 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return departDeviceNumMap.keySet()
|
||||
.stream()
|
||||
.map(sysBaseAPI::getDepartIdsByOrgCodeNew)
|
||||
.sorted(this::departComparator)
|
||||
.sorted(
|
||||
Comparator.<SysDepart>comparingInt(sysDepart -> sysDepart.getOrgCode().length())
|
||||
.thenComparing(this::departComparator)
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -825,6 +923,9 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
||||
return order1.compareTo(order2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对内存中的部门列表执行分页截取
|
||||
*/
|
||||
private List<SysDepart> getPagedDeparts(List<SysDepart> sysDeparts, DepartStatsFilter filter) {
|
||||
return getSubList(sysDeparts, filter.getPageNo(), filter.getPageSize());
|
||||
}
|
||||
|
||||
+3
@@ -3,6 +3,7 @@ package com.renkang.watch.service.impl;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@@ -87,6 +88,7 @@ public class WatchBindHisServiceImpl extends ServiceImpl<WatchBindHisMapper, Wat
|
||||
@Override
|
||||
public IPage<WatchBindHisVo> queryPageList(IPage<WatchBindHisVo> page, WatchBindHisDTO dto) {
|
||||
if (StringUtils.hasLength(dto.getBindRealName())) {
|
||||
dto.setBindRealName(HtmlUtil.unescape(dto.getBindRealName()));
|
||||
List<String> userIdsByDepartAndUsername = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", "");
|
||||
if (CollectionUtil.isEmpty(userIdsByDepartAndUsername)) {
|
||||
return new Page<>();
|
||||
@@ -372,6 +374,7 @@ public class WatchBindHisServiceImpl extends ServiceImpl<WatchBindHisMapper, Wat
|
||||
String orgCode = StrUtil.isNotBlank(dto.getThirdDepart())?dto.getThirdDepart() : dto.getSecondDepart();
|
||||
List<String> userIds = new ArrayList<>();
|
||||
if (StrUtil.isNotBlank(dto.getBindRealName())) {
|
||||
dto.setBindRealName(HtmlUtil.unescape(dto.getBindRealName()));
|
||||
userIds = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", "");
|
||||
if (CollectionUtil.isEmpty(userIds)) {
|
||||
return new Page<>();
|
||||
|
||||
+4
@@ -7,6 +7,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -104,6 +105,9 @@ public class WatchDataServiceImpl extends ServiceImpl<WatchDataMapper, WatchData
|
||||
}
|
||||
|
||||
String sql = QueryGenerator.installAuthJdbc(WatchData.class);
|
||||
if(StrUtil.isNotBlank(watchData.getUsername())){
|
||||
watchData.setUsername(HtmlUtil.unescape(watchData.getUsername()));
|
||||
}
|
||||
IPage<WatchData> pageList = watchDataMapper.pageCustom(page, watchData, sql);
|
||||
if (CollectionUtil.isNotEmpty(pageList.getRecords())) {
|
||||
List<LoginUserNew> loginUserNews = null;
|
||||
|
||||
+67
-15
@@ -7,6 +7,7 @@ import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
@@ -167,6 +168,9 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
@Override
|
||||
public IPage<WatchDeviceVo> queryPageList(IPage<WatchDeviceVo> page, WatchDeviceDTO dto) {
|
||||
if (StrUtil.isNotBlank(dto.getBindRealName()) || StrUtil.isNotBlank(dto.getWorkNo())) {
|
||||
if(StrUtil.isNotBlank(dto.getBindRealName())){
|
||||
dto.setBindRealName(HtmlUtil.unescape(dto.getBindRealName()));
|
||||
}
|
||||
List<String> userIds = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", dto.getWorkNo());
|
||||
if (CollectionUtil.isEmpty(userIds)) {
|
||||
return new Page<>();
|
||||
@@ -291,10 +295,20 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
}
|
||||
WatchDevice watchDevice = getDeviceByWatchNo(dto.getWatchNo());
|
||||
if (watchDevice == null) {
|
||||
return Result.error("该工具不存在");
|
||||
return Result.error("该监测工具不存在");
|
||||
}
|
||||
|
||||
// 新增手表只能绑定给本单位的人
|
||||
LoginUser userById = sysBaseAPI.getUserById(dto.getBindUserId());
|
||||
|
||||
if (userById.getOrgCode() == null || watchDevice.getOrgCode() == null ||
|
||||
userById.getOrgCode().length() < 6 || watchDevice.getOrgCode().length() < 6 ||
|
||||
!userById.getOrgCode().substring(0, 6).equals(watchDevice.getOrgCode().substring(0, 6))) {
|
||||
return Result.error("手表只能绑定给本单位人员");
|
||||
}
|
||||
|
||||
if (isDeviceAlreadyBound(watchDevice, dto)) {
|
||||
return Result.error("该设备已绑定该员工");
|
||||
return Result.error("该设备已被绑定");
|
||||
}
|
||||
// 查询员工是否有绑定
|
||||
List<WatchDevice> deviceByUserIdList = getDeviceByUserId(dto.getBindUserId());
|
||||
@@ -317,14 +331,20 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
}
|
||||
// 更新工具信息
|
||||
watchDevice.setBindDate(dto.getBindDate());
|
||||
// 根据员工更新手表单位信息(如果单位编码大于6位,取6位)
|
||||
String orgCode = userById.getOrgCode();
|
||||
if(StrUtil.isNotBlank(orgCode) && orgCode.length() > 6){
|
||||
orgCode = orgCode.substring(0, 6);
|
||||
}
|
||||
watchDevice.setOrgCode(orgCode);
|
||||
watchDevice.setDeptId(sysCache.getDepartIdByOrgCode(orgCode));
|
||||
bindUserToDevice(watchDevice, dto);
|
||||
if (StrUtil.isNotBlank(dto.getBindUserId())) {
|
||||
// 初始化开关数据
|
||||
unbindUserAndInitDeviceSwitch(dto);
|
||||
}
|
||||
LoginUser userById = sysBaseAPI.getUserById(dto.getBindUserId());
|
||||
// 此处修改 只有是职工的是才赋值单位信息
|
||||
if (dto.getIsEmployee()){
|
||||
if (dto.getIsEmployee()) {
|
||||
SysDepart inDepart = sysCache.getDepartByOrgCode(userById.getOrgCode());
|
||||
dto.setOrgCode(inDepart.getOrgCode());
|
||||
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(userById.getOrgCode()));
|
||||
@@ -350,6 +370,17 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
return Result.ok("操作成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result bindDevice(String watchNo) throws ParseException {
|
||||
BindUserDTO dto = new BindUserDTO();
|
||||
dto.setBindUserId(GlobalUtils.getLoginUser().getId());
|
||||
dto.setWatchNo(watchNo);
|
||||
dto.setBindDate(new Date());
|
||||
dto.setChangeUser(false);
|
||||
return bindUser(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户拉取数据
|
||||
*
|
||||
@@ -385,7 +416,7 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result unbundleUser(BindUserDTO dto) throws ParseException {
|
||||
public Result unbundleUser(BindUserDTO dto){
|
||||
WatchDevice watchDevice = getDeviceByWatchNo(dto.getWatchNo());
|
||||
if (watchDevice == null) {
|
||||
return Result.error("该设备不存在");
|
||||
@@ -409,6 +440,17 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
return Result.ok("操作成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result unbundleDevice(String watchNo){
|
||||
// BindUserDTO dto = new BindUserDTO();
|
||||
// dto.setBindUserId(GlobalUtils.getLoginUser().getId());
|
||||
// dto.setWatchNo(watchNo);
|
||||
// dto.setBindDate(new Date());
|
||||
// return unbundleUser(dto);
|
||||
// 2026.04.09 说 不能从移动端解绑了
|
||||
return Result.error("请联系管理员解绑");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手表编号获取手表信息
|
||||
@@ -456,6 +498,13 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
updateWrapper.eq("id", watchDevice.getId());
|
||||
updateWrapper.set("bind_user_id", dto.getBindUserId());
|
||||
updateWrapper.set("bind_date", watchDevice.getBindDate());
|
||||
if(StrUtil.isNotBlank(watchDevice.getOrgCode())){
|
||||
updateWrapper.set("org_code", watchDevice.getOrgCode());
|
||||
}
|
||||
if (StrUtil.isNotBlank(watchDevice.getDeptId())){
|
||||
updateWrapper.set("dept_id", watchDevice.getDeptId());
|
||||
}
|
||||
|
||||
this.update(updateWrapper);
|
||||
}
|
||||
|
||||
@@ -472,15 +521,18 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
switchUpdateWrapper.eq(WatchDeviceSwitch::getWatchNo, dto.getWatchNo());
|
||||
watchDeviceSwitchService.update(switchUpdateWrapper);
|
||||
String[] wdTypes = {"heart_rate", "spo2", "stress", "temperature"};
|
||||
for (int i = 0; i < wdTypes.length; i++) {
|
||||
WarnSwitch warnSwitch = new WarnSwitch();
|
||||
warnSwitch.setWatchNo(dto.getWatchNo());
|
||||
warnSwitch.setWdType(wdTypes[i]);
|
||||
warnSwitch.setSwitchFlag("1");
|
||||
dataCenterExecutor.saveWarnSwitch(warnSwitch);
|
||||
}
|
||||
//通知更新
|
||||
dataCenterExecutor.cmdUpdateSwitch(dto.getWatchNo());
|
||||
// 异步调用外部系统,防止外部系统异常导致功能异常,解绑这里非必要
|
||||
asyncTaskExecutor.execute(() -> {
|
||||
for (int i = 0; i < wdTypes.length; i++) {
|
||||
WarnSwitch warnSwitch = new WarnSwitch();
|
||||
warnSwitch.setWatchNo(dto.getWatchNo());
|
||||
warnSwitch.setWdType(wdTypes[i]);
|
||||
warnSwitch.setSwitchFlag("1");
|
||||
dataCenterExecutor.saveWarnSwitch(warnSwitch);
|
||||
}
|
||||
//通知更新
|
||||
dataCenterExecutor.cmdUpdateSwitch(dto.getWatchNo());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2209,7 +2261,7 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
||||
cloudResultData.add(user);
|
||||
// 判断是否绑定手表
|
||||
List<WatchDevice> watchDeviceList = watchDeviceMapper.selectList(new LambdaQueryWrapper<WatchDevice>()
|
||||
.in(WatchDevice::getBindUserId, cloudResultData.stream().map(FamilyMembersVO::getFamilyMembersId).collect(Collectors.toList())));
|
||||
.in(WatchDevice::getBindUserId, cloudResultData.stream().map(FamilyMembersVO::getFamilyMembersId).collect(Collectors.toList())));
|
||||
if (CollUtil.isEmpty(watchDeviceList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
+99
@@ -265,6 +265,102 @@ public class WatchMonitorDataServiceImpl extends ServiceImpl<WatchMonitorDataMap
|
||||
return ajaxJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新告警事件GPS位置信息
|
||||
* 根据 watchNo + eventId 联合定位已有告警数据;
|
||||
* watchNo 或 eventId 任一缺失则忽略;
|
||||
* 已存在 GPS 信息则忽略本次更新;
|
||||
* 参考 createMonitorData 进行 GPS84 → GCJ02 坐标转换 + 高德地址反查;
|
||||
* 仅更新数据,不推送大屏与短信。
|
||||
*
|
||||
* @param json 入参(含 watchNo、eventId、lon、lat、address、locateTime、gpsErrorMsg 等)
|
||||
* @return 处理结果
|
||||
*/
|
||||
@Override
|
||||
public AjaxJson updateMonitorGps(JSONObject json) {
|
||||
GlobalUtils.setHttpFeignToken();
|
||||
|
||||
AjaxJson ajaxJson = new AjaxJson();
|
||||
logger.info("接收更新告警事件GPS信息=={}", json.toJSONString());
|
||||
|
||||
WatchNotifyVo data = json.toJavaObject(WatchNotifyVo.class);
|
||||
|
||||
// watchNo 或 eventId 任一缺失则忽略
|
||||
if (StrUtil.isBlank(data.getWatchNo()) || StrUtil.isBlank(data.getEventId())) {
|
||||
logger.warn("更新GPS信息缺失关键定位字段,忽略本次请求 watchNo={}, eventId={}", data.getWatchNo(), data.getEventId());
|
||||
ajaxJson.setSuccess(true);
|
||||
return ajaxJson;
|
||||
}
|
||||
|
||||
// watchNo + eventId 联合定位已有告警数据
|
||||
LambdaQueryWrapper<WatchMonitorData> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(WatchMonitorData::getWatchNo, data.getWatchNo())
|
||||
.eq(WatchMonitorData::getEventId, data.getEventId());
|
||||
List<WatchMonitorData> existList = this.list(queryWrapper);
|
||||
if (CollUtil.isEmpty(existList)) {
|
||||
logger.warn("更新GPS信息失败,未匹配到告警记录 watchNo={}, eventId={}", data.getWatchNo(), data.getEventId());
|
||||
ajaxJson.setSuccess(true);
|
||||
return ajaxJson;
|
||||
}
|
||||
|
||||
// 已存在GPS信息则忽略本次更新,避免覆盖已有有效坐标
|
||||
boolean hasGps = existList.stream().anyMatch(m -> m.getLon() != null && m.getLat() != null);
|
||||
if (hasGps) {
|
||||
logger.info("告警记录已有GPS信息,忽略本次更新 watchNo={}, eventId={}", data.getWatchNo(), data.getEventId());
|
||||
ajaxJson.setSuccess(true);
|
||||
return ajaxJson;
|
||||
}
|
||||
|
||||
// 计算高德坐标与高德地址(参考 createMonitorData 逻辑)
|
||||
WatchMonitorData updateEntity = new WatchMonitorData();
|
||||
updateEntity.setLon(data.getLon());
|
||||
updateEntity.setLat(data.getLat());
|
||||
updateEntity.setAddress(data.getAddress());
|
||||
updateEntity.setGpsErrorMsg(data.getGpsErrorMsg());
|
||||
updateEntity.setLocateTime(data.getLocateTime());
|
||||
|
||||
if (Stream.of(data.getLon(), data.getLat()).allMatch(value -> ObjUtil.isNotEmpty(value) && value != 0)) {
|
||||
Gps gps = GPS84ToGCJ02(data.getLon(), data.getLat());
|
||||
if (null != gps) {
|
||||
// 写入高德坐标
|
||||
updateEntity.setLonGd(BigDecimal.valueOf(gps.getLongitude()));
|
||||
updateEntity.setLatGd(BigDecimal.valueOf(gps.getLatitude()));
|
||||
// 调用高德接口获取地址信息
|
||||
AmapResultVo amapResultVo = amapService.getFormattedAddress(gps.getLongitude(), gps.getLatitude(), 5000);
|
||||
if (amapResultVo.isSuccess()) {
|
||||
updateEntity.setAddressGd(amapResultVo.getAddress());
|
||||
updateEntity.setAddress(amapResultVo.getAddress());
|
||||
} else {
|
||||
String originError = StrUtil.isBlank(updateEntity.getGpsErrorMsg()) ? "" : updateEntity.getGpsErrorMsg() + ";";
|
||||
updateEntity.setGpsErrorMsg(originError + amapResultVo.getErrorMsg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 仅更新 GPS 相关字段,不推送大屏、不发短信
|
||||
boolean updated = this.lambdaUpdate()
|
||||
.eq(WatchMonitorData::getWatchNo, data.getWatchNo())
|
||||
.eq(WatchMonitorData::getEventId, data.getEventId())
|
||||
.set(WatchMonitorData::getLon, updateEntity.getLon())
|
||||
.set(WatchMonitorData::getLat, updateEntity.getLat())
|
||||
.set(WatchMonitorData::getAddress, updateEntity.getAddress())
|
||||
.set(WatchMonitorData::getLonGd, updateEntity.getLonGd())
|
||||
.set(WatchMonitorData::getLatGd, updateEntity.getLatGd())
|
||||
.set(WatchMonitorData::getAddressGd, updateEntity.getAddressGd())
|
||||
.set(WatchMonitorData::getGpsErrorMsg, updateEntity.getGpsErrorMsg())
|
||||
.set(WatchMonitorData::getLocateTime, updateEntity.getLocateTime())
|
||||
.update();
|
||||
|
||||
if (!updated) {
|
||||
logger.warn("更新GPS信息失败 watchNo={}, eventId={}", data.getWatchNo(), data.getEventId());
|
||||
} else {
|
||||
logger.info("更新GPS信息成功 watchNo={}, eventId={}, 共更新{}条", data.getWatchNo(), data.getEventId(), existList.size());
|
||||
}
|
||||
|
||||
ajaxJson.setSuccess(true);
|
||||
return ajaxJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建异常数据
|
||||
*
|
||||
@@ -272,13 +368,16 @@ public class WatchMonitorDataServiceImpl extends ServiceImpl<WatchMonitorDataMap
|
||||
* @return 异常数据
|
||||
*/
|
||||
private WatchMonitorData createMonitorData(WatchNotifyVo data) {
|
||||
|
||||
log.info("开始构建异常数据 => {}", JSONObject.toJSONString(data));
|
||||
WatchMonitorData monitorDataEntity = new WatchMonitorData();
|
||||
monitorDataEntity.setAddress(data.getAddress());
|
||||
monitorDataEntity.setWatchNo(data.getWatchNo());
|
||||
monitorDataEntity.setLon(data.getLon());
|
||||
monitorDataEntity.setLat(data.getLat());
|
||||
monitorDataEntity.setLocateTime(data.getLocateTime());
|
||||
monitorDataEntity.setEventType(data.getEventType());
|
||||
monitorDataEntity.setEventId(data.getEventId());
|
||||
monitorDataEntity.setDataValue(data.getDataValue());
|
||||
monitorDataEntity.setCreateDate(data.getCreateTime());
|
||||
monitorDataEntity.setWarnTime(data.getWarnTime());
|
||||
|
||||
@@ -721,4 +721,9 @@ public interface CommonConstant {
|
||||
* 多选
|
||||
*/
|
||||
String PD = "PD";
|
||||
|
||||
/**
|
||||
* url加解密密钥(AES)
|
||||
*/
|
||||
String URL_SECRET_KEY = "a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5";
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.apache.http.impl.client.HttpClients;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.exception.ExceptionAssertsUtil;
|
||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||
import org.jeecg.util.GlobalManager;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -51,7 +52,9 @@ public class MyUploadUtil {
|
||||
private static String muFileListUploadPath;
|
||||
private static String muDownPath;
|
||||
private static String fileTypeWhiteList;
|
||||
// private static final int timeout = 10000;
|
||||
/** 全局配置管理器,用于读取 Redis 中的动态配置 */
|
||||
private static GlobalManager globalManager;
|
||||
// private static final int timeout = 10000;
|
||||
//有些大文件可能需要比较长的时间,这里改成5分钟
|
||||
private static final int socketTimeout = 10*60*1000;
|
||||
private static final int connectTimeout = 60*1000;
|
||||
@@ -108,6 +111,10 @@ public class MyUploadUtil {
|
||||
MyUploadUtil.fileTypeWhiteList = fileTypeWhiteList;
|
||||
}
|
||||
|
||||
public static void setGlobalManager(GlobalManager globalManager) {
|
||||
MyUploadUtil.globalManager = globalManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
@@ -116,43 +123,52 @@ public class MyUploadUtil {
|
||||
*/
|
||||
|
||||
public static String upload(MultipartFile file, String bizPath) throws Exception {
|
||||
String fileUrl = "";
|
||||
String fileName = file.getOriginalFilename();// 获取文件名
|
||||
checkFileLegitimacy(fileName);
|
||||
//???先屏蔽了
|
||||
// if (StringUtils.hasText(file.getOriginalFilename())) {
|
||||
// String originalFilename = file.getOriginalFilename();
|
||||
// String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
// fileName = fileName + ext;
|
||||
// }
|
||||
bizPath = StrAttackFilter.filter(bizPath);
|
||||
// .tar 文件固定走 MyUpload
|
||||
if (fileName != null && (fileName.endsWith(".tar") || fileName.endsWith(".apk"))) {
|
||||
log.info("上传文件为tar/apk文件,使用MyUploadUtil上传");
|
||||
try {
|
||||
return upload(file.getInputStream(), bizPath, fileName,true);
|
||||
} catch (Exception e) {
|
||||
log.error("upload error {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 其他文件:oss_flag=1 走 XjOssUtil,否则走 MyUpload
|
||||
String ossFlag = globalManager != null ? globalManager.getConfigValueByKey("oss_flag") : "";
|
||||
if ("1".equals(ossFlag)) {
|
||||
log.info("开启新疆OSS上传");
|
||||
return XjOssUtil.upload(file, bizPath);
|
||||
}
|
||||
log.info("使用MyUploadUtil上传");
|
||||
try {
|
||||
fileUrl = upload(file.getInputStream(), bizPath, fileName);
|
||||
return upload(file.getInputStream(), bizPath, fileName);
|
||||
} catch (Exception e) {
|
||||
fileUrl = null;
|
||||
log.error("upload error", e);
|
||||
log.error("upload error {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件 带缩略图
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public static String uploadImg(MultipartFile file, String bizPath, Boolean needThumbnailFlag) throws Exception {
|
||||
String fileUrl = "";
|
||||
String fileName = file.getOriginalFilename();// 获取文件名
|
||||
checkFileLegitimacy(fileName);
|
||||
bizPath = StrAttackFilter.filter(bizPath);
|
||||
try {
|
||||
fileUrl = uploadImg(file.getInputStream(), bizPath, fileName, needThumbnailFlag);
|
||||
} catch (Exception e) {
|
||||
log.error("upload error", e);
|
||||
}
|
||||
return fileUrl;
|
||||
}
|
||||
// /**
|
||||
// * 上传文件 带缩略图
|
||||
// *
|
||||
// * @param file
|
||||
// * @return
|
||||
// */
|
||||
// public static String uploadImg(MultipartFile file, String bizPath, Boolean needThumbnailFlag) throws Exception {
|
||||
// String fileUrl = "";
|
||||
// String fileName = file.getOriginalFilename();// 获取文件名
|
||||
// checkFileLegitimacy(fileName);
|
||||
// bizPath = StrAttackFilter.filter(bizPath);
|
||||
// try {
|
||||
// fileUrl = uploadImg(file.getInputStream(), bizPath, fileName, needThumbnailFlag);
|
||||
// } catch (Exception e) {
|
||||
// log.error("upload error", e);
|
||||
// }
|
||||
// return fileUrl;
|
||||
// }
|
||||
|
||||
public static String upload(InputStream stream, String bizPath, String fileName, boolean check) throws Exception {
|
||||
if (check) {
|
||||
@@ -220,145 +236,152 @@ public class MyUploadUtil {
|
||||
* @return
|
||||
*/
|
||||
public static String upload(InputStream stream, String bizPath, String fileName) throws Exception {
|
||||
// 其他文件:oss_flag=1 走 XjOssUtil,否则走 MyUpload
|
||||
String ossFlag = globalManager != null ? globalManager.getConfigValueByKey("oss_flag") : "";
|
||||
if ("1".equals(ossFlag)) {
|
||||
log.info("开启新疆OSS上传");
|
||||
return XjOssUtil.upload(stream, bizPath,fileName);
|
||||
}
|
||||
log.info("使用MyUploadUtil上传");
|
||||
return upload(stream, bizPath, fileName, true);
|
||||
}
|
||||
/**
|
||||
* 上传文件到myupload
|
||||
*
|
||||
* @param stream
|
||||
* @return
|
||||
*/
|
||||
public static String uploadImg(InputStream stream, String bizPath, String fileName, Boolean needThumbnailFlag) throws Exception {
|
||||
//避免重复文件覆盖,这里处理一下
|
||||
if (fileName.indexOf(".") > -1) {
|
||||
//String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
|
||||
String fileNameLeft = UUID.randomUUID().toString();
|
||||
fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
|
||||
} else {
|
||||
fileName += "_" + System.currentTimeMillis();
|
||||
}
|
||||
String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String path = bizPath + File.separator + nowday;
|
||||
String url = muUrl + "/" + muFileUploadPath;
|
||||
String fileUrl = "";
|
||||
//创建post方法连接实例,在post方法中传入待连接地址
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
CloseableHttpResponse httpResponse = null;
|
||||
// /**
|
||||
// * 上传文件到myupload
|
||||
// *
|
||||
// * @param stream
|
||||
// * @return
|
||||
// */
|
||||
// public static String uploadImg(InputStream stream, String bizPath, String fileName, Boolean needThumbnailFlag) throws Exception {
|
||||
// //避免重复文件覆盖,这里处理一下
|
||||
// if (fileName.indexOf(".") > -1) {
|
||||
// //String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
|
||||
// String fileNameLeft = UUID.randomUUID().toString();
|
||||
// fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
|
||||
// } else {
|
||||
// fileName += "_" + System.currentTimeMillis();
|
||||
// }
|
||||
// String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
// String path = bizPath + File.separator + nowday;
|
||||
// String url = muUrl + "/" + muFileUploadPath;
|
||||
// String fileUrl = "";
|
||||
// //创建post方法连接实例,在post方法中传入待连接地址
|
||||
// CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
// CloseableHttpResponse httpResponse = null;
|
||||
//
|
||||
// byte[] bytes = IoUtil.readBytes(stream);
|
||||
//
|
||||
// try {
|
||||
// URI uri = new URIBuilder(url)
|
||||
// .addParameter("fileName", fileName)
|
||||
// .addParameter("path", path)
|
||||
// .build();
|
||||
// HttpPost httppost = new HttpPost(uri);
|
||||
// RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
|
||||
// httppost.setConfig(requestConfig);
|
||||
// httppost.addHeader("token", muToken);
|
||||
// HttpEntity reqEntity = MultipartEntityBuilder.create()
|
||||
// //设置浏览器兼容模式
|
||||
// .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
|
||||
// //设置请求的编码格式
|
||||
// .setCharset(StandardCharsets.UTF_8)
|
||||
// .addBinaryBody("file", stream, ContentType.APPLICATION_OCTET_STREAM, fileName)
|
||||
// .build();
|
||||
// httppost.setEntity(reqEntity);
|
||||
// httppost.setEntity(reqEntity);
|
||||
//
|
||||
// httpResponse = httpClient.execute(httppost);
|
||||
//
|
||||
//
|
||||
// int backCode = httpResponse.getStatusLine().getStatusCode();
|
||||
// if (backCode != HttpStatus.SC_OK) {
|
||||
// log.error("上传文件失败" + backCode);
|
||||
// } else {
|
||||
// fileUrl = path + File.separator + fileName;
|
||||
// fileUrl = relaceSlash(fileUrl);
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// log.error("文件服务器连接失败", e);
|
||||
// } finally {
|
||||
// //释放资源
|
||||
// try {
|
||||
// httpClient.close();
|
||||
// if (httpResponse != null) {
|
||||
// httpResponse.close();
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
//// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (needThumbnailFlag) {
|
||||
// uploadThumbnail(url, bytes, fileName, path);
|
||||
// }
|
||||
//
|
||||
// return fileUrl;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 上传缩略图到myupload
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// private static void uploadThumbnail(String url, byte[] bytes, String fileName, String path) {
|
||||
// //创建post方法连接实例,在post方法中传入待连接地址
|
||||
// CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
// CloseableHttpResponse httpResponse = null;
|
||||
// try {
|
||||
// String thumbnailName = CommonConstant.THUMBNAIL_PREFIX + fileName;
|
||||
//
|
||||
// BufferedImage bufferedImage = Thumbnails.of(new ByteArrayInputStream(bytes))
|
||||
// .scale(CommonConstant.THUMBNAIL_SCALE)
|
||||
// .asBufferedImage();
|
||||
//
|
||||
// ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
// ImageIO.write(bufferedImage, "png", os);
|
||||
// InputStream is = new ByteArrayInputStream(os.toByteArray());
|
||||
// URI uri = new URIBuilder(url)
|
||||
// .addParameter("fileName", thumbnailName)
|
||||
// .addParameter("path", path)
|
||||
// .build();
|
||||
// HttpPost httppost = new HttpPost(uri);
|
||||
// RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
|
||||
// httppost.setConfig(requestConfig);
|
||||
// httppost.addHeader("token", muToken);
|
||||
// HttpEntity reqEntity = MultipartEntityBuilder.create()
|
||||
// //设置浏览器兼容模式
|
||||
// .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
|
||||
// //设置请求的编码格式
|
||||
// .setCharset(Consts.UTF_8)
|
||||
//
|
||||
// .addBinaryBody("file", is, ContentType.APPLICATION_OCTET_STREAM, thumbnailName)
|
||||
// .build();
|
||||
// httppost.setEntity(reqEntity);
|
||||
//
|
||||
// httpResponse = httpClient.execute(httppost);
|
||||
//
|
||||
// int backCode = httpResponse.getStatusLine().getStatusCode();
|
||||
// if (backCode != HttpStatus.SC_OK) {
|
||||
// log.error("上传缩略图失败" + backCode);
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// log.error("文件服务器连接失败", e);
|
||||
// } catch (URISyntaxException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// } finally {
|
||||
// //释放资源
|
||||
// try {
|
||||
// httpClient.close();
|
||||
// if (httpResponse != null) {
|
||||
// httpResponse.close();
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
byte[] bytes = IoUtil.readBytes(stream);
|
||||
|
||||
try {
|
||||
URI uri = new URIBuilder(url)
|
||||
.addParameter("fileName", fileName)
|
||||
.addParameter("path", path)
|
||||
.build();
|
||||
HttpPost httppost = new HttpPost(uri);
|
||||
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
|
||||
httppost.setConfig(requestConfig);
|
||||
httppost.addHeader("token", muToken);
|
||||
HttpEntity reqEntity = MultipartEntityBuilder.create()
|
||||
//设置浏览器兼容模式
|
||||
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
|
||||
//设置请求的编码格式
|
||||
.setCharset(StandardCharsets.UTF_8)
|
||||
.addBinaryBody("file", stream, ContentType.APPLICATION_OCTET_STREAM, fileName)
|
||||
.build();
|
||||
httppost.setEntity(reqEntity);
|
||||
httppost.setEntity(reqEntity);
|
||||
|
||||
httpResponse = httpClient.execute(httppost);
|
||||
|
||||
|
||||
int backCode = httpResponse.getStatusLine().getStatusCode();
|
||||
if (backCode != HttpStatus.SC_OK) {
|
||||
log.error("上传文件失败" + backCode);
|
||||
} else {
|
||||
fileUrl = path + File.separator + fileName;
|
||||
fileUrl = relaceSlash(fileUrl);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("文件服务器连接失败", e);
|
||||
} finally {
|
||||
//释放资源
|
||||
try {
|
||||
httpClient.close();
|
||||
if (httpResponse != null) {
|
||||
httpResponse.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (needThumbnailFlag) {
|
||||
uploadThumbnail(url, bytes, fileName, path);
|
||||
}
|
||||
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传缩略图到myupload
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static void uploadThumbnail(String url, byte[] bytes, String fileName, String path) {
|
||||
//创建post方法连接实例,在post方法中传入待连接地址
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
CloseableHttpResponse httpResponse = null;
|
||||
try {
|
||||
String thumbnailName = CommonConstant.THUMBNAIL_PREFIX + fileName;
|
||||
|
||||
BufferedImage bufferedImage = Thumbnails.of(new ByteArrayInputStream(bytes))
|
||||
.scale(CommonConstant.THUMBNAIL_SCALE)
|
||||
.asBufferedImage();
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, "png", os);
|
||||
InputStream is = new ByteArrayInputStream(os.toByteArray());
|
||||
URI uri = new URIBuilder(url)
|
||||
.addParameter("fileName", thumbnailName)
|
||||
.addParameter("path", path)
|
||||
.build();
|
||||
HttpPost httppost = new HttpPost(uri);
|
||||
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
|
||||
httppost.setConfig(requestConfig);
|
||||
httppost.addHeader("token", muToken);
|
||||
HttpEntity reqEntity = MultipartEntityBuilder.create()
|
||||
//设置浏览器兼容模式
|
||||
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
|
||||
//设置请求的编码格式
|
||||
.setCharset(Consts.UTF_8)
|
||||
|
||||
.addBinaryBody("file", is, ContentType.APPLICATION_OCTET_STREAM, thumbnailName)
|
||||
.build();
|
||||
httppost.setEntity(reqEntity);
|
||||
|
||||
httpResponse = httpClient.execute(httppost);
|
||||
|
||||
int backCode = httpResponse.getStatusLine().getStatusCode();
|
||||
if (backCode != HttpStatus.SC_OK) {
|
||||
log.error("上传缩略图失败" + backCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("文件服务器连接失败", e);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
//释放资源
|
||||
try {
|
||||
httpClient.close();
|
||||
if (httpResponse != null) {
|
||||
httpResponse.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getMuFileExistPath() {
|
||||
return muFileExistPath;
|
||||
}
|
||||
// public String getMuFileExistPath() {
|
||||
// return muFileExistPath;
|
||||
// }
|
||||
|
||||
public static void setMuFileExistPath(String muFileExistPath) {
|
||||
MyUploadUtil.muFileExistPath = muFileExistPath;
|
||||
|
||||
@@ -0,0 +1,542 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.mime.HttpMultipartMode;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jeecg.common.exception.ExceptionAssertsUtil;
|
||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||
import org.jeecg.util.RedisClientUtil;
|
||||
import org.jeecg.util.RedisKeyPrefix;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新疆油田平台(IOSP)OSS 文件工具类
|
||||
* <p>
|
||||
* 功能:
|
||||
* 1. OAuth2 client_credentials 鉴权,token 缓存到 Redis,401 时自动重新鉴权
|
||||
* 2. 文件上传(支持 InputStream 和 MultipartFile)
|
||||
* 3. 文件下载(通过 fileKey 获取文件流,处理 302 重定向)
|
||||
* </p>
|
||||
* <p>
|
||||
* 配置项(Nacos 中配置,前缀 xj.oss):
|
||||
* - gateway-url 网关地址,如 http://api.iosp.ydpt.tech
|
||||
* - client-id OAuth2 客户端ID
|
||||
* - client-secret OAuth2 客户端密钥
|
||||
* - organization-id 组织ID(上传/下载路径参数)
|
||||
* - config-code 配置Code(上传/下载路径参数)
|
||||
* - base-url-prefix 文件基础路径前缀(可选,用于截取 Location 中的相对路径)
|
||||
* </p>
|
||||
*/
|
||||
@Slf4j
|
||||
public class XjOssUtil {
|
||||
|
||||
// ===================== 静态配置字段(由 XjOssConfig 注入) =====================
|
||||
|
||||
/** 网关地址,如 http://api.iosp.ydpt.tech */
|
||||
private static String gatewayUrl;
|
||||
/** OAuth2 客户端ID */
|
||||
private static String clientId;
|
||||
/** OAuth2 客户端密钥 */
|
||||
private static String clientSecret;
|
||||
/** 组织ID */
|
||||
private static String organizationId;
|
||||
/** 配置Code */
|
||||
private static String configCode;
|
||||
/** 文件基础路径前缀,如 http://11.71.10.44:8060/wbwj/scyx/scfz/aygc/jkgl/0 */
|
||||
private static String baseUrlPrefix;
|
||||
/** 允许上传的文件类型白名单,逗号分隔,如 jpg,png,pdf */
|
||||
private static String fileTypeWhiteList;
|
||||
/** 授权上传目录,如 scyx/scfz/aygc/jkgl/ */
|
||||
private static String directory;
|
||||
|
||||
// ===================== Redis 工具(由 XjOssConfig 注入) =====================
|
||||
|
||||
private static RedisClientUtil redisClientUtil;
|
||||
|
||||
// ===================== 常量 =====================
|
||||
|
||||
/** Redis key 前缀(不含过期时间,仅用于 delete/get 操作) */
|
||||
private static final RedisKeyPrefix REDIS_PREFIX_NO_TTL = new RedisKeyPrefix("xj:oss");
|
||||
/** Redis 中存储 token 的 key */
|
||||
private static final String TOKEN_KEY = "access_token";
|
||||
/** token 过期时间预留缓冲(秒),提前刷新避免临界过期 */
|
||||
private static final int TOKEN_EXPIRE_BUFFER = 60;
|
||||
/** HTTP 连接超时(毫秒) */
|
||||
private static final int CONNECT_TIMEOUT = 30_000;
|
||||
/** HTTP Socket 超时(毫秒),大文件上传需要较长时间 */
|
||||
private static final int SOCKET_TIMEOUT = 10 * 60_000;
|
||||
private static final String DIR = "/jkgl/";
|
||||
|
||||
// ===================== Setter(供 XjOssConfig 注入) =====================
|
||||
|
||||
public static void setGatewayUrl(String gatewayUrl) {
|
||||
XjOssUtil.gatewayUrl = gatewayUrl;
|
||||
}
|
||||
|
||||
public static void setClientId(String clientId) {
|
||||
XjOssUtil.clientId = clientId;
|
||||
}
|
||||
|
||||
public static void setClientSecret(String clientSecret) {
|
||||
XjOssUtil.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public static void setOrganizationId(String organizationId) {
|
||||
XjOssUtil.organizationId = organizationId;
|
||||
}
|
||||
|
||||
public static void setConfigCode(String configCode) {
|
||||
XjOssUtil.configCode = configCode;
|
||||
}
|
||||
|
||||
public static void setBaseUrlPrefix(String baseUrlPrefix) {
|
||||
XjOssUtil.baseUrlPrefix = baseUrlPrefix;
|
||||
}
|
||||
|
||||
public static void setFileTypeWhiteList(String fileTypeWhiteList) {
|
||||
XjOssUtil.fileTypeWhiteList = fileTypeWhiteList;
|
||||
}
|
||||
|
||||
public static void setDirectory(String directory) {
|
||||
XjOssUtil.directory = directory;
|
||||
}
|
||||
|
||||
public static void setRedisClientUtil(RedisClientUtil redisClientUtil) {
|
||||
XjOssUtil.redisClientUtil = redisClientUtil;
|
||||
}
|
||||
|
||||
// ===================== 鉴权 =====================
|
||||
|
||||
/**
|
||||
* 向 IOSP 网关发起 OAuth2 鉴权,获取 access_token 并存入 Redis
|
||||
* <p>
|
||||
* 接口:POST {gatewayUrl}/oauth/oauth/token
|
||||
* 参数:grant_type=client_credentials, client_id, client_secret
|
||||
* </p>
|
||||
*
|
||||
* @return 获取到的 access_token,失败返回 null
|
||||
*/
|
||||
public static String authenticate() {
|
||||
String url = gatewayUrl + "/oauth/oauth/token";
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
HttpPost post = new HttpPost(url);
|
||||
post.setConfig(buildRequestConfig());
|
||||
// 使用 multipart/form-data 提交鉴权参数
|
||||
post.setEntity(MultipartEntityBuilder.create()
|
||||
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
|
||||
.setCharset(StandardCharsets.UTF_8)
|
||||
.addTextBody("grant_type", "client_credentials")
|
||||
.addTextBody("client_id", clientId)
|
||||
.addTextBody("client_secret", clientSecret)
|
||||
.build());
|
||||
|
||||
response = httpClient.execute(post);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
log.error("[XjOssUtil] 鉴权失败,HTTP状态码: {}", statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
JSONObject json = JSON.parseObject(body);
|
||||
String token = json.getString("access_token");
|
||||
// expires_in 单位为秒,减去缓冲时间后存入 Redis
|
||||
int expiresIn = json.getIntValue("expires_in");
|
||||
int ttl = Math.max(expiresIn - TOKEN_EXPIRE_BUFFER, 30);
|
||||
|
||||
// 存入 Redis,TTL = expires_in - 缓冲;动态创建带 TTL 的 RedisKeyPrefix
|
||||
RedisKeyPrefix prefixWithTtl = new RedisKeyPrefix(ttl, "xj:oss");
|
||||
redisClientUtil.set(prefixWithTtl, TOKEN_KEY, token);
|
||||
log.info("[XjOssUtil] 鉴权成功,token 有效期 {}s(含缓冲)", ttl);
|
||||
return token;
|
||||
} catch (Exception e) {
|
||||
log.error("[XjOssUtil] 鉴权异常", e);
|
||||
return null;
|
||||
} finally {
|
||||
closeQuietly(httpClient, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效的 access_token
|
||||
* <p>
|
||||
* 优先从 Redis 读取缓存,不存在时自动重新鉴权
|
||||
* </p>
|
||||
*
|
||||
* @return 有效的 access_token,失败返回 null
|
||||
*/
|
||||
public static String getToken() {
|
||||
String token = redisClientUtil.get(REDIS_PREFIX_NO_TTL, TOKEN_KEY, String.class);
|
||||
if (token == null) {
|
||||
log.info("[XjOssUtil] Redis 中 token 不存在或已过期,重新鉴权");
|
||||
token = authenticate();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
// ===================== 文件上传 =====================
|
||||
|
||||
/**
|
||||
* 上传文件(MultipartFile 方式)
|
||||
*
|
||||
* @param file 前端上传的文件
|
||||
* @param bizPath 业务子路径,如 import、temp,拼接在授权目录之后
|
||||
* @return 上传成功后的文件 URL,失败返回 null
|
||||
*/
|
||||
public static String upload(MultipartFile file, String bizPath) {
|
||||
try {
|
||||
return upload(file.getInputStream(), bizPath, file.getOriginalFilename());
|
||||
} catch (Exception e) {
|
||||
log.error("[XjOssUtil] MultipartFile 上传失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件(InputStream 方式)
|
||||
*
|
||||
* @param stream 文件输入流
|
||||
* @param bizPath 业务子路径,如 import、temp,拼接在授权目录之后
|
||||
* @param fileName 文件名,如 test.png
|
||||
* @return 上传成功后的文件 URL,失败返回 null
|
||||
*/
|
||||
public static String upload(InputStream stream, String bizPath, String fileName) {
|
||||
checkFileLegitimacy(fileName);
|
||||
// 过滤上传文件夹名特殊字符,防止路径攻击
|
||||
bizPath = StrAttackFilter.filter(bizPath);
|
||||
// 构造上传 URL:/hfle/v2/{organizationId}/files/{configCode}/multipart
|
||||
String url = gatewayUrl + "/hfle/v2/" + organizationId + "/files/" + configCode + "/multipart";
|
||||
|
||||
// 避免重复文件覆盖,追加时间戳
|
||||
if (fileName.contains(".")) {
|
||||
String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
|
||||
fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
|
||||
} else {
|
||||
fileName += "_" + System.currentTimeMillis();
|
||||
}
|
||||
String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
// 最终 fileName = bizPath/yyyyMMdd/originalName_timestamp.ext
|
||||
final String finalFileName = DIR + bizPath + "/" + nowday + "/" + fileName;
|
||||
|
||||
return doUpload(stream, url, finalFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行实际上传请求,401 时刷新 token 重试一次
|
||||
* <p>fileName 为已处理好的完整路径,不再做二次处理</p>
|
||||
*/
|
||||
private static String doUpload(InputStream stream, String url, String fileName) {
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
URI uri = new URIBuilder(url)
|
||||
.addParameter("directory", directory)
|
||||
.addParameter("fileName", fileName)
|
||||
.addParameter("docType", "0")
|
||||
.build();
|
||||
|
||||
HttpPost post = new HttpPost(uri);
|
||||
post.setConfig(buildRequestConfig());
|
||||
post.addHeader("Authorization", "Bearer " + getToken());
|
||||
post.addHeader("accept", "application/json;charset=utf-8");
|
||||
post.setEntity(MultipartEntityBuilder.create()
|
||||
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
|
||||
.setCharset(StandardCharsets.UTF_8)
|
||||
.addBinaryBody("file", stream, ContentType.APPLICATION_OCTET_STREAM, fileName)
|
||||
.build());
|
||||
|
||||
response = httpClient.execute(post);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
// 401 表示 token 过期,清除缓存后重试一次(fileName 已处理,不会重复拼接)
|
||||
if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
|
||||
log.warn("[XjOssUtil] 上传时 token 过期,重新鉴权后重试");
|
||||
closeQuietly(httpClient, response);
|
||||
redisClientUtil.delete(REDIS_PREFIX_NO_TTL, TOKEN_KEY);
|
||||
return doUpload(stream, url, fileName);
|
||||
}
|
||||
|
||||
if (statusCode != HttpStatus.SC_OK) {
|
||||
log.error("[XjOssUtil] 文件上传失败,HTTP状态码: {}", statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
String fileUrl = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
// 接口可能返回 HTTP 200 但 body 为业务错误 JSON,需单独判断
|
||||
JSONObject resultJson = null;
|
||||
try {
|
||||
resultJson = JSON.parseObject(fileUrl);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (resultJson != null && Boolean.TRUE.equals(resultJson.getBoolean("failed"))) {
|
||||
log.error("[XjOssUtil] 文件上传业务失败: {}", fileUrl);
|
||||
return null;
|
||||
}
|
||||
String fileKey = extractFileKey(fileUrl);
|
||||
log.info("[XjOssUtil] 文件上传成功,fileKey: {}", fileKey);
|
||||
return fileKey;
|
||||
} catch (Exception e) {
|
||||
log.error("[XjOssUtil] 文件上传异常", e);
|
||||
return null;
|
||||
} finally {
|
||||
closeQuietly(httpClient, response);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 文件下载 =====================
|
||||
|
||||
/**
|
||||
* 下载文件,返回文件输入流
|
||||
* <p>
|
||||
* 入参为短 fileKey,如 808fec572eda4428a84c8b4c4f07f511@aa.png
|
||||
* 内部自动拼接完整路径:{directory}0/{fileKey}
|
||||
* </p>
|
||||
*
|
||||
* @param fileKey 短文件 key,如 808fec572eda4428a84c8b4c4f07f511@aa.png
|
||||
* @return 文件输入流,调用方负责关闭;失败返回 null
|
||||
*/
|
||||
public static InputStream down(String fileKey) {
|
||||
String locationUrl = getLocationUrl(buildFullFileKey(fileKey));
|
||||
if (locationUrl == null) {
|
||||
return null;
|
||||
}
|
||||
CloseableHttpClient downloadClient = HttpClients.createDefault();
|
||||
try {
|
||||
HttpGet downloadGet = new HttpGet(locationUrl);
|
||||
downloadGet.setConfig(buildRequestConfig());
|
||||
CloseableHttpResponse downloadResponse = downloadClient.execute(downloadGet);
|
||||
int downloadStatus = downloadResponse.getStatusLine().getStatusCode();
|
||||
if (downloadStatus != HttpStatus.SC_OK) {
|
||||
log.error("[XjOssUtil] 获取文件流失败,HTTP状态码: {}", downloadStatus);
|
||||
closeQuietly(downloadClient, downloadResponse);
|
||||
return null;
|
||||
}
|
||||
// 返回包装流,关闭时自动释放 HTTP 资源
|
||||
return new HttpEntityInputStream(downloadResponse.getEntity().getContent(), downloadClient, downloadResponse);
|
||||
} catch (Exception e) {
|
||||
log.error("[XjOssUtil] 文件下载异常", e);
|
||||
closeQuietly(downloadClient, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件预览地址(Location 重定向 URL)
|
||||
* <p>
|
||||
* 入参为短 fileKey,如 808fec572eda4428a84c8b4c4f07f511@aa.png
|
||||
* 内部自动拼接完整路径:{directory}0/{fileKey}
|
||||
* 返回的 URL 可直接用于浏览器预览或前端展示
|
||||
* </p>
|
||||
*
|
||||
* @param fileKey 短文件 key,如 808fec572eda4428a84c8b4c4f07f511@aa.png
|
||||
* @return 预览 URL,失败返回 null
|
||||
*/
|
||||
public static String show(String fileKey) {
|
||||
return getLocationUrl(buildFullFileKey(fileKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将短 fileKey 拼接为下载接口所需的完整路径
|
||||
* <p>规则:{directory}0/{fileKey},示例:scyx/scfz/aygc/jkgl/0/808fec...@aa.png</p>
|
||||
*/
|
||||
private static String buildFullFileKey(String fileKey) {
|
||||
String dir = directory.endsWith("/") ? directory : directory + "/";
|
||||
return dir + "0/" + fileKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 download-by-key 接口,获取 302 重定向的 Location URL
|
||||
* <p>401 时自动刷新 token 重试一次</p>
|
||||
*
|
||||
* @param fullFileKey 完整 fileKey,如 scyx/scfz/aygc/jkgl/0/808fec...@aa.png
|
||||
* @return Location URL,失败返回 null
|
||||
*/
|
||||
private static String getLocationUrl(String fullFileKey) {
|
||||
String url = gatewayUrl + "/hfle/v2/" + organizationId + "/files/" + configCode + "/download-by-key";
|
||||
CloseableHttpClient noRedirectClient = org.apache.http.impl.client.HttpClientBuilder.create()
|
||||
.disableRedirectHandling()
|
||||
.build();
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
URI uri = new URIBuilder(url)
|
||||
.addParameter("fileKey", fullFileKey)
|
||||
.build();
|
||||
HttpGet get = new HttpGet(uri);
|
||||
get.setConfig(buildRequestConfig());
|
||||
String token = getToken();
|
||||
get.addHeader("Authorization", "Bearer " + token);
|
||||
|
||||
// 打印等效 curl 命令,便于调试
|
||||
log.info("[XjOssUtil] curl --request GET \\\n --url '{}' \\\n --header 'Authorization: Bearer {}'",
|
||||
uri.toString(), token);
|
||||
|
||||
response = noRedirectClient.execute(get);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
// 401 表示 token 过期,重新鉴权后重试一次
|
||||
if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
|
||||
log.warn("[XjOssUtil] 获取 Location 时 token 过期,重新鉴权后重试");
|
||||
closeQuietly(noRedirectClient, response);
|
||||
redisClientUtil.delete(REDIS_PREFIX_NO_TTL, TOKEN_KEY);
|
||||
return getLocationUrl(fullFileKey);
|
||||
}
|
||||
|
||||
if (statusCode != HttpStatus.SC_MOVED_TEMPORARILY && statusCode != HttpStatus.SC_SEE_OTHER) {
|
||||
log.error("[XjOssUtil] download-by-key 返回非预期状态码: {}", statusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
Header locationHeader = response.getFirstHeader("Location");
|
||||
if (locationHeader == null) {
|
||||
log.error("[XjOssUtil] download-by-key 未返回 Location 响应头");
|
||||
return null;
|
||||
}
|
||||
String locationUrl = locationHeader.getValue();
|
||||
log.info("[XjOssUtil] 获取到 Location: {}", locationUrl);
|
||||
return locationUrl;
|
||||
} catch (Exception e) {
|
||||
log.error("[XjOssUtil] 获取 Location 异常", e);
|
||||
return null;
|
||||
} finally {
|
||||
closeQuietly(noRedirectClient, response);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 私有工具方法 =====================
|
||||
|
||||
/**
|
||||
* 文件合法性校验(文件类型白名单)
|
||||
* <p>
|
||||
* 白名单为空时跳过校验;文件名无扩展名或扩展名不在白名单中时抛出异常。
|
||||
* </p>
|
||||
*
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
private static void checkFileLegitimacy(String fileName) {
|
||||
if (StrUtil.isNotEmpty(fileTypeWhiteList)) {
|
||||
String extName = FileNameUtil.extName(fileName);
|
||||
if (StrUtil.isBlank(extName)) {
|
||||
ExceptionAssertsUtil.fail("文件上传失败:不能识别的文件类型!");
|
||||
}
|
||||
List<String> fileTypeList = Arrays.asList(fileTypeWhiteList.split(","));
|
||||
// 统一转小写比对,忽略大小写差异
|
||||
if (!fileTypeList.contains(extName.toLowerCase())) {
|
||||
ExceptionAssertsUtil.fail("文件上传失败:不支持的文件类型!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从上传接口返回的完整 URL 中截取 fileKey
|
||||
* <p>
|
||||
* 返回示例:https://wbwj.http://11.71.10.44:8060/scyx/.../0/808fec572eda4428a84c8b4c4f07f511@aa.png
|
||||
* 截取结果:808fec572eda4428a84c8b4c4f07f511@aa.png
|
||||
* 规则:取 @ 符号前最后一个 / 之后的所有内容
|
||||
* </p>
|
||||
*
|
||||
* @param fileUrl 接口返回的完整 URL
|
||||
* @return fileKey,解析失败时返回原始 URL
|
||||
*/
|
||||
private static String extractFileKey(String fileUrl) {
|
||||
int atIndex = fileUrl.indexOf("@");
|
||||
if (atIndex < 0) {
|
||||
return fileUrl;
|
||||
}
|
||||
int slashIndex = fileUrl.lastIndexOf("/", atIndex);
|
||||
if (slashIndex < 0) {
|
||||
return fileUrl;
|
||||
}
|
||||
return fileUrl.substring(slashIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建统一的 HTTP 请求配置
|
||||
*/
|
||||
private static RequestConfig buildRequestConfig() {
|
||||
return RequestConfig.custom()
|
||||
.setConnectTimeout(CONNECT_TIMEOUT)
|
||||
.setSocketTimeout(SOCKET_TIMEOUT)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默关闭 HTTP 资源,忽略异常
|
||||
*/
|
||||
private static void closeQuietly(CloseableHttpClient client, CloseableHttpResponse response) {
|
||||
try {
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
try {
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 内部类:包装 InputStream,关闭时释放 HTTP 资源 =====================
|
||||
|
||||
/**
|
||||
* 包装 HTTP 响应的 InputStream,关闭时自动释放 HTTP 连接资源
|
||||
*/
|
||||
private static class HttpEntityInputStream extends InputStream {
|
||||
|
||||
private final InputStream delegate;
|
||||
private final CloseableHttpClient httpClient;
|
||||
private final CloseableHttpResponse httpResponse;
|
||||
|
||||
HttpEntityInputStream(InputStream delegate, CloseableHttpClient httpClient, CloseableHttpResponse httpResponse) {
|
||||
this.delegate = delegate;
|
||||
this.httpClient = httpClient;
|
||||
this.httpResponse = httpResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return delegate.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return delegate.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
delegate.close();
|
||||
} finally {
|
||||
closeQuietly(httpClient, httpResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package org.jeecg.config.oss;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.util.MyUploadUtil;
|
||||
import org.jeecg.util.GlobalManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -29,6 +31,9 @@ public class MyUploadConfig {
|
||||
@Value(value = "${jeecg.myupload.mu_file_type_white_list}")
|
||||
private String fileTypeWhiteList;
|
||||
|
||||
@Autowired
|
||||
private GlobalManager globalManager;
|
||||
|
||||
@Bean
|
||||
public void initMu() {
|
||||
MyUploadUtil.setMuUrl(muUrl);
|
||||
@@ -39,6 +44,7 @@ public class MyUploadConfig {
|
||||
MyUploadUtil.setMuFileListUploadPath(muFileListUploadPath);
|
||||
MyUploadUtil.setMuDownPath(muDownPath);
|
||||
MyUploadUtil.setFileTypeWhiteList(fileTypeWhiteList);
|
||||
MyUploadUtil.setGlobalManager(globalManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.jeecg.config.oss;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.util.XjOssUtil;
|
||||
import org.jeecg.util.RedisClientUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 新疆油田平台(IOSP)OSS 配置类
|
||||
* <p>
|
||||
* 从 Nacos 读取配置并注入到 XjOssUtil 静态字段中。
|
||||
* 对应 Nacos 配置前缀:xj.oss
|
||||
* </p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class XjOssConfig {
|
||||
|
||||
/** 网关地址,如 http://api.iosp.ydpt.tech */
|
||||
@Value(value = "${variable.anyan.host}")
|
||||
private String gatewayUrl;
|
||||
|
||||
/** OAuth2 客户端ID */
|
||||
@Value(value = "${variable.anyan.client-id}")
|
||||
private String clientId;
|
||||
|
||||
/** OAuth2 客户端密钥 */
|
||||
@Value(value = "${variable.anyan.client-secret}")
|
||||
private String clientSecret;
|
||||
|
||||
/** 组织ID(上传/下载路径参数) */
|
||||
@Value(value = "${variable.anyan.organization-id}")
|
||||
private String organizationId;
|
||||
|
||||
/** 配置Code(上传/下载路径参数) */
|
||||
@Value(value = "${variable.anyan.config-code}")
|
||||
private String configCode;
|
||||
|
||||
/**
|
||||
* 文件基础路径前缀(可选)
|
||||
* 示例:http://11.71.10.44:8060/wbwj/scyx/scfz/aygc/jkgl/0
|
||||
* 用于从完整 URL 中截取相对 fileKey
|
||||
*/
|
||||
@Value(value = "${variable.anyan.base-url-prefix:}")
|
||||
private String baseUrlPrefix;
|
||||
|
||||
/**
|
||||
* 允许上传的文件类型白名单,逗号分隔(可选)
|
||||
* 示例:jpg,jpeg,png,pdf,doc,docx
|
||||
* 为空时不做类型限制
|
||||
*/
|
||||
@Value(value = "${variable.anyan.file-type-white-list:}")
|
||||
private String fileTypeWhiteList;
|
||||
|
||||
/**
|
||||
* 授权上传目录,需与 IOSP 平台授权的目录一致
|
||||
* 示例:scyx/scfz/aygc/jkgl/
|
||||
*/
|
||||
@Value(value = "${variable.anyan.directory}")
|
||||
private String directory;
|
||||
|
||||
@Autowired
|
||||
private RedisClientUtil redisClientUtil;
|
||||
|
||||
/**
|
||||
* 初始化 XjOssUtil 静态配置
|
||||
*/
|
||||
@Bean
|
||||
public void initXjOss() {
|
||||
XjOssUtil.setGatewayUrl(gatewayUrl);
|
||||
XjOssUtil.setClientId(clientId);
|
||||
XjOssUtil.setClientSecret(clientSecret);
|
||||
XjOssUtil.setOrganizationId(organizationId);
|
||||
XjOssUtil.setConfigCode(configCode);
|
||||
XjOssUtil.setBaseUrlPrefix(baseUrlPrefix);
|
||||
XjOssUtil.setFileTypeWhiteList(fileTypeWhiteList);
|
||||
XjOssUtil.setDirectory(directory);
|
||||
XjOssUtil.setRedisClientUtil(redisClientUtil);
|
||||
log.info("[XjOssConfig] IOSP OSS 配置初始化完成,网关地址: {}", gatewayUrl);
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,8 @@ public class ShiroConfig {
|
||||
|
||||
filterChainDefinitionMap.put("/version/getSysVersionDetailByPackageName", "anon");
|
||||
filterChainDefinitionMap.put("/sys/api/notice/selectIosUpdate", "anon");
|
||||
// filterChainDefinitionMap.put("/sys/upload/down/**", "anon");
|
||||
// filterChainDefinitionMap.put("/sys/upload/show/**", "anon");
|
||||
|
||||
// 添加自己的过滤器并且取名为jwt
|
||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.jeecg.util;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* AES/CBC/PKCS5Padding 对称加密工具类
|
||||
* 加密结果经 Base64 URL Safe(无填充)编码,可安全用于 URL 传参
|
||||
*/
|
||||
public class AesEncryptUtils {
|
||||
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
||||
/**
|
||||
* CBC 模式 IV 固定 16 字节
|
||||
*/
|
||||
private static final int IV_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* 加密
|
||||
* 随机生成 IV,将 [IV(16字节) + 密文] 拼接后进行 Base64 URL Safe(无填充)编码
|
||||
*
|
||||
* @param plainText 明文字符串
|
||||
* @param secretKey 密钥(任意长度字符串,内部经 SHA-256 截取为 128 位)
|
||||
* @return URL 安全的密文字符串,不含 +、/、= 等特殊字符
|
||||
* @throws Exception 加密异常
|
||||
*/
|
||||
public static String encrypt(String plainText, String secretKey) throws Exception {
|
||||
byte[] keyBytes = deriveKey(secretKey);
|
||||
byte[] ivBytes = generateIv();
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
cipher.init(Cipher.ENCRYPT_MODE,
|
||||
new SecretKeySpec(keyBytes, ALGORITHM),
|
||||
new IvParameterSpec(ivBytes));
|
||||
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
// 将 IV 前缀拼接到密文,解密时从头部分离
|
||||
byte[] combined = new byte[IV_LENGTH + encrypted.length];
|
||||
System.arraycopy(ivBytes, 0, combined, 0, IV_LENGTH);
|
||||
System.arraycopy(encrypted, 0, combined, IV_LENGTH, encrypted.length);
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(combined);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
* 从密文头部分离 IV,再执行 AES/CBC 解密
|
||||
*
|
||||
* @param cipherText URL 安全的密文字符串(由 encrypt 方法生成)
|
||||
* @param secretKey 密钥(须与加密时保持一致)
|
||||
* @return 解密后的明文字符串
|
||||
* @throws Exception 解密异常(密钥错误或数据被篡改时抛出)
|
||||
*/
|
||||
public static String decrypt(String cipherText, String secretKey) throws Exception {
|
||||
byte[] combined = Base64.getUrlDecoder().decode(cipherText);
|
||||
byte[] ivBytes = Arrays.copyOfRange(combined, 0, IV_LENGTH);
|
||||
byte[] encrypted = Arrays.copyOfRange(combined, IV_LENGTH, combined.length);
|
||||
byte[] keyBytes = deriveKey(secretKey);
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
cipher.init(Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(keyBytes, ALGORITHM),
|
||||
new IvParameterSpec(ivBytes));
|
||||
return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 SHA-256 对密钥字符串进行哈希,取前 16 字节作为 AES 128 位密钥
|
||||
* 支持任意长度的密钥输入,避免 AES 对密钥长度的严格限制
|
||||
*
|
||||
* @param secretKey 原始密钥字符串
|
||||
* @return 16 字节的 AES 密钥
|
||||
*/
|
||||
private static byte[] deriveKey(String secretKey) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(secretKey.getBytes(StandardCharsets.UTF_8));
|
||||
// 取前 16 字节 = 128 位 AES 密钥
|
||||
return Arrays.copyOf(hash, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 SecureRandom 随机生成 16 字节 IV
|
||||
* 每次加密使用不同 IV,防止相同明文产生相同密文
|
||||
*
|
||||
* @return 16 字节随机 IV
|
||||
*/
|
||||
private static byte[] generateIv() {
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
String plainText = "https://hello world";
|
||||
String secretKey = CommonConstant.URL_SECRET_KEY;
|
||||
// String cipherText = AesEncryptUtils.encrypt(plainText, secretKey);
|
||||
// System.out.println(cipherText);
|
||||
String decryptedText = AesEncryptUtils.decrypt("6oFxHVMYNHXpz8VtsREcNlokVqOdvaZNSGBJ5U3_7mY", secretKey);
|
||||
System.out.println(decryptedText);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -82,18 +82,36 @@ public class BaseEmployeeInfo {
|
||||
@Schema(title = "二级部门Code(单位)")
|
||||
private String secondDeptCode;
|
||||
|
||||
@Schema(title = "二级部门id(单位)")
|
||||
private String secondDeptId;
|
||||
|
||||
@Schema(title = "二级部门名称(单位)")
|
||||
private String secondDeptName;
|
||||
|
||||
@Schema(title = "三级部门Code(部门)")
|
||||
private String thirdDeptCode;
|
||||
|
||||
@Schema(title = "三级部门id(部门)")
|
||||
private String thirdDeptId;
|
||||
|
||||
@Schema(title = "三级部门名称(部门)")
|
||||
private String thirdDeptName;
|
||||
|
||||
@Schema(title = "当前部门Code(用户所属部门)")
|
||||
private String thisDeptCode;
|
||||
|
||||
@Schema(title = "当前部门id(用户所属部门)")
|
||||
private String thisDeptId;
|
||||
|
||||
@Schema(title = "当前部门名称(用户所属部门)")
|
||||
private String thisDeptName;
|
||||
|
||||
@Schema(title = "角色编码")
|
||||
private String roleCodes;
|
||||
|
||||
@Schema(title = "管理部门(code)")
|
||||
private String departCodes;
|
||||
|
||||
@Schema(title = "管理部门(id)")
|
||||
private String departIds;
|
||||
}
|
||||
|
||||
+13
@@ -3,16 +3,20 @@ package org.jeecg.modules.api.controller;
|
||||
import org.jeecg.base.UserBase;
|
||||
import org.jeecg.bean.response.BaseEmployeeInfo;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.modules.system.bean.response.ScalePersonalUserInfo;
|
||||
import org.jeecg.modules.system.service.ISysUserService;
|
||||
import org.jeecg.modules.system.service.ScalePersonalService;
|
||||
import org.jeecg.util.AesEncryptUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author stan
|
||||
@@ -67,5 +71,14 @@ public class SysApiController {
|
||||
return Result.ok(sysUserService.getUserInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串加密
|
||||
*/
|
||||
@GetMapping("encryptInfo")
|
||||
public Result<Map<String, String>> encryptInfo(@RequestParam String str) throws Exception {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("encryptData", AesEncryptUtils.encrypt(str, CommonConstant.URL_SECRET_KEY));
|
||||
return Result.ok(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ package org.jeecg.modules.system.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -124,6 +125,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
|
||||
HttpServletRequest req) {
|
||||
|
||||
Page<UserEmployee> page = new Page<>(pageNo, pageSize);
|
||||
userFilter.setRealname(HtmlUtil.unescape(userFilter.getRealname()));
|
||||
IPage<UserEmployee> pageList = healthUserEmployeeExService.selectHealthUserEmployeeExList(page, userFilter, idStatus);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
+100
@@ -1,10 +1,12 @@
|
||||
package org.jeecg.modules.system.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.util.MyUploadUtil;
|
||||
import org.jeecg.common.util.XjOssUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.oss.service.IOssFileService;
|
||||
import org.jeecg.modules.system.bean.request.UploadFilsDTO;
|
||||
@@ -15,6 +17,11 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -89,6 +96,9 @@ public class SysUploadController {
|
||||
} catch (Exception e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
if (StrUtil.isBlank(fileUrl)){
|
||||
return Result.error("上传失败,请检查配置信息是否正确!");
|
||||
}
|
||||
Map map = new HashMap();
|
||||
map.put("url", fileUrl);
|
||||
return Result.OK(map);
|
||||
@@ -115,4 +125,94 @@ public class SysUploadController {
|
||||
return Result.OK(urlList);
|
||||
}
|
||||
|
||||
@Operation(summary = "IOSP文件下载", description = "根据 fileKey 下载文件,如 sys/upload/down/808fec...@aa.png")
|
||||
@RequestMapping(value = "/down/**", method = RequestMethod.GET)
|
||||
public void fileDown(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String fileKey = extractPathParam(request, "/down/");
|
||||
if (isIllegalFileKey(fileKey)) {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "fileKey 格式非法");
|
||||
return;
|
||||
}
|
||||
try (InputStream in = XjOssUtil.down(fileKey)) {
|
||||
if (in == null) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在或下载失败");
|
||||
return;
|
||||
}
|
||||
// 从 fileKey 中取文件名(@ 后面的部分)
|
||||
String fileName = fileKey.contains("@") ? fileKey.substring(fileKey.indexOf("@") + 1) : fileKey;
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
|
||||
try (OutputStream out = response.getOutputStream()) {
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(buf)) != -1) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[SysUploadController] IOSP文件下载失败, fileKey={}", fileKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "IOSP文件预览", description = "根据 fileKey 获取预览地址,如 sys/upload/show/808fec...@aa.png")
|
||||
@RequestMapping(value = "/show/**", method = RequestMethod.GET)
|
||||
public void fileShow(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String fileKey = extractPathParam(request, "/show/");
|
||||
if (isIllegalFileKey(fileKey)) {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "fileKey 格式非法");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String url = XjOssUtil.show(fileKey);
|
||||
if (url == null) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在或获取预览地址失败");
|
||||
return;
|
||||
}
|
||||
// 重定向到真实预览地址
|
||||
response.sendRedirect(url);
|
||||
} catch (Exception e) {
|
||||
log.error("[SysUploadController] IOSP文件预览失败, fileKey={}", fileKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求路径中提取 fileKey
|
||||
* <p>截取 marker 之后的路径部分,并做 URL 解码</p>
|
||||
*/
|
||||
private String extractPathParam(HttpServletRequest request, String marker) {
|
||||
String uri = request.getRequestURI();
|
||||
int idx = uri.indexOf(marker);
|
||||
if (idx < 0) {
|
||||
return "";
|
||||
}
|
||||
String raw = uri.substring(idx + marker.length());
|
||||
try {
|
||||
return java.net.URLDecoder.decode(raw, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 fileKey 是否存在路径穿透风险
|
||||
* <p>
|
||||
* 拦截 ../、..\ 及其 URL 编码变体(%2e%2e%2f 等),防止跳出授权目录
|
||||
* </p>
|
||||
*/
|
||||
private boolean isIllegalFileKey(String fileKey) {
|
||||
if (oConvertUtils.isEmpty(fileKey)) {
|
||||
return true;
|
||||
}
|
||||
// 先做 URL 解码,防止编码绕过
|
||||
String decoded;
|
||||
try {
|
||||
decoded = java.net.URLDecoder.decode(fileKey, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
return decoded.contains("../") || decoded.contains("..\\")
|
||||
|| decoded.startsWith("..") || decoded.contains("/..");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,7 @@ package org.jeecg.modules.system.controller;
|
||||
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
@@ -460,6 +461,9 @@ public class SysUserController {
|
||||
@RequestMapping(value = "/listAll", method = RequestMethod.GET)
|
||||
public Result<IPage<SysUser>> queryAllPageList(SysUser user, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
if(StrUtil.isNotBlank(user.getRealname())){
|
||||
user.setRealname(HtmlUtil.unescape(user.getRealname()));
|
||||
}
|
||||
QueryWrapper<SysUser> queryWrapper = QueryGenerator.initQueryWrapper(user, req.getParameterMap());
|
||||
return sysUserService.queryPageList(req, queryWrapper, pageSize, pageNo);
|
||||
}
|
||||
|
||||
+5
@@ -1,6 +1,8 @@
|
||||
package org.jeecg.modules.system.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -77,6 +79,9 @@ public class SysUserDepartChangeApplyController extends JeecgController<SysUserD
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||
Page<ChangeDepartVo> page = new Page<>(pageNo, pageSize);
|
||||
if(StrUtil.isNotBlank(userName)){
|
||||
userName = HtmlUtil.unescape(userName);
|
||||
}
|
||||
IPage<ChangeDepartVo> pageList = sysUserDepartChangeApplyService.pageCustom(page, userName, status,workNo, userId);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
package org.jeecg.modules.system.entity;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -11,6 +12,10 @@ import lombok.Data;
|
||||
public class SelectWrapperDTO {
|
||||
private String orgCode;
|
||||
private String realName;
|
||||
|
||||
public void setRealName(String realName) {
|
||||
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||
}
|
||||
private String workNo;
|
||||
private String sex;
|
||||
private String code;
|
||||
|
||||
+8
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -192,6 +193,10 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
|
||||
}
|
||||
List<String> orgCodes = GlobalUtils.getManagedCodes(filter.getOrgCode());
|
||||
filter.setOrgCodes(orgCodes);
|
||||
// 名字中包含·(中文间隔号)时,前端可能传入 HTML 实体 ·,需转义回原字符
|
||||
if (StrUtil.isNotBlank(filter.getRealname())) {
|
||||
filter.setRealname(filter.getRealname().replaceAll("·", "·"));
|
||||
}
|
||||
IPage<UserEmployee> userEmployeeIPage = healthUserEmployeeExMapper.queryUserEmployeeList(page, filter);
|
||||
if (CollectionUtils.isEmpty(userEmployeeIPage.getRecords())) {
|
||||
return userEmployeeIPage;
|
||||
@@ -774,6 +779,9 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
|
||||
|
||||
@Override
|
||||
public IPage<UserEmployee> selectUserListByWatch(Page page, UserFilter filter) {
|
||||
if(StrUtil.isNotBlank(filter.getRealname())){
|
||||
filter.setRealname(HtmlUtil.unescape(filter.getRealname()));
|
||||
}
|
||||
List<String> bindUserIdList = watchApi.getBindUserIdList();
|
||||
if (!CollectionUtils.isEmpty(bindUserIdList)) {
|
||||
filter.setIdList(bindUserIdList);
|
||||
|
||||
+4
@@ -8,6 +8,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdcardUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -262,6 +263,9 @@ public class SysBaseApiImpl implements ISysBaseAPI {
|
||||
|
||||
@Override
|
||||
public List<String> getUserIdsByDepartAndUsername(String departCode, String username, String idCard, String workNo) {
|
||||
if(StrUtil.isNotBlank(username)){
|
||||
username = HtmlUtil.unescape(username);
|
||||
}
|
||||
if (StrUtil.isAllBlank(departCode, username, idCard, workNo)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+18
@@ -6,6 +6,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.extra.pinyin.PinyinUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
@@ -2875,6 +2876,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
queryWrapper.eq(SysUser::getStatus, 1);
|
||||
queryWrapper.eq(SysUser::getPersonType, 1);
|
||||
if (StrUtil.isNotEmpty(name)) {
|
||||
name = HtmlUtil.unescape(name);
|
||||
queryWrapper.like(SysUser::getRealname, name);
|
||||
}
|
||||
if (StrUtil.isNotEmpty(orgCode)) {
|
||||
@@ -3906,6 +3908,16 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
if (null == loginUser) {
|
||||
ExceptionAssertsUtil.fail("用户不存在!");
|
||||
}
|
||||
List<String> managedCodes = GlobalUtils.getManagedCodes(null);
|
||||
Set<String> departIds = new HashSet<>();
|
||||
if (CollUtil.isNotEmpty(managedCodes)) {
|
||||
managedCodes.forEach(s -> {
|
||||
String departIdByOrgCode = sysCache.getDepartIdByOrgCode(s);
|
||||
if (StringUtils.isNotBlank(departIdByOrgCode) && !"null".equals(departIdByOrgCode)) {
|
||||
departIds.add(departIdByOrgCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
MPJLambdaWrapper<SysUser> queryWrapper = new MPJLambdaWrapper<SysUser>()
|
||||
.selectAs(SysUser::getId, BaseEmployeeInfo::getUserId)
|
||||
.selectAs(SysUser::getUsername, BaseEmployeeInfo::getUserName)
|
||||
@@ -3931,13 +3943,19 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
baseEmployeeInfo.setAge(ObjectUtils.isNotEmpty(loginUser.getAge()) && loginUser.getAge() > 0 ? loginUser.getAge() : null);
|
||||
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(loginUser.getOrgCode()));
|
||||
baseEmployeeInfo.setSecondDeptCode(secondDepart.getOrgCode());
|
||||
baseEmployeeInfo.setSecondDeptId(secondDepart.getId());
|
||||
baseEmployeeInfo.setSecondDeptName(secondDepart.getDepartName());
|
||||
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
|
||||
baseEmployeeInfo.setThirdDeptCode(thirdDepart.getOrgCode());
|
||||
baseEmployeeInfo.setThirdDeptId(thirdDepart.getId());
|
||||
baseEmployeeInfo.setThirdDeptName(thirdDepart.getDepartName());
|
||||
baseEmployeeInfo.setThisDeptCode(loginUser.getOrgCode());
|
||||
SysDepart departByOrgCode = sysCache.getDepartByOrgCode(loginUser.getOrgCode());
|
||||
baseEmployeeInfo.setThisDeptName(departByOrgCode.getDepartName());
|
||||
baseEmployeeInfo.setThisDeptId(departByOrgCode.getId());
|
||||
baseEmployeeInfo.setRoleCodes(loginUser.getRoleCodes());
|
||||
baseEmployeeInfo.setDepartCodes(String.join(",", managedCodes));
|
||||
baseEmployeeInfo.setDepartIds(String.join(",", departIds));
|
||||
return baseEmployeeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!--<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
@@ -87,7 +87,7 @@
|
||||
</container>
|
||||
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugin>-->
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
Reference in New Issue
Block a user