Compare commits
48
Commits
b2b96290bf
...
develop
| 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 | ||
|
|
98dc17552d | ||
|
|
e9784f7957 | ||
|
|
175238e291 | ||
|
|
3c822d581c | ||
|
|
77d72d1d78 | ||
|
|
9dbdd911d9 | ||
|
|
0022497f64 | ||
|
|
9714e97a71 | ||
|
|
65f5755422 | ||
|
|
677b4d5080 | ||
|
|
49f316dd82 | ||
|
|
b6aecf4e64 | ||
|
|
cd7f5661de | ||
|
|
6e005f2f4e | ||
|
|
03f8be4c98 |
@@ -31,5 +31,5 @@ CREATE TABLE `sys_message_notice_scope` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_notice_id` (`notice_id`),
|
KEY `idx_notice_id` (`notice_id`),
|
||||||
KEY `idx_type` (`type`),
|
KEY `idx_type` (`type`),
|
||||||
KEY `idx_scope_id` (`scope_id`)
|
KEY `idx_scope_notice` (`scope_id`, `notice_id`) COMMENT '联表查询复合索引,支持WHERE scope_id + JOIN notice_id'
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息通知范围表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息通知范围表';
|
||||||
@@ -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.查询关爱联络员信息及急救人员查询接口
|
* 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.批量更新员工关键信息接口
|
* 19.批量更新员工关键信息接口
|
||||||
|
|||||||
+3
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.anyan.model.dto;
|
package com.renkang.anyan.model.dto;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -34,4 +35,6 @@ public class CareUserDetailsParam {
|
|||||||
|
|
||||||
@Schema(title = "pageType")
|
@Schema(title = "pageType")
|
||||||
private String pageType = "plus";
|
private String pageType = "plus";
|
||||||
|
|
||||||
|
private int year = DateUtil.thisYear();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@ public enum PointsSourceEnum {
|
|||||||
TYPE_2_VIOLATION(2, "violation", "违规扣除"),
|
TYPE_2_VIOLATION(2, "violation", "违规扣除"),
|
||||||
TYPE_2_EXPIRED(2, "expired", "过期清除");
|
TYPE_2_EXPIRED(2, "expired", "过期清除");
|
||||||
/**
|
/**
|
||||||
*类型1-获得积分,2-扣减积分
|
* 类型1-获得积分,2-扣减积分
|
||||||
*/
|
*/
|
||||||
public final Integer changeType;
|
public final Integer changeType;
|
||||||
/**
|
/**
|
||||||
@@ -23,7 +23,7 @@ public enum PointsSourceEnum {
|
|||||||
*/
|
*/
|
||||||
public final String source;
|
public final String source;
|
||||||
/**
|
/**
|
||||||
*来源描述
|
* 来源描述
|
||||||
*/
|
*/
|
||||||
public final String desc;
|
public final String desc;
|
||||||
|
|
||||||
|
|||||||
+24
-1
@@ -6,6 +6,9 @@ import lombok.Data;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.jeecg.base.PageInfo;
|
import org.jeecg.base.PageInfo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author pj
|
* @author pj
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
@@ -16,7 +19,27 @@ import org.jeecg.base.PageInfo;
|
|||||||
@Data
|
@Data
|
||||||
public class UserPointsRankingDTO extends PageInfo<PointsRule> {
|
public class UserPointsRankingDTO extends PageInfo<PointsRule> {
|
||||||
|
|
||||||
@Schema(title = "榜单类型,0总榜,1周榜,2月榜")
|
@Schema(title = "榜单类型,0日榜,1周榜,2月榜,3季榜,4年榜,5选时排名,10总榜")
|
||||||
private String rankType;
|
private String rankType;
|
||||||
|
|
||||||
|
@Schema(title = "开始时间")
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
@Schema(title = "截止时间")
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
@Schema(title = "所属单位")
|
||||||
|
private String orgCode;
|
||||||
|
|
||||||
|
@Schema(title = "所属部门")
|
||||||
|
private String deptCode;
|
||||||
|
|
||||||
|
@Schema(title = "姓名")
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
@Schema(title = "员工编号")
|
||||||
|
private String workNo;
|
||||||
|
|
||||||
|
private List<String> userIds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,4 +81,16 @@ public class PointsRule implements Serializable {
|
|||||||
@Schema(title = "最后修改时间")
|
@Schema(title = "最后修改时间")
|
||||||
private Date lastModifyDate;
|
private Date lastModifyDate;
|
||||||
|
|
||||||
|
@Schema(title = "周期内打卡次数上限")
|
||||||
|
private Integer checkInUpper;
|
||||||
|
|
||||||
|
@Schema(title = "年额外积分上限")
|
||||||
|
private BigDecimal yearExtraPointUpper;
|
||||||
|
|
||||||
|
@Schema(title = "是否支持打卡叠加得分,0不支持,1支持")
|
||||||
|
private Integer supportCum;
|
||||||
|
|
||||||
|
@Schema(title = "是否需要填写补充信息,0不需要,1需要")
|
||||||
|
private Integer needRemark;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -40,9 +41,11 @@ public class UserPoints implements Serializable {
|
|||||||
@Schema(title = "用户部门")
|
@Schema(title = "用户部门")
|
||||||
private String orgCode;
|
private String orgCode;
|
||||||
|
|
||||||
|
@Excel(name = "当前可用积分", width = 15, orderNum = "7")
|
||||||
@Schema(title = "当前可用积分")
|
@Schema(title = "当前可用积分")
|
||||||
private BigDecimal points;
|
private BigDecimal points;
|
||||||
|
|
||||||
|
@Excel(name = "累计获得积分", width = 15, orderNum = "8")
|
||||||
@Schema(title = "历史获得的积分总和")
|
@Schema(title = "历史获得的积分总和")
|
||||||
private BigDecimal addPointsSum;
|
private BigDecimal addPointsSum;
|
||||||
|
|
||||||
@@ -55,6 +58,7 @@ public class UserPoints implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 积分兑换记录总和,只记录积分兑换
|
* 积分兑换记录总和,只记录积分兑换
|
||||||
*/
|
*/
|
||||||
|
@Excel(name = "累计兑换积分", width = 15, orderNum = "9")
|
||||||
@Schema(title = "历史兑换积分总和")
|
@Schema(title = "历史兑换积分总和")
|
||||||
private BigDecimal exchangePointsSum;
|
private BigDecimal exchangePointsSum;
|
||||||
|
|
||||||
@@ -71,6 +75,7 @@ public class UserPoints implements Serializable {
|
|||||||
@Schema(title = "最新一天消耗积分的总和")
|
@Schema(title = "最新一天消耗积分的总和")
|
||||||
private BigDecimal latestDeductDaySum;
|
private BigDecimal latestDeductDaySum;
|
||||||
|
|
||||||
|
@Excel(name = "最近一次打卡时间", width = 25, format = "yyyy-MM-dd HH:mm:ss", orderNum = "10")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
@Schema(title = "最新获得积分的日期")
|
@Schema(title = "最新获得积分的日期")
|
||||||
|
|||||||
+18
@@ -2,6 +2,7 @@ package com.renkang.bank.model.vo;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@@ -14,27 +15,44 @@ import java.math.BigDecimal;
|
|||||||
@Data
|
@Data
|
||||||
public class UserPointRankingVO {
|
public class UserPointRankingVO {
|
||||||
|
|
||||||
|
@Excel(name = "排名", width = 15, orderNum = "8")
|
||||||
@Schema(title = "排名")
|
@Schema(title = "排名")
|
||||||
private Integer ranking;
|
private Integer ranking;
|
||||||
|
|
||||||
@Schema(title = "用户id")
|
@Schema(title = "用户id")
|
||||||
private String userId;
|
private String userId;
|
||||||
|
|
||||||
|
@Excel(name = "姓名", width = 15, orderNum = "3")
|
||||||
@Schema(title = "姓名")
|
@Schema(title = "姓名")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
|
@Excel(name = "员工编号", width = 15, orderNum = "4")
|
||||||
|
@Schema(title = "员工编号")
|
||||||
|
private String workNo;
|
||||||
|
|
||||||
|
@Excel(name = "年龄", width = 15, orderNum = "6")
|
||||||
|
@Schema(title = "年龄")
|
||||||
|
private Integer age;
|
||||||
|
|
||||||
@Schema(title = "头像")
|
@Schema(title = "头像")
|
||||||
private String avatar;
|
private String avatar;
|
||||||
|
|
||||||
|
@Excel(name = "所属单位", width = 15, orderNum = "1")
|
||||||
@Schema(title = "单位名称")
|
@Schema(title = "单位名称")
|
||||||
private String orgName;
|
private String orgName;
|
||||||
|
|
||||||
|
@Excel(name = "所属部门", width = 15, orderNum = "2")
|
||||||
@Schema(title = "部门名称")
|
@Schema(title = "部门名称")
|
||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
@Schema(title = "性别")
|
@Schema(title = "性别")
|
||||||
private Integer sex;
|
private Integer sex;
|
||||||
|
|
||||||
|
@Excel(name = "性别", width = 15, orderNum = "5")
|
||||||
|
@Schema(title = "性别")
|
||||||
|
private String sexName;
|
||||||
|
|
||||||
|
@Excel(name = "日累计积分", width = 15, orderNum = "7")
|
||||||
@Schema(title = "积分")
|
@Schema(title = "积分")
|
||||||
private BigDecimal pointSum;
|
private BigDecimal pointSum;
|
||||||
}
|
}
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package com.renkang.bank.model.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户积分详情VO
|
||||||
|
*
|
||||||
|
* @author feng
|
||||||
|
* @date 2026-02-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(title = "用户积分详情VO", description = "用户积分详情VO")
|
||||||
|
public class UserPointsDeductDetailsVO {
|
||||||
|
|
||||||
|
@Schema(title = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(title = "用户ID")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Excel(name = "积分变化", width = 15, orderNum = "2")
|
||||||
|
@Schema(title = "本次积分变化")
|
||||||
|
private BigDecimal points;
|
||||||
|
|
||||||
|
@Excel(name = "支出信息", width = 25, orderNum = "1")
|
||||||
|
@Schema(title = "补充信息")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(title = "关联业务ID(规则ID,礼品兑换订单Id等)")
|
||||||
|
private Long businessId;
|
||||||
|
|
||||||
|
@Excel(name = "支出时间", width = 25, format = "yyyy-MM-dd HH:mm:ss", orderNum = "3")
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
@Schema(title = "创建时间")
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package com.renkang.bank.model.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户积分详情VO
|
||||||
|
*
|
||||||
|
* @author feng
|
||||||
|
* @date 2026-02-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(title = "用户积分详情VO", description = "用户积分详情VO")
|
||||||
|
public class UserPointsDetailsVO {
|
||||||
|
|
||||||
|
@Schema(title = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(title = "用户ID")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Excel(name = "积分变化", width = 15, orderNum = "4")
|
||||||
|
@Schema(title = "本次积分变化")
|
||||||
|
private BigDecimal points;
|
||||||
|
|
||||||
|
@Excel(name = "补充信息", width = 15, orderNum = "2")
|
||||||
|
@Schema(title = "补充信息")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(title = "关联业务ID(规则ID,礼品兑换订单Id等)")
|
||||||
|
private Long businessId;
|
||||||
|
|
||||||
|
@Excel(name = "健康行为", width = 25, orderNum = "1")
|
||||||
|
@Schema(title = "关联业务详情(可选择性的冗余业务详情:待定)")
|
||||||
|
private String businessInfo;
|
||||||
|
|
||||||
|
@Excel(name = "打卡时间", width = 25, format = "yyyy-MM-dd HH:mm:ss", orderNum = "5")
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
@Schema(title = "创建时间")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@Excel(name = "行为周期", width = 15, replace = {"次_1", "日_2", "周_3", "月_4", "季度_5", "年_6"}, orderNum = "3")
|
||||||
|
@Schema(title = "问答频次(1-单次,2-日/次,3-周/次,4-月/次,5-季度/次,6-年/次)")
|
||||||
|
private Integer frequency;
|
||||||
|
}
|
||||||
+7
@@ -4,6 +4,7 @@ import com.renkang.bank.model.po.UserPoints;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author pj
|
* @author pj
|
||||||
@@ -15,21 +16,27 @@ import lombok.EqualsAndHashCode;
|
|||||||
@Data
|
@Data
|
||||||
public class UserPointsListVO extends UserPoints {
|
public class UserPointsListVO extends UserPoints {
|
||||||
|
|
||||||
|
@Excel(name = "姓名", width = 15, orderNum = "3")
|
||||||
@Schema(title = "姓名")
|
@Schema(title = "姓名")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
|
@Excel(name = "员工编号", width = 15, orderNum = "4")
|
||||||
@Schema(title = "员工编号")
|
@Schema(title = "员工编号")
|
||||||
private String workNo;
|
private String workNo;
|
||||||
|
|
||||||
|
@Excel(name = "所属单位", width = 15, orderNum = "1")
|
||||||
@Schema(title = "所属单位名称")
|
@Schema(title = "所属单位名称")
|
||||||
private String orgName;
|
private String orgName;
|
||||||
|
|
||||||
|
@Excel(name = "所属部门", width = 15, orderNum = "2")
|
||||||
@Schema(title = "所属部门名称")
|
@Schema(title = "所属部门名称")
|
||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
|
@Excel(name = "性别", width = 15, orderNum = "5")
|
||||||
@Schema(title = "性别")
|
@Schema(title = "性别")
|
||||||
private String sexName;
|
private String sexName;
|
||||||
|
|
||||||
|
@Excel(name = "年龄", width = 15, orderNum = "6")
|
||||||
@Schema(title = "年龄")
|
@Schema(title = "年龄")
|
||||||
private Integer age;
|
private Integer age;
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-5
@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import com.renkang.bank.model.dto.UpdUserPointsDTO;
|
import com.renkang.bank.model.dto.UpdUserPointsDTO;
|
||||||
import com.renkang.bank.model.dto.UserPointsPageDTO;
|
import com.renkang.bank.model.dto.UserPointsPageDTO;
|
||||||
import com.renkang.bank.model.dto.UserPointsRankingDTO;
|
import com.renkang.bank.model.dto.UserPointsRankingDTO;
|
||||||
import com.renkang.bank.model.po.UserPointsDetails;
|
|
||||||
import com.renkang.bank.model.vo.*;
|
import com.renkang.bank.model.vo.*;
|
||||||
import com.renkang.bank.service.IUserPointsService;
|
import com.renkang.bank.service.IUserPointsService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -30,9 +29,9 @@ public class UserPointsController {
|
|||||||
private IUserPointsService iUserPointsService;
|
private IUserPointsService iUserPointsService;
|
||||||
|
|
||||||
@Operation(summary = "测试积分变更", description = "测试积分变更")
|
@Operation(summary = "测试积分变更", description = "测试积分变更")
|
||||||
@PostMapping(value = "/updateUserPoints")
|
@PostMapping(value = "/testUpdateUserPoints")
|
||||||
public Result<Boolean> updateUserPoints(@RequestBody UpdUserPointsDTO dto) {
|
public Result<Boolean> testUpdateUserPoints(@RequestBody UpdUserPointsDTO dto) {
|
||||||
return Result.OK(iUserPointsService.updateUserPoints(dto));
|
return Result.OK(iUserPointsService.testUpdateUserPoints(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "用户积分信息-分页列表", description = "用户积分信息-分页列表")
|
@Operation(summary = "用户积分信息-分页列表", description = "用户积分信息-分页列表")
|
||||||
@@ -41,12 +40,26 @@ public class UserPointsController {
|
|||||||
return Result.OK(iUserPointsService.pageList(dto));
|
return Result.OK(iUserPointsService.pageList(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户积分信息-分页列表(导出)", description = "用户积分信息-分页列表(导出)")
|
||||||
|
@PostMapping(value = "/exportUserPoints")
|
||||||
|
public Result<String> exportUserPoints(@RequestBody UserPointsPageDTO dto) {
|
||||||
|
iUserPointsService.exportUserPoints(dto);
|
||||||
|
return Result.OK("导出成功,请在导出记录查看");
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "用户积分信息-积分明细", description = "用户积分信息-积分明细")
|
@Operation(summary = "用户积分信息-积分明细", description = "用户积分信息-积分明细")
|
||||||
@PostMapping(value = "/detailPageList")
|
@PostMapping(value = "/detailPageList")
|
||||||
public Result<IPage<UserPointsDetails>> detailPageList(@RequestBody UserPointsPageDTO dto) {
|
public Result<IPage<UserPointsDetailsVO>> detailPageList(@RequestBody UserPointsPageDTO dto) {
|
||||||
return Result.OK(iUserPointsService.detailPageList(dto));
|
return Result.OK(iUserPointsService.detailPageList(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户积分信息-积分明细(导出)", description = "用户积分信息-积分明细(导出)")
|
||||||
|
@PostMapping(value = "/exportPointsDetail")
|
||||||
|
public Result<String> exportPointsDetail(@RequestBody UserPointsPageDTO dto) {
|
||||||
|
iUserPointsService.exportPointsDetail(dto);
|
||||||
|
return Result.OK("导出成功,请在导出记录查看");
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "用户积分信息-扣除积分", description = "用户积分信息-扣除积分")
|
@Operation(summary = "用户积分信息-扣除积分", description = "用户积分信息-扣除积分")
|
||||||
@PostMapping(value = "/deductPoint")
|
@PostMapping(value = "/deductPoint")
|
||||||
public Result<Boolean> deductPoint(@RequestBody UpdUserPointsDTO dto) {
|
public Result<Boolean> deductPoint(@RequestBody UpdUserPointsDTO dto) {
|
||||||
@@ -59,6 +72,26 @@ public class UserPointsController {
|
|||||||
return Result.OK(iUserPointsService.clockInMonitor(dto));
|
return Result.OK(iUserPointsService.clockInMonitor(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户积分信息-员工打卡监测(导出)", description = "用户积分信息-员工打卡监测(导出)")
|
||||||
|
@PostMapping(value = "/exportClockInMonitor")
|
||||||
|
public Result<String> exportClockInMonitor(@RequestBody UserPointsPageDTO dto) {
|
||||||
|
iUserPointsService.exportClockInMonitor(dto);
|
||||||
|
return Result.OK("导出成功,请在导出记录查看");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户积分信息-员工积分排名", description = "用户积分信息-员工积分排名")
|
||||||
|
@PostMapping(value = "/userRanking")
|
||||||
|
public Result<IPage<UserPointRankingVO>> userRanking(@RequestBody UserPointsRankingDTO dto) {
|
||||||
|
return Result.OK(iUserPointsService.userRanking(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户积分信息-员工积分排名(导出)", description = "用户积分信息-员工积分排名(导出)")
|
||||||
|
@PostMapping(value = "/exportUserRanking")
|
||||||
|
public Result<String> exportUserRanking(@RequestBody UserPointsRankingDTO dto) {
|
||||||
|
iUserPointsService.exportUserRanking(dto);
|
||||||
|
return Result.OK("导出成功,请在导出记录查看");
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "用户积分信息-app我的积分", description = "用户积分信息-app我的积分")
|
@Operation(summary = "用户积分信息-app我的积分", description = "用户积分信息-app我的积分")
|
||||||
@GetMapping(value = "/app/myPoints")
|
@GetMapping(value = "/app/myPoints")
|
||||||
public Result<UserPointsInfoVO> myPoints() {
|
public Result<UserPointsInfoVO> myPoints() {
|
||||||
|
|||||||
+3
@@ -2,6 +2,7 @@ package com.renkang.bank.mapper;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.github.yulichang.base.MPJBaseMapper;
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.renkang.bank.model.dto.UserPointsRankingDTO;
|
||||||
import com.renkang.bank.model.po.UserPointsDetails;
|
import com.renkang.bank.model.po.UserPointsDetails;
|
||||||
import com.renkang.bank.model.vo.UserPointRankingVO;
|
import com.renkang.bank.model.vo.UserPointRankingVO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
@@ -11,6 +12,8 @@ import java.util.Date;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface UserPointsDetailsMapper extends MPJBaseMapper<UserPointsDetails> {
|
public interface UserPointsDetailsMapper extends MPJBaseMapper<UserPointsDetails> {
|
||||||
|
|
||||||
|
IPage<UserPointRankingVO> userRanking(IPage<UserPointRankingVO> page, Date startTime, Date endTime, UserPointsRankingDTO dto);
|
||||||
|
|
||||||
IPage<UserPointRankingVO> pointRanking(IPage<UserPointRankingVO> page, Date startTime, Date endTime, String secondOrgCode);
|
IPage<UserPointRankingVO> pointRanking(IPage<UserPointRankingVO> page, Date startTime, Date endTime, String secondOrgCode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+47
@@ -3,6 +3,53 @@
|
|||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.renkang.bank.mapper.UserPointsDetailsMapper">
|
<mapper namespace="com.renkang.bank.mapper.UserPointsDetailsMapper">
|
||||||
|
|
||||||
|
<select id="userRanking" resultType="com.renkang.bank.model.vo.UserPointRankingVO">
|
||||||
|
<!--定义公共表表达式用于计算每个用户的总积分-->
|
||||||
|
WITH UserPointsSummary AS (
|
||||||
|
SELECT
|
||||||
|
a.user_id,
|
||||||
|
SUM(CASE WHEN a.change_type = 1 THEN a.points ELSE 0 END) -
|
||||||
|
SUM(CASE WHEN a.change_type = 2 THEN a.points ELSE 0 END) AS pointSum
|
||||||
|
FROM
|
||||||
|
bank_user_points_details a
|
||||||
|
INNER JOIN bank_user_points b ON a.user_id = b.user_id
|
||||||
|
<if test="null != dto.orgCode and '' != dto.orgCode">
|
||||||
|
AND b.org_code like concat(#{dto.orgCode},'%')
|
||||||
|
</if>
|
||||||
|
<if test="null != dto.deptCode and '' != dto.deptCode">
|
||||||
|
AND b.org_code like concat(#{dto.deptCode},'%')
|
||||||
|
</if>
|
||||||
|
GROUP BY
|
||||||
|
a.user_id
|
||||||
|
),
|
||||||
|
<!--计算所有用户的排名-->
|
||||||
|
RankedUsers AS (
|
||||||
|
SELECT
|
||||||
|
user_id,
|
||||||
|
pointSum,
|
||||||
|
ROW_NUMBER() OVER (ORDER BY pointSum DESC, user_id ASC) AS ranking
|
||||||
|
FROM
|
||||||
|
UserPointsSummary
|
||||||
|
)
|
||||||
|
<!--主查询:根据是否传入用户 ID 返回结果-->
|
||||||
|
SELECT
|
||||||
|
ranking,
|
||||||
|
user_id,
|
||||||
|
pointSum
|
||||||
|
FROM
|
||||||
|
RankedUsers
|
||||||
|
<where>
|
||||||
|
<if test="dto.userIds != null and dto.userIds.size() > 0">
|
||||||
|
AND user_id in
|
||||||
|
<foreach collection="dto.userIds" item="userId" open="(" separator="," close=")">
|
||||||
|
#{userId}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY
|
||||||
|
pointSum DESC, user_id ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="pointRanking" resultType="com.renkang.bank.model.vo.UserPointRankingVO">
|
<select id="pointRanking" resultType="com.renkang.bank.model.vo.UserPointRankingVO">
|
||||||
WITH UserPointsSummary AS (
|
WITH UserPointsSummary AS (
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
package com.renkang.bank.redis.config;
|
||||||
|
|
||||||
|
import com.renkang.bank.redis.listener.BankAsyncListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.system.vo.SysUserModel;
|
||||||
|
import org.jeecg.enums.ServerEnum;
|
||||||
|
import org.jeecg.redis.RedisStreamKeyEnum;
|
||||||
|
import org.jeecg.util.IPUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.task.TaskExecutor;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.connection.stream.Consumer;
|
||||||
|
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
||||||
|
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||||
|
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||||
|
import org.springframework.data.redis.hash.Jackson2HashMapper;
|
||||||
|
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shunzhi Jiang
|
||||||
|
* @since 2023/6/13
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class BankRedisStreamConfig {
|
||||||
|
|
||||||
|
private final Consumer consumer = Consumer.from(ServerEnum.BANK.getCustomGroup(), IPUtil.getCurrentServiceIp());
|
||||||
|
|
||||||
|
private final ReadOffset readOffset = ReadOffset.lastConsumed();
|
||||||
|
|
||||||
|
private BankAsyncListener bankAsyncListener;
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setDepartAsyncListener(BankAsyncListener bankAsyncListener) {
|
||||||
|
this.bankAsyncListener = bankAsyncListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, SysUserModel>> sysUserModelInterveneStreamOptions(
|
||||||
|
@Qualifier("asyncTaskExecutor") TaskExecutor taskExecutor,
|
||||||
|
StringRedisSerializer stringSerializer,
|
||||||
|
GenericJackson2JsonRedisSerializer jsonSerializer
|
||||||
|
) {
|
||||||
|
return StreamMessageListenerContainer.StreamMessageListenerContainerOptions
|
||||||
|
.builder()
|
||||||
|
// 一次最多获取多少条消息
|
||||||
|
.batchSize(10)
|
||||||
|
// 运行 Stream 的 poll task
|
||||||
|
.executor(taskExecutor)
|
||||||
|
// 可以理解为 Stream Key 的序列化方式
|
||||||
|
.keySerializer(stringSerializer)
|
||||||
|
// 可以理解为 Stream 后方的字段的 key 的序列化方式
|
||||||
|
.hashKeySerializer(stringSerializer)
|
||||||
|
// 可以理解为 Stream 后方的字段的 value 的序列化方式
|
||||||
|
.hashValueSerializer(jsonSerializer)
|
||||||
|
// ObjectRecord 时,将 对象的 filed 和 value 转换成一个 Map 比如:将Book对象转换成map
|
||||||
|
.objectMapper(new Jackson2HashMapper(true))
|
||||||
|
// 将发送到Stream中的Record转换成ObjectRecord,转换成具体的类型是这个地方指定的类型
|
||||||
|
.targetType(SysUserModel.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||||
|
public StreamMessageListenerContainer<String, ObjectRecord<String, SysUserModel>> sysUserModelInterveneStreamMessageListenerContainer(
|
||||||
|
RedisConnectionFactory redisConnectionFactory,
|
||||||
|
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, SysUserModel>> options
|
||||||
|
) {
|
||||||
|
StreamMessageListenerContainer<String, ObjectRecord<String, SysUserModel>> container =
|
||||||
|
StreamMessageListenerContainer.create(redisConnectionFactory, options);
|
||||||
|
// 修改用户
|
||||||
|
container.receive(
|
||||||
|
consumer,
|
||||||
|
createStreamOffset(RedisStreamKeyEnum.CHANGE_USER_DEPART),
|
||||||
|
bankAsyncListener
|
||||||
|
);
|
||||||
|
log.info("redisStream配置完成");
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreamOffset<String> createStreamOffset(RedisStreamKeyEnum streamKeyEnum) {
|
||||||
|
return StreamOffset.create(streamKeyEnum.getKey(), readOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package com.renkang.bank.redis.listener;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.renkang.bank.mapper.UserPointsMapper;
|
||||||
|
import com.renkang.bank.model.po.UserPoints;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.system.vo.SysUserModel;
|
||||||
|
import org.jeecg.enums.ServerEnum;
|
||||||
|
import org.jeecg.redis.RedisStreamKeyEnum;
|
||||||
|
import org.jeecg.redis.RedisStreamUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
||||||
|
import org.springframework.data.redis.connection.stream.RecordId;
|
||||||
|
import org.springframework.data.redis.stream.StreamListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Shunzhi Jiang
|
||||||
|
* @since 2023/6/13
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class BankAsyncListener implements StreamListener<String, ObjectRecord<String, SysUserModel>> {
|
||||||
|
@Autowired
|
||||||
|
private RedisStreamUtil redisStreamUtil;
|
||||||
|
@Autowired
|
||||||
|
private UserPointsMapper userPointsMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(ObjectRecord<String, SysUserModel> message) {
|
||||||
|
String streamKey = message.getStream();
|
||||||
|
SysUserModel model = message.getValue();
|
||||||
|
log.info("收到Redis Stream Key:{}", streamKey);
|
||||||
|
RecordId recordId = message.getId();
|
||||||
|
log.info("ID:{}, DATA: {}", recordId, JSON.toJSONString(model));
|
||||||
|
try {
|
||||||
|
if (RedisStreamKeyEnum.CHANGE_USER_DEPART.getKey().equals(streamKey)) {
|
||||||
|
if (StrUtil.isBlank(model.getId()) || StrUtil.isBlank(model.getOrgCode())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 修改用户积分主表单位信息
|
||||||
|
updateUserPointsInfo(model);
|
||||||
|
}
|
||||||
|
ack(message);
|
||||||
|
log.info("Redis Stream消费成功:Key: {}, ID: {}", streamKey, recordId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Redis Stream消费异常:Key: {}, ID: {}", streamKey, recordId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUserPointsInfo(SysUserModel model) {
|
||||||
|
userPointsMapper.update(new LambdaUpdateWrapper<UserPoints>()
|
||||||
|
.eq(UserPoints::getUserId, model.getId())
|
||||||
|
.set(UserPoints::getOrgCode, model.getOrgCode())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ack(ObjectRecord<String, SysUserModel> message) {
|
||||||
|
redisStreamUtil.ack(ServerEnum.BANK.getCustomGroup(), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-3
@@ -4,23 +4,32 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import com.renkang.bank.model.dto.UpdUserPointsDTO;
|
import com.renkang.bank.model.dto.UpdUserPointsDTO;
|
||||||
import com.renkang.bank.model.dto.UserPointsPageDTO;
|
import com.renkang.bank.model.dto.UserPointsPageDTO;
|
||||||
import com.renkang.bank.model.dto.UserPointsRankingDTO;
|
import com.renkang.bank.model.dto.UserPointsRankingDTO;
|
||||||
import com.renkang.bank.model.po.UserPointsDetails;
|
|
||||||
import com.renkang.bank.model.vo.*;
|
import com.renkang.bank.model.vo.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface IUserPointsService {
|
public interface IUserPointsService {
|
||||||
|
|
||||||
boolean updateUserPoints(UpdUserPointsDTO dto);
|
boolean testUpdateUserPoints(UpdUserPointsDTO dto);
|
||||||
|
|
||||||
IPage<UserPointsListVO> pageList(UserPointsPageDTO dto);
|
IPage<UserPointsListVO> pageList(UserPointsPageDTO dto);
|
||||||
|
|
||||||
IPage<UserPointsDetails> detailPageList(UserPointsPageDTO dto);
|
void exportUserPoints(UserPointsPageDTO dto);
|
||||||
|
|
||||||
|
IPage<UserPointsDetailsVO> detailPageList(UserPointsPageDTO dto);
|
||||||
|
|
||||||
|
void exportPointsDetail(UserPointsPageDTO dto);
|
||||||
|
|
||||||
boolean deductPoint(UpdUserPointsDTO dto);
|
boolean deductPoint(UpdUserPointsDTO dto);
|
||||||
|
|
||||||
IPage<UserPointsMonitorVO> clockInMonitor(UserPointsPageDTO dto);
|
IPage<UserPointsMonitorVO> clockInMonitor(UserPointsPageDTO dto);
|
||||||
|
|
||||||
|
void exportClockInMonitor(UserPointsPageDTO dto);
|
||||||
|
|
||||||
|
IPage<UserPointRankingVO> userRanking(UserPointsRankingDTO dto);
|
||||||
|
|
||||||
|
void exportUserRanking(UserPointsRankingDTO dto);
|
||||||
|
|
||||||
UserPointsInfoVO myPoints();
|
UserPointsInfoVO myPoints();
|
||||||
|
|
||||||
List<UserPointsTaskVO> pointsTaskList();
|
List<UserPointsTaskVO> pointsTaskList();
|
||||||
|
|||||||
+264
-28
@@ -1,10 +1,10 @@
|
|||||||
package com.renkang.bank.service.impl;
|
package com.renkang.bank.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.date.DateTime;
|
import cn.hutool.core.date.DateTime;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@@ -27,13 +27,18 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jeecg.common.exception.ExceptionAssertsUtil;
|
import org.jeecg.common.exception.ExceptionAssertsUtil;
|
||||||
|
import org.jeecg.common.export.GenericExportHandler;
|
||||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.enums.ExportExcelEnum;
|
||||||
import org.jeecg.global.GlobalUtils;
|
import org.jeecg.global.GlobalUtils;
|
||||||
import org.jeecg.modules.manager.ISysCache;
|
import org.jeecg.modules.manager.ISysCache;
|
||||||
import org.jeecg.modules.system.entity.SysDepart;
|
import org.jeecg.modules.system.entity.SysDepart;
|
||||||
|
import org.jeecg.modules.system.entity.SysUser;
|
||||||
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -67,7 +72,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
// 动态排序
|
// 动态排序
|
||||||
if (StringUtils.isNotBlank(dto.getColumn())) {
|
if (StringUtils.isNotBlank(dto.getColumn())) {
|
||||||
// 驼峰转下划线
|
// 驼峰转下划线
|
||||||
String column = StrUtil.toUnderlineCase(dto.getColumn());
|
String column = CharSequenceUtil.toUnderlineCase(dto.getColumn());
|
||||||
if ("asc".equalsIgnoreCase(dto.getOrder())) {
|
if ("asc".equalsIgnoreCase(dto.getOrder())) {
|
||||||
queryWrapper.orderByAsc(column);
|
queryWrapper.orderByAsc(column);
|
||||||
} else {
|
} else {
|
||||||
@@ -95,11 +100,11 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
vo.setSexName(ObjectUtil.isNotNull(loginUser.getSex()) && loginUser.getSex().equals(1) ? "女" : "男");
|
vo.setSexName(ObjectUtil.isNotNull(loginUser.getSex()) && loginUser.getSex().equals(1) ? "女" : "男");
|
||||||
vo.setAge(ObjectUtil.isNotNull(loginUser.getAge()) && loginUser.getAge() > 0 ? loginUser.getAge() : null);
|
vo.setAge(ObjectUtil.isNotNull(loginUser.getAge()) && loginUser.getAge() > 0 ? loginUser.getAge() : null);
|
||||||
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(vo.getOrgCode()));
|
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(vo.getOrgCode()));
|
||||||
if (ObjUtil.isNotEmpty(secondDepart)) {
|
if (ObjectUtil.isNotEmpty(secondDepart)) {
|
||||||
vo.setDeptName(secondDepart.getDepartName());
|
vo.setDeptName(secondDepart.getDepartName());
|
||||||
}
|
}
|
||||||
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(vo.getOrgCode()));
|
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(vo.getOrgCode()));
|
||||||
if (ObjUtil.isNotEmpty(thirdDepart)) {
|
if (ObjectUtil.isNotEmpty(thirdDepart)) {
|
||||||
vo.setOrgName(thirdDepart.getDepartName());
|
vo.setOrgName(thirdDepart.getDepartName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,28 +113,106 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
return voPage;
|
return voPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AsyncTaskExecutor asyncTaskExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 导出数据(设备数据)
|
||||||
|
* @author PengJ
|
||||||
|
* @date 2025/11/18 14:57
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IPage<UserPointsDetails> detailPageList(UserPointsPageDTO dto) {
|
public void exportUserPoints(UserPointsPageDTO dto) {
|
||||||
|
GenericExportHandler<UserPointsListVO, UserPointsPageDTO> handler =
|
||||||
|
new GenericExportHandler<>(
|
||||||
|
UserPointsListVO.class,
|
||||||
|
ExportExcelEnum.BANK_USER_POINTS,
|
||||||
|
asyncTaskExecutor,
|
||||||
|
log,
|
||||||
|
filter -> {
|
||||||
|
filter.setPageNo(1);
|
||||||
|
filter.setPageSize(-1);
|
||||||
|
List<UserPointsListVO> records = pageList(filter).getRecords();
|
||||||
|
return GenericExportHandler.handleData(records, UserPointsListVO.class);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handler.exportAsync(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<UserPointsDetailsVO> detailPageList(UserPointsPageDTO dto) {
|
||||||
if (StringUtils.isBlank(dto.getUserId())) {
|
if (StringUtils.isBlank(dto.getUserId())) {
|
||||||
ExceptionAssertsUtil.fail("用户id不能为空");
|
ExceptionAssertsUtil.fail("用户id不能为空");
|
||||||
}
|
}
|
||||||
if (ObjectUtil.isNull(dto.getChangeType())) {
|
if (ObjectUtil.isNull(dto.getChangeType())) {
|
||||||
ExceptionAssertsUtil.fail("收支类型不能为空");
|
ExceptionAssertsUtil.fail("收支类型不能为空");
|
||||||
}
|
}
|
||||||
Page<UserPointsDetails> page = new Page<>(dto.getPageNo(), dto.getPageSize());
|
Page<UserPointsDetailsVO> page = new Page<>(dto.getPageNo(), dto.getPageSize());
|
||||||
LambdaQueryWrapper<UserPointsDetails> queryWrapper = new LambdaQueryWrapper<>();
|
MPJLambdaWrapper<UserPointsDetails> queryWrapper = new MPJLambdaWrapper<>();
|
||||||
queryWrapper.select(UserPointsDetails::getId, UserPointsDetails::getBusinessInfo,
|
queryWrapper.select(UserPointsDetails::getId, UserPointsDetails::getBusinessInfo,
|
||||||
UserPointsDetails::getPoints, UserPointsDetails::getCreateTime, UserPointsDetails::getChangeType);
|
UserPointsDetails::getPoints, UserPointsDetails::getCreateTime, UserPointsDetails::getChangeType,
|
||||||
|
UserPointsDetails::getRemark, UserPointsDetails::getBusinessId, UserPointsDetails::getUserId);
|
||||||
|
queryWrapper.select(PointsRule::getFrequency);
|
||||||
|
queryWrapper.leftJoin(PointsRule.class, PointsRule::getId, UserPointsDetails::getBusinessId);
|
||||||
queryWrapper.eq(UserPointsDetails::getUserId, dto.getUserId());
|
queryWrapper.eq(UserPointsDetails::getUserId, dto.getUserId());
|
||||||
queryWrapper.eq(UserPointsDetails::getChangeType, dto.getChangeType());
|
queryWrapper.eq(UserPointsDetails::getChangeType, dto.getChangeType());
|
||||||
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
|
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
|
||||||
return userPointsDetailsMapper.selectPage(page, queryWrapper);
|
Page<UserPointsDetailsVO> voPage = userPointsDetailsMapper.selectJoinPage(page, UserPointsDetailsVO.class, queryWrapper);
|
||||||
|
if (dto.getChangeType().equals(HealthBankConstants.CHANGE_TYPE_DEDUCT)) {
|
||||||
|
voPage.getRecords().forEach(vo -> {
|
||||||
|
vo.setPoints(vo.getPoints().multiply(new BigDecimal(-1)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return voPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportPointsDetail(UserPointsPageDTO dto) {
|
||||||
|
if (StringUtils.isBlank(dto.getUserId())) {
|
||||||
|
ExceptionAssertsUtil.fail("用户id不能为空");
|
||||||
|
}
|
||||||
|
if (ObjectUtil.isNull(dto.getChangeType())) {
|
||||||
|
ExceptionAssertsUtil.fail("收支类型不能为空");
|
||||||
|
}
|
||||||
|
if (dto.getChangeType().equals(HealthBankConstants.CHANGE_TYPE_ADD)) {
|
||||||
|
GenericExportHandler<UserPointsDetailsVO, UserPointsPageDTO> handler =
|
||||||
|
new GenericExportHandler<>(
|
||||||
|
UserPointsDetailsVO.class,
|
||||||
|
ExportExcelEnum.BANK_POINTS_DETAIL,
|
||||||
|
asyncTaskExecutor,
|
||||||
|
log,
|
||||||
|
filter -> {
|
||||||
|
filter.setPageNo(1);
|
||||||
|
filter.setPageSize(-1);
|
||||||
|
List<UserPointsDetailsVO> records = detailPageList(filter).getRecords();
|
||||||
|
return GenericExportHandler.handleData(records, UserPointsDetailsVO.class);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handler.exportAsync(dto);
|
||||||
|
} else if (dto.getChangeType().equals(HealthBankConstants.CHANGE_TYPE_DEDUCT)) {
|
||||||
|
GenericExportHandler<UserPointsDeductDetailsVO, UserPointsPageDTO> handler =
|
||||||
|
new GenericExportHandler<>(
|
||||||
|
UserPointsDeductDetailsVO.class,
|
||||||
|
ExportExcelEnum.BANK_POINTS_DETAIL,
|
||||||
|
asyncTaskExecutor,
|
||||||
|
log,
|
||||||
|
filter -> {
|
||||||
|
filter.setPageNo(1);
|
||||||
|
filter.setPageSize(-1);
|
||||||
|
List<UserPointsDetailsVO> records = detailPageList(filter).getRecords();
|
||||||
|
List<UserPointsDeductDetailsVO> voList = BeanUtil.copyToList(records, UserPointsDeductDetailsVO.class);
|
||||||
|
return GenericExportHandler.handleData(voList, UserPointsDeductDetailsVO.class);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handler.exportAsync(dto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deductPoint(UpdUserPointsDTO dto) {
|
public boolean deductPoint(UpdUserPointsDTO dto) {
|
||||||
dto.setSourceType(PointsSourceEnum.TYPE_2_VIOLATION.getSource());
|
dto.setSourceType(PointsSourceEnum.TYPE_2_VIOLATION.getSource());
|
||||||
return updateUserPoints(dto);
|
LoginUser loginUser = GlobalUtils.getLoginUser();
|
||||||
|
return updateUserPoints(dto, loginUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -138,14 +221,12 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
MPJLambdaWrapper<UserPointsDetails> queryWrapper = new MPJLambdaWrapper<>();
|
MPJLambdaWrapper<UserPointsDetails> queryWrapper = new MPJLambdaWrapper<>();
|
||||||
queryWrapper.selectAll(UserPointsDetails.class);
|
queryWrapper.selectAll(UserPointsDetails.class);
|
||||||
queryWrapper.select(PointsRule::getFrequency);
|
queryWrapper.select(PointsRule::getFrequency);
|
||||||
queryWrapper.eq(UserPointsDetails::getChangeType, 1);
|
queryWrapper.eq(UserPointsDetails::getSource, PointsSourceEnum.TYPE_1_SURVEY.getSource());
|
||||||
if (StringUtils.isNotBlank(dto.getRealName()) || StringUtils.isNotBlank(dto.getWorkNo())) {
|
if (StringUtils.isNotBlank(dto.getRealName()) || StringUtils.isNotBlank(dto.getWorkNo())) {
|
||||||
//根据姓名和工号查询用户
|
//根据姓名和工号查询用户
|
||||||
List<String> userIdList = iSysBaseAPI.getUserIdsByDepartAndUsername(null, dto.getRealName(), null, dto.getWorkNo());
|
List<String> userIdList = iSysBaseAPI.getUserIdsByDepartAndUsername(null, dto.getRealName(), null, dto.getWorkNo());
|
||||||
queryWrapper.in(!userIdList.isEmpty(), UserPoints::getUserId, userIdList);
|
queryWrapper.in(!userIdList.isEmpty(), UserPoints::getUserId, userIdList);
|
||||||
}
|
}
|
||||||
// 动态排序
|
|
||||||
|
|
||||||
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
|
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
|
||||||
if (StringUtils.isNotBlank(dto.getOrgCode())) {
|
if (StringUtils.isNotBlank(dto.getOrgCode())) {
|
||||||
queryWrapper.likeRight(UserPoints::getOrgCode, dto.getOrgCode());
|
queryWrapper.likeRight(UserPoints::getOrgCode, dto.getOrgCode());
|
||||||
@@ -154,6 +235,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
queryWrapper.likeRight(UserPoints::getOrgCode, dto.getDeptCode());
|
queryWrapper.likeRight(UserPoints::getOrgCode, dto.getDeptCode());
|
||||||
}
|
}
|
||||||
queryWrapper.leftJoin(PointsRule.class, PointsRule::getId, UserPointsDetails::getBusinessId);
|
queryWrapper.leftJoin(PointsRule.class, PointsRule::getId, UserPointsDetails::getBusinessId);
|
||||||
|
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
|
||||||
Page<UserPointsMonitorVO> voPage = userPointsDetailsMapper.selectJoinPage(page, UserPointsMonitorVO.class, queryWrapper);
|
Page<UserPointsMonitorVO> voPage = userPointsDetailsMapper.selectJoinPage(page, UserPointsMonitorVO.class, queryWrapper);
|
||||||
List<String> userIds = voPage.getRecords().stream().map(UserPointsMonitorVO::getUserId).collect(Collectors.toList());
|
List<String> userIds = voPage.getRecords().stream().map(UserPointsMonitorVO::getUserId).collect(Collectors.toList());
|
||||||
if (!userIds.isEmpty()) {
|
if (!userIds.isEmpty()) {
|
||||||
@@ -166,11 +248,11 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
vo.setSexName(ObjectUtil.isNotNull(loginUser.getSex()) && loginUser.getSex().equals(1) ? "女" : "男");
|
vo.setSexName(ObjectUtil.isNotNull(loginUser.getSex()) && loginUser.getSex().equals(1) ? "女" : "男");
|
||||||
vo.setAge(ObjectUtil.isNotNull(loginUser.getAge()) && loginUser.getAge() > 0 ? loginUser.getAge() : null);
|
vo.setAge(ObjectUtil.isNotNull(loginUser.getAge()) && loginUser.getAge() > 0 ? loginUser.getAge() : null);
|
||||||
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(loginUser.getOrgCode()));
|
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(loginUser.getOrgCode()));
|
||||||
if (ObjUtil.isNotEmpty(secondDepart)) {
|
if (ObjectUtil.isNotEmpty(secondDepart)) {
|
||||||
vo.setDeptName(secondDepart.getDepartName());
|
vo.setDeptName(secondDepart.getDepartName());
|
||||||
}
|
}
|
||||||
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
|
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
|
||||||
if (ObjUtil.isNotEmpty(thirdDepart)) {
|
if (ObjectUtil.isNotEmpty(thirdDepart)) {
|
||||||
vo.setOrgName(thirdDepart.getDepartName());
|
vo.setOrgName(thirdDepart.getDepartName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,15 +261,139 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
return voPage;
|
return voPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportClockInMonitor(UserPointsPageDTO dto) {
|
||||||
|
GenericExportHandler<UserPointsMonitorVO, UserPointsPageDTO> handler =
|
||||||
|
new GenericExportHandler<>(
|
||||||
|
UserPointsMonitorVO.class,
|
||||||
|
ExportExcelEnum.BANK_CLOCK_IN_RECORD,
|
||||||
|
asyncTaskExecutor,
|
||||||
|
log,
|
||||||
|
filter -> {
|
||||||
|
filter.setPageNo(1);
|
||||||
|
filter.setPageSize(-1);
|
||||||
|
List<UserPointsMonitorVO> records = clockInMonitor(filter).getRecords();
|
||||||
|
return GenericExportHandler.handleData(records, UserPointsMonitorVO.class);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handler.exportAsync(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<UserPointRankingVO> userRanking(UserPointsRankingDTO dto) {
|
||||||
|
Page<UserPointRankingVO> page = new Page<>(dto.getPageNo(), dto.getPageSize());
|
||||||
|
Date startTime = null;
|
||||||
|
Date endTime = null;
|
||||||
|
Date now = new Date();
|
||||||
|
if (StringUtils.isNotBlank(dto.getRankType())) {
|
||||||
|
if ("0".equals(dto.getRankType())) {
|
||||||
|
// 获取当天的开始时间
|
||||||
|
startTime = DateUtil.beginOfDay(now);
|
||||||
|
// 获取当天的结束时间
|
||||||
|
endTime = DateUtil.endOfDay(now);
|
||||||
|
} else if ("1".equals(dto.getRankType())) {
|
||||||
|
// 获取本周的开始时间(周一)
|
||||||
|
startTime = DateUtil.beginOfWeek(now, true);
|
||||||
|
// 获取本周的结束时间(周日)
|
||||||
|
endTime = DateUtil.endOfWeek(now, true);
|
||||||
|
} else if ("2".equals(dto.getRankType())) {
|
||||||
|
// 获取本月的开始时间
|
||||||
|
startTime = DateUtil.beginOfMonth(now);
|
||||||
|
// 获取本月的结束时间
|
||||||
|
endTime = DateUtil.endOfMonth(now);
|
||||||
|
} else if ("3".equals(dto.getRankType())) {
|
||||||
|
// 获取本季度的开始时间
|
||||||
|
startTime = DateUtil.beginOfQuarter(now);
|
||||||
|
// 获取本季度的结束时间
|
||||||
|
endTime = DateUtil.endOfQuarter(now);
|
||||||
|
} else if ("4".equals(dto.getRankType())) {
|
||||||
|
// 获取本年度的开始时间
|
||||||
|
startTime = DateUtil.beginOfYear(now);
|
||||||
|
// 获取本年度的结束时间
|
||||||
|
endTime = DateUtil.endOfYear(now);
|
||||||
|
} else if ("5".equals(dto.getRankType())) {
|
||||||
|
// 获取指定时间的开始时间
|
||||||
|
startTime = DateUtil.beginOfDay(dto.getStartTime());
|
||||||
|
// 获取指定时间的结束时间
|
||||||
|
endTime = DateUtil.endOfDay(dto.getEndTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(dto.getRealName()) || StringUtils.isNotBlank(dto.getWorkNo())) {
|
||||||
|
List<String> sysUserIds = iSysBaseAPI.getUserIdsByDepartAndUsername(
|
||||||
|
StringUtils.isNotBlank(dto.getDeptCode()) ? dto.getDeptCode() : dto.getOrgCode(),
|
||||||
|
dto.getRealName(),
|
||||||
|
null,
|
||||||
|
dto.getWorkNo()
|
||||||
|
);
|
||||||
|
if (!sysUserIds.isEmpty()) {
|
||||||
|
dto.setUserIds(sysUserIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IPage<UserPointRankingVO> voiPage = userPointsDetailsMapper.userRanking(page, startTime, endTime, dto);
|
||||||
|
List<String> userIds = voiPage.getRecords().stream().map(UserPointRankingVO::getUserId).collect(Collectors.toList());
|
||||||
|
List<LoginUser> loginUsers = iSysBaseAPI.listUserByIdsNew(userIds);
|
||||||
|
voiPage.getRecords().forEach(vo -> {
|
||||||
|
loginUsers.forEach(loginUser -> {
|
||||||
|
if (vo.getUserId().equals(loginUser.getId())) {
|
||||||
|
vo.setRealName(loginUser.getRealname());
|
||||||
|
vo.setAvatar(loginUser.getAvatar());
|
||||||
|
vo.setSex(loginUser.getSex());
|
||||||
|
vo.setSexName(ObjectUtil.isNotNull(loginUser.getSex()) && loginUser.getSex().equals(1) ? "女" : "男");
|
||||||
|
vo.setWorkNo(loginUser.getWorkNo());
|
||||||
|
vo.setAge(ObjectUtil.isNotNull(loginUser.getAge()) && loginUser.getAge() > 0 ? loginUser.getAge() : null);
|
||||||
|
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(loginUser.getOrgCode()));
|
||||||
|
if (ObjectUtil.isNotEmpty(secondDepart)) {
|
||||||
|
vo.setDeptName(secondDepart.getDepartName());
|
||||||
|
}
|
||||||
|
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
|
||||||
|
if (ObjectUtil.isNotEmpty(thirdDepart)) {
|
||||||
|
vo.setOrgName(thirdDepart.getDepartName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return voiPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportUserRanking(UserPointsRankingDTO dto) {
|
||||||
|
GenericExportHandler<UserPointRankingVO, UserPointsRankingDTO> handler =
|
||||||
|
new GenericExportHandler<>(
|
||||||
|
UserPointRankingVO.class,
|
||||||
|
ExportExcelEnum.BANK_USER_RANKING,
|
||||||
|
asyncTaskExecutor,
|
||||||
|
log,
|
||||||
|
filter -> {
|
||||||
|
filter.setPageNo(1);
|
||||||
|
filter.setPageSize(-1);
|
||||||
|
List<UserPointRankingVO> records = userRanking(filter).getRecords();
|
||||||
|
return GenericExportHandler.handleData(records, UserPointRankingVO.class);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
handler.exportAsync(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean testUpdateUserPoints(UpdUserPointsDTO dto) {
|
||||||
|
List<String> sysUserIds = iSysBaseAPI.findSysUserIds(new SysUser());
|
||||||
|
List<LoginUser> loginUsers = iSysBaseAPI.listUserByIdsNew(sysUserIds);
|
||||||
|
loginUsers.forEach(user -> {
|
||||||
|
try {
|
||||||
|
updateUserPoints(dto, user);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("用户:{}({})更新积分异常,入参:{}", user.getRealname(), user.getUsername(), dto, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新用户积分
|
* 更新用户积分
|
||||||
*
|
*
|
||||||
* @param dto
|
* @param dto
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
public boolean updateUserPoints(UpdUserPointsDTO dto, LoginUser loginUser) {
|
||||||
public boolean updateUserPoints(UpdUserPointsDTO dto) {
|
|
||||||
LoginUser loginUser = GlobalUtils.getLoginUser();
|
|
||||||
log.info("------用户:{}({})开始更新积分,入参:{}------", loginUser.getRealname(), loginUser.getUsername(), dto);
|
log.info("------用户:{}({})开始更新积分,入参:{}------", loginUser.getRealname(), loginUser.getUsername(), dto);
|
||||||
PointsSourceEnum sourceEnum = PointsSourceEnum.getBySource(dto.getSourceType());
|
PointsSourceEnum sourceEnum = PointsSourceEnum.getBySource(dto.getSourceType());
|
||||||
if (sourceEnum == null) {
|
if (sourceEnum == null) {
|
||||||
@@ -203,7 +409,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
//违规扣除
|
//违规扣除
|
||||||
userId = dto.getUserId();
|
userId = dto.getUserId();
|
||||||
} else {
|
} else {
|
||||||
userId = GlobalUtils.getLoginUser().getId();
|
userId = loginUser.getId();
|
||||||
}
|
}
|
||||||
UserPoints userPoints = baseMapper.selectOne(new LambdaQueryWrapper<UserPoints>()
|
UserPoints userPoints = baseMapper.selectOne(new LambdaQueryWrapper<UserPoints>()
|
||||||
.eq(UserPoints::getUserId, userId)
|
.eq(UserPoints::getUserId, userId)
|
||||||
@@ -253,10 +459,10 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
List<UserPointsDetails> details = userPointsDetailsMapper.selectList(new LambdaQueryWrapper<UserPointsDetails>()
|
List<UserPointsDetails> details = userPointsDetailsMapper.selectList(new LambdaQueryWrapper<UserPointsDetails>()
|
||||||
.eq(UserPointsDetails::getUserId, userId)
|
.eq(UserPointsDetails::getUserId, userId)
|
||||||
.eq(UserPointsDetails::getChangeType, HealthBankConstants.CHANGE_TYPE_ADD)
|
.eq(UserPointsDetails::getChangeType, HealthBankConstants.CHANGE_TYPE_ADD)
|
||||||
.apply("DATE_FORMAT(create_time, '%Y-%m-%d') = '" + DateUtil.today() + "'")
|
.apply("DATE_FORMAT(create_time, '%Y-%m-%d') = {0}", DateUtil.today())
|
||||||
);
|
);
|
||||||
double sum = details.stream().map(UserPointsDetails::getPoints).mapToDouble(BigDecimal::doubleValue).sum();
|
double sum = details.stream().map(UserPointsDetails::getPoints).mapToDouble(BigDecimal::doubleValue).sum();
|
||||||
userPoints.setLatestAddDaySum(new BigDecimal(sum).add(pointsRule.getPoints()));
|
userPoints.setLatestAddDaySum(BigDecimal.valueOf(sum).add(pointsRule.getPoints()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_2_USE:
|
case TYPE_2_USE:
|
||||||
@@ -269,7 +475,8 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
ExceptionAssertsUtil.fail("用户积分不存在");
|
ExceptionAssertsUtil.fail("用户积分不存在");
|
||||||
}
|
}
|
||||||
userPoints.setDeductPointsSum(userPoints.getDeductPointsSum().add(dto.getPoints()));
|
userPoints.setDeductPointsSum(userPoints.getDeductPointsSum().add(dto.getPoints()));
|
||||||
userPoints.setPoints(userPoints.getPoints().subtract(dto.getPoints()));
|
BigDecimal subtract = userPoints.getPoints().subtract(dto.getPoints());
|
||||||
|
userPoints.setPoints(subtract.compareTo(BigDecimal.valueOf(0)) < 0 ? BigDecimal.valueOf(0) : subtract);
|
||||||
userPointsDetails.setPoints(dto.getPoints());
|
userPointsDetails.setPoints(dto.getPoints());
|
||||||
userPointsDetails.setRemark(dto.getRemark());
|
userPointsDetails.setRemark(dto.getRemark());
|
||||||
userPointsDetails.setSource(sourceEnum.getSource());
|
userPointsDetails.setSource(sourceEnum.getSource());
|
||||||
@@ -316,7 +523,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
wrapper.between(UserPointsDetails::getCreateTime, startOfDay, endOfDay);
|
wrapper.between(UserPointsDetails::getCreateTime, startOfDay, endOfDay);
|
||||||
c = userPointsDetailsMapper.selectCount(wrapper);
|
c = userPointsDetailsMapper.selectCount(wrapper);
|
||||||
if (c > 0) {
|
if (c > 0) {
|
||||||
ExceptionAssertsUtil.fail("本日已获得该积分");
|
ExceptionAssertsUtil.fail("今日已获得该积分");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// 周/次:检查用户本周是否已经获得过该积分
|
// 周/次:检查用户本周是否已经获得过该积分
|
||||||
@@ -356,7 +563,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
wrapper.between(UserPointsDetails::getCreateTime, startOfYear, endOfYear);
|
wrapper.between(UserPointsDetails::getCreateTime, startOfYear, endOfYear);
|
||||||
c = userPointsDetailsMapper.selectCount(wrapper);
|
c = userPointsDetailsMapper.selectCount(wrapper);
|
||||||
if (c > 0) {
|
if (c > 0) {
|
||||||
ExceptionAssertsUtil.fail("本年度已获得该积分");
|
ExceptionAssertsUtil.fail("本年已获得该积分");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -365,6 +572,11 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* app我的积分
|
||||||
|
*
|
||||||
|
* @return 积分信息
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public UserPointsInfoVO myPoints() {
|
public UserPointsInfoVO myPoints() {
|
||||||
String userId = GlobalUtils.getLoginUser().getId();
|
String userId = GlobalUtils.getLoginUser().getId();
|
||||||
@@ -380,18 +592,36 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* app积分任务
|
||||||
|
*
|
||||||
|
* @return 列表
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<UserPointsTaskVO> pointsTaskList() {
|
public List<UserPointsTaskVO> pointsTaskList() {
|
||||||
String userId = GlobalUtils.getLoginUser().getId();
|
String userId = GlobalUtils.getLoginUser().getId();
|
||||||
return pointsRuleMapper.pointsTaskList(userId);
|
return pointsRuleMapper.pointsTaskList(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* app积分打卡
|
||||||
|
*
|
||||||
|
* @param dto 参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean pointCheckIn(UpdUserPointsDTO dto) {
|
public boolean pointCheckIn(UpdUserPointsDTO dto) {
|
||||||
dto.setSourceType(PointsSourceEnum.TYPE_1_SURVEY.getSource());
|
dto.setSourceType(PointsSourceEnum.TYPE_1_SURVEY.getSource());
|
||||||
return updateUserPoints(dto);
|
LoginUser loginUser = GlobalUtils.getLoginUser();
|
||||||
|
return updateUserPoints(dto, loginUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* app积分明细
|
||||||
|
*
|
||||||
|
* @param dto 参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IPage<UserPointRecordVO> pointRecord(UserPointsPageDTO dto) {
|
public IPage<UserPointRecordVO> pointRecord(UserPointsPageDTO dto) {
|
||||||
if (ObjectUtil.isNull(dto.getChangeType())) {
|
if (ObjectUtil.isNull(dto.getChangeType())) {
|
||||||
@@ -406,6 +636,12 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
return userPointsDetailsMapper.selectJoinPage(page, UserPointRecordVO.class, wrapper);
|
return userPointsDetailsMapper.selectJoinPage(page, UserPointRecordVO.class, wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* app积分榜单
|
||||||
|
*
|
||||||
|
* @param dto 参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IPage<UserPointRankingVO> pointRanking(UserPointsRankingDTO dto) {
|
public IPage<UserPointRankingVO> pointRanking(UserPointsRankingDTO dto) {
|
||||||
String orgCode = GlobalUtils.getLoginUser().getOrgCode();
|
String orgCode = GlobalUtils.getLoginUser().getOrgCode();
|
||||||
@@ -437,11 +673,11 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
|
|||||||
vo.setAvatar(loginUser.getAvatar());
|
vo.setAvatar(loginUser.getAvatar());
|
||||||
vo.setSex(loginUser.getSex());
|
vo.setSex(loginUser.getSex());
|
||||||
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(loginUser.getOrgCode()));
|
SysDepart secondDepart = sysCache.getDepartByOrgCode(GlobalUtils.getSecondDepartOrgCode(loginUser.getOrgCode()));
|
||||||
if (ObjUtil.isNotEmpty(secondDepart)) {
|
if (ObjectUtil.isNotEmpty(secondDepart)) {
|
||||||
vo.setDeptName(secondDepart.getDepartName());
|
vo.setDeptName(secondDepart.getDepartName());
|
||||||
}
|
}
|
||||||
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
|
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
|
||||||
if (ObjUtil.isNotEmpty(thirdDepart)) {
|
if (ObjectUtil.isNotEmpty(thirdDepart)) {
|
||||||
vo.setOrgName(thirdDepart.getDepartName());
|
vo.setOrgName(thirdDepart.getDepartName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.consultation.dto;
|
package com.renkang.consultation.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.renkang.consultation.entity.ConPage;
|
import com.renkang.consultation.entity.ConPage;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -11,6 +12,10 @@ public class AccountDTO extends ConPage {
|
|||||||
@Schema(title = "专家姓名")
|
@Schema(title = "专家姓名")
|
||||||
private String doctorName;
|
private String doctorName;
|
||||||
|
|
||||||
|
public void setDoctorName(String doctorName) {
|
||||||
|
this.doctorName = doctorName != null ? HtmlUtil.unescape(doctorName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(title = "医院选择")
|
@Schema(title = "医院选择")
|
||||||
private String hospitalName;
|
private String hospitalName;
|
||||||
|
|
||||||
|
|||||||
+5
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.consultation.dto;
|
package com.renkang.consultation.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.renkang.consultation.entity.ConPage;
|
import com.renkang.consultation.entity.ConPage;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -10,6 +11,10 @@ public class ConDoctorDTO extends ConPage {
|
|||||||
|
|
||||||
@Schema(title = "专家姓名")
|
@Schema(title = "专家姓名")
|
||||||
private String doctorName;
|
private String doctorName;
|
||||||
|
|
||||||
|
public void setDoctorName(String doctorName) {
|
||||||
|
this.doctorName = doctorName != null ? HtmlUtil.unescape(doctorName) : null;
|
||||||
|
}
|
||||||
@Schema(title = "专家编号")
|
@Schema(title = "专家编号")
|
||||||
private String doctorNo;
|
private String doctorNo;
|
||||||
|
|
||||||
|
|||||||
+8
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.consultation.dto;
|
package com.renkang.consultation.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.renkang.consultation.entity.ConPage;
|
import com.renkang.consultation.entity.ConPage;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@@ -17,6 +18,9 @@ public class ConEvaluateDTO extends ConPage {
|
|||||||
|
|
||||||
@Schema(title = "咨询人")
|
@Schema(title = "咨询人")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.userName = userName != null ? HtmlUtil.unescape(userName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(title = "二级单位id")
|
@Schema(title = "二级单位id")
|
||||||
private String sysOrgSecondId;
|
private String sysOrgSecondId;
|
||||||
@@ -27,6 +31,10 @@ public class ConEvaluateDTO extends ConPage {
|
|||||||
@Schema(title = "咨询专家")
|
@Schema(title = "咨询专家")
|
||||||
private String expertName;
|
private String expertName;
|
||||||
|
|
||||||
|
public void setExpertName(String expertName) {
|
||||||
|
this.expertName = expertName != null ? HtmlUtil.unescape(expertName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(title = "专家医院")
|
@Schema(title = "专家医院")
|
||||||
private String expertHospital;
|
private String expertHospital;
|
||||||
|
|
||||||
|
|||||||
+5
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.consultation.dto;
|
package com.renkang.consultation.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.renkang.consultation.entity.ConPage;
|
import com.renkang.consultation.entity.ConPage;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -10,6 +11,10 @@ public class ConHealthInfoAnswerDTO extends ConPage {
|
|||||||
@Schema(title = "员工姓名")
|
@Schema(title = "员工姓名")
|
||||||
private String fromName;
|
private String fromName;
|
||||||
|
|
||||||
|
public void setFromName(String fromName) {
|
||||||
|
this.fromName = fromName != null ? HtmlUtil.unescape(fromName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(title = "手机号")
|
@Schema(title = "手机号")
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
|
|||||||
+5
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.consultation.dto;
|
package com.renkang.consultation.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.renkang.consultation.entity.ConPage;
|
import com.renkang.consultation.entity.ConPage;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -11,6 +12,10 @@ public class ConHelperDTO extends ConPage {
|
|||||||
@Schema(title = "全科医生姓名")
|
@Schema(title = "全科医生姓名")
|
||||||
private String userName;
|
private String userName;
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.userName = userName != null ? HtmlUtil.unescape(userName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(title = "状态")
|
@Schema(title = "状态")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
|||||||
+9
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.consultation.dto;
|
package com.renkang.consultation.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.renkang.consultation.entity.ConPage;
|
import com.renkang.consultation.entity.ConPage;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@@ -17,6 +18,10 @@ public class ConSessionDTO extends ConPage {
|
|||||||
@Schema(title = "员工姓名")
|
@Schema(title = "员工姓名")
|
||||||
private String fromName;
|
private String fromName;
|
||||||
|
|
||||||
|
public void setFromName(String fromName) {
|
||||||
|
this.fromName = fromName != null ? HtmlUtil.unescape(fromName) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(title = "员工id")
|
@Schema(title = "员工id")
|
||||||
private String fromAccount;
|
private String fromAccount;
|
||||||
|
|
||||||
@@ -26,6 +31,10 @@ public class ConSessionDTO extends ConPage {
|
|||||||
@Schema(title = "专家姓名")
|
@Schema(title = "专家姓名")
|
||||||
private String doctorName;
|
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超时自动结束)")
|
@Schema(title = "咨询状态(0待确认 1接收预约 2拒绝预约 3已过期 4已取消 5咨询中 6专家咨询结束 7员工咨询结束 8超时自动结束)")
|
||||||
private String contentStatus;
|
private String contentStatus;
|
||||||
|
|
||||||
|
|||||||
+6
@@ -480,4 +480,10 @@ public class ConSessionApiController {
|
|||||||
return conSessionApiService.insertUserAndHelperSessionNew();
|
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();
|
UserSessionCount countForUser();
|
||||||
|
|
||||||
|
String endVideoSession();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -2114,4 +2114,8 @@ public class ConSessionApiServiceImpl implements ConSessionApiService {
|
|||||||
return conSessionMapper.selectCount(wrapper).intValue();
|
return conSessionMapper.selectCount(wrapper).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String endVideoSession() {
|
||||||
|
return conSessionMapper.endVideoSession();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -484,4 +484,9 @@ public class ConSessionController extends JeecgController<ConSession, IConSessio
|
|||||||
return conSessionService.exportXlsConSession(request, dto);
|
return conSessionService.exportXlsConSession(request, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getPicUrl")
|
||||||
|
public Result<String> getPicUrl(){
|
||||||
|
return Result.ok(conSessionService.getPicUrl());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -294,4 +294,13 @@ public interface ConSessionMapper extends BaseMapper<ConSession> , MPJBaseMapper
|
|||||||
@Param("doctorjob")String doctorjob,
|
@Param("doctorjob")String doctorjob,
|
||||||
@Param("contentType")String contentType,
|
@Param("contentType")String contentType,
|
||||||
@Param("toAccount")String toAccount);
|
@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();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -85,5 +85,7 @@ public interface IConSessionService extends IService<ConSession> {
|
|||||||
Result<List<com.renkang.im.entity.ImMsgRecordAll>> selectImMessageList(String groupId);
|
Result<List<com.renkang.im.entity.ImMsgRecordAll>> selectImMessageList(String groupId);
|
||||||
|
|
||||||
Result<?> exportXlsConSession(HttpServletRequest request, ConSessionDTO dto);
|
Result<?> exportXlsConSession(HttpServletRequest request, ConSessionDTO dto);
|
||||||
|
|
||||||
|
String getPicUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-4
@@ -3,7 +3,6 @@ package com.renkang.consultation.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@@ -249,9 +248,6 @@ public class ConDoctorServiceImpl extends ServiceImpl<ConDoctorMapper, ConDoctor
|
|||||||
if (StringUtils.hasLength(dto.getHospitalName())) {
|
if (StringUtils.hasLength(dto.getHospitalName())) {
|
||||||
conDoctor.setResourceId(dto.getHospitalName());
|
conDoctor.setResourceId(dto.getHospitalName());
|
||||||
}
|
}
|
||||||
if (StrUtil.isNotEmpty(dto.getDoctorName())){
|
|
||||||
conDoctor.setDoctorName(HtmlUtil.unescape(dto.getDoctorName()));
|
|
||||||
}
|
|
||||||
IPage<ConDoctor> conDoctorIPage = conDoctorMapper.queryPageList(page, conDoctor);
|
IPage<ConDoctor> conDoctorIPage = conDoctorMapper.queryPageList(page, conDoctor);
|
||||||
List<ConDoctor> conDoctorList = conDoctorIPage.getRecords();
|
List<ConDoctor> conDoctorList = conDoctorIPage.getRecords();
|
||||||
if (CollectionUtils.isEmpty(conDoctorList)) {
|
if (CollectionUtils.isEmpty(conDoctorList)) {
|
||||||
|
|||||||
+7
@@ -52,6 +52,7 @@ import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
|||||||
import org.jeecgframework.poi.exception.excel.enums.ExcelExportEnum;
|
import org.jeecgframework.poi.exception.excel.enums.ExcelExportEnum;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.task.AsyncTaskExecutor;
|
import org.springframework.core.task.AsyncTaskExecutor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -1264,5 +1265,11 @@ public class ConSessionServiceImpl extends ServiceImpl<ConSessionMapper, ConSess
|
|||||||
return sendTime;
|
return sendTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value("${banner.picUrl:}")
|
||||||
|
private String bannerPicUrl;
|
||||||
|
public String getPicUrl(){
|
||||||
|
// 读取配置文件的url
|
||||||
|
return bannerPicUrl;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+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.context.XxlJobHelper;
|
||||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
import org.jeecg.config.shiro.ClientSignThreadLocal;
|
import org.jeecg.config.shiro.ClientSignThreadLocal;
|
||||||
import org.parboiled.common.StringUtils;
|
import org.parboiled.common.StringUtils;
|
||||||
@@ -36,10 +37,10 @@ public class SyncScheduleJob {
|
|||||||
String expireHour = "168";
|
String expireHour = "168";
|
||||||
|
|
||||||
String param = XxlJobHelper.getJobParam();
|
String param = XxlJobHelper.getJobParam();
|
||||||
if(StringUtils.isNotEmpty(param)){
|
if (StringUtils.isNotEmpty(param)) {
|
||||||
JSONObject jsonObject = JSONObject.parseObject(param);
|
JSONObject jsonObject = JSONObject.parseObject(param);
|
||||||
String expireHourRequest = jsonObject.getString("expireHour");
|
String expireHourRequest = jsonObject.getString("expireHour");
|
||||||
if(StringUtils.isNotEmpty(expireHourRequest)){
|
if (StringUtils.isNotEmpty(expireHourRequest)) {
|
||||||
expireHour = jsonObject.getString("expireHour");
|
expireHour = jsonObject.getString("expireHour");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,4 +54,12 @@ public class SyncScheduleJob {
|
|||||||
return ReturnT.SUCCESS;
|
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;
|
package com.renkang.emergency.entity.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -7,6 +8,10 @@ import lombok.Data;
|
|||||||
public class CallLogDTO {
|
public class CallLogDTO {
|
||||||
@Schema(description = "姓名")
|
@Schema(description = "姓名")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
|
public void setRealName(String realName) {
|
||||||
|
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||||
|
}
|
||||||
@Schema(description = "呼叫中心")
|
@Schema(description = "呼叫中心")
|
||||||
private String centerId;
|
private String centerId;
|
||||||
@Schema(description = "坐席工号")
|
@Schema(description = "坐席工号")
|
||||||
|
|||||||
+5
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.emergency.entity.dto;
|
package com.renkang.emergency.entity.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -21,6 +22,10 @@ public class PhoneListDTO {
|
|||||||
private String phoneType;
|
private String phoneType;
|
||||||
@Schema(description = "姓名")
|
@Schema(description = "姓名")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
|
public void setRealName(String realName) {
|
||||||
|
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 0:全部 1:女 2:男
|
* 0:全部 1:女 2:男
|
||||||
*/
|
*/
|
||||||
|
|||||||
+5
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.emergency.entity.dto;
|
package com.renkang.emergency.entity.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -9,6 +10,10 @@ public class TblQimoNoticeDTO {
|
|||||||
private String seatId;
|
private String seatId;
|
||||||
@Schema(title = "姓名")
|
@Schema(title = "姓名")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
|
public void setRealName(String realName) {
|
||||||
|
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||||
|
}
|
||||||
@Schema(title = "所属应急中心")
|
@Schema(title = "所属应急中心")
|
||||||
private String centerId;
|
private String centerId;
|
||||||
@Schema(title = "orgCode")
|
@Schema(title = "orgCode")
|
||||||
|
|||||||
+2
@@ -1,6 +1,7 @@
|
|||||||
package com.renkang.emergency.controller;
|
package com.renkang.emergency.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.renkang.emergency.entity.EmergencyProfessionUser;
|
import com.renkang.emergency.entity.EmergencyProfessionUser;
|
||||||
import com.renkang.emergency.service.EmergencyProfessionUserService;
|
import com.renkang.emergency.service.EmergencyProfessionUserService;
|
||||||
@@ -40,6 +41,7 @@ public class EmergencyProfessionUserController {
|
|||||||
@RequestParam(name = "type", defaultValue = "5") Integer type,
|
@RequestParam(name = "type", defaultValue = "5") Integer type,
|
||||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||||
|
name = HtmlUtil.unescape(name);
|
||||||
IPage<EmergencyProfessionUser> pageList = emergencyProfessionUserService.customPage(name, centerId, type, pageNo, pageSize);
|
IPage<EmergencyProfessionUser> pageList = emergencyProfessionUserService.customPage(name, centerId, type, pageNo, pageSize);
|
||||||
return Result.OK(pageList);
|
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.ObjUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@@ -58,6 +59,9 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<Order> pageCustom(Page<Order> page, Order order, HttpServletRequest req) {
|
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 salvageUserName = order.getSalvageUserName();
|
||||||
String initiatorUserName = order.getInitiatorUserName();
|
String initiatorUserName = order.getInitiatorUserName();
|
||||||
Order orderCopy = new Order();
|
Order orderCopy = new Order();
|
||||||
|
|||||||
+4
@@ -22,6 +22,10 @@ public class WatchH5IndexItemNewVo {
|
|||||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
private Date dataDate;
|
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时此字段返回长睡时长
|
* 注意 当wdType=sleep时此字段返回长睡时长
|
||||||
*/
|
*/
|
||||||
|
|||||||
+4
@@ -1,5 +1,6 @@
|
|||||||
package com.renkang.watch.bean.request;
|
package com.renkang.watch.bean.request;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
@@ -19,6 +20,9 @@ public class AbnormalEventFilter {
|
|||||||
private String departOrgCode;
|
private String departOrgCode;
|
||||||
// 用户名称
|
// 用户名称
|
||||||
private String userName;
|
private String userName;
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.userName = userName != null ? HtmlUtil.unescape(userName) : null;
|
||||||
|
}
|
||||||
// 工具编码
|
// 工具编码
|
||||||
private String watchNo;
|
private String watchNo;
|
||||||
//员工编号
|
//员工编号
|
||||||
|
|||||||
+15
@@ -35,6 +35,7 @@ public class AbnormalEvent extends BaseUser {
|
|||||||
@DateTimeFormat(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 = 15, orderNum = "10")
|
@Excel(name = "报警时间", format = "yyyy-MM-dd HH:mm:ss", width = 15, orderNum = "10")
|
||||||
private Date warnTime;
|
private Date warnTime;
|
||||||
|
@Excel(name = "回传时间", format = "yyyy-MM-dd HH:mm:ss", width = 15, orderNum = "11")
|
||||||
@JsonFormat(timezone = "GMT+8", pattern = "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")
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@Schema(title = "创建日期")
|
@Schema(title = "创建日期")
|
||||||
@@ -51,4 +52,18 @@ public class AbnormalEvent extends BaseUser {
|
|||||||
private Date bindDate;
|
private Date bindDate;
|
||||||
@Schema(title = "重点指标用户(0:非重点指标用户 1:大病异常 2:慢病异常 3:指标异常)")
|
@Schema(title = "重点指标用户(0:非重点指标用户 1:大病异常 2:慢病异常 3:指标异常)")
|
||||||
private List<WatchKeyUser> keyUserList;
|
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;
|
package com.renkang.watch.dto;
|
||||||
|
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -18,6 +19,10 @@ public class CalorieDataByUserDTO {
|
|||||||
private Integer pageSize = 10;
|
private Integer pageSize = 10;
|
||||||
@Schema(title = "用户姓名")
|
@Schema(title = "用户姓名")
|
||||||
private String realName;
|
private String realName;
|
||||||
|
|
||||||
|
public void setRealName(String realName) {
|
||||||
|
this.realName = realName != null ? HtmlUtil.unescape(realName) : null;
|
||||||
|
}
|
||||||
@Schema(title = "工号")
|
@Schema(title = "工号")
|
||||||
private String workNo;
|
private String workNo;
|
||||||
@Schema(title = "用户id组")
|
@Schema(title = "用户id组")
|
||||||
|
|||||||
+14
@@ -151,6 +151,20 @@ public class WatchMonitorData implements Serializable {
|
|||||||
@Excel(name = "部门编码", width = 15)
|
@Excel(name = "部门编码", width = 15)
|
||||||
@Schema(title = "部门编码")
|
@Schema(title = "部门编码")
|
||||||
private java.lang.String orgCode;
|
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 = "员工编号")
|
@Schema(title = "员工编号")
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
|
|||||||
+51
-2
@@ -1,22 +1,71 @@
|
|||||||
package com.renkang.watch.vo.UserData.res;
|
package com.renkang.watch.vo.UserData.res;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.renkang.watch.WatchH5IndexItemNewVo;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/** 用户统计数据
|
/**
|
||||||
|
* 用户统计数据
|
||||||
|
*
|
||||||
* @author stan
|
* @author stan
|
||||||
* @since 2024-11-04 16:33
|
* @since 2024-11-04 16:33
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class UserStat {
|
public class UserStat {
|
||||||
|
/**
|
||||||
|
* 数据类型:0=心率 1=血氧 2=压力 3=体温 4=睡眠
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
private BigDecimal latest;
|
private BigDecimal latest;
|
||||||
private BigDecimal max;
|
private BigDecimal max;
|
||||||
private BigDecimal min;
|
private BigDecimal min;
|
||||||
private BigDecimal avg;
|
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;
|
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 Date warnTime;
|
||||||
|
|
||||||
private String gpsErrorMsg;
|
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;
|
private Integer assignedDeviceNums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未分配设备数(入库设备数 - 已分配设备数,仅一、二级部门有值)
|
||||||
|
*/
|
||||||
|
@Excel(name = "未分配设备数", width = 15,orderNum = "5")
|
||||||
|
private Integer undistributedDeviceNums;
|
||||||
|
|
||||||
@Excel(name = "入库设备数", width = 15,orderNum = "3")
|
@Excel(name = "入库设备数", width = 15,orderNum = "3")
|
||||||
private Integer totalAssignedDeviceNums;
|
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;
|
private Integer assignedUserNums;
|
||||||
/**
|
/**
|
||||||
* 未佩戴人员数
|
* 未佩戴人员数
|
||||||
*/
|
*/
|
||||||
@Excel(name = "未佩戴人员数", width = 15,orderNum = "6")
|
@Excel(name = "未佩戴人员数", width = 15,orderNum = "7")
|
||||||
private Integer undistributedUserNums;
|
private Integer undistributedUserNums;
|
||||||
|
|
||||||
@Excel(name = "心率异常人数", width = 15,orderNum = "7")
|
@Excel(name = "心率异常人数", width = 15,orderNum = "8")
|
||||||
private Integer heartRateNums;
|
private Integer heartRateNums;
|
||||||
@Excel(name = "血氧异常人数", width = 15,orderNum = "8")
|
@Excel(name = "血氧异常人数", width = 15,orderNum = "9")
|
||||||
private Integer spo2Nums;
|
private Integer spo2Nums;
|
||||||
@Excel(name = "压力异常人数", width = 15,orderNum = "9")
|
@Excel(name = "压力异常人数", width = 15,orderNum = "10")
|
||||||
private Integer stressNums;
|
private Integer stressNums;
|
||||||
@Excel(name = "体温异常人数", width = 15,orderNum = "10")
|
@Excel(name = "体温异常人数", width = 15,orderNum = "11")
|
||||||
private Integer tempNums;
|
private Integer tempNums;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -41,6 +42,16 @@ public class WatchDataApiController {
|
|||||||
return Result.ok(watchDataApiService.userStatResult(type));
|
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 org.jeecg.common.api.vo.Result;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface WatchDataApiService {
|
public interface WatchDataApiService {
|
||||||
@@ -43,6 +44,14 @@ public interface WatchDataApiService {
|
|||||||
|
|
||||||
UserStat userStatResult(String type);
|
UserStat userStatResult(String type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前登录用户所有健康指标统计数据(按指定日期过滤)
|
||||||
|
*
|
||||||
|
* @param date 查询日期,为 null 时返回最新一条数据
|
||||||
|
* @return 各指标统计结果列表
|
||||||
|
*/
|
||||||
|
List<UserStat> userStatResultAll(Date date);
|
||||||
|
|
||||||
Boolean userHaveWatch(String userId);
|
Boolean userHaveWatch(String userId);
|
||||||
|
|
||||||
UserWatchInfo findWatchInfo(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.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
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.bean.response.UserWatchInfo;
|
||||||
import com.renkang.watch.api.service.WatchDataApiService;
|
import com.renkang.watch.api.service.WatchDataApiService;
|
||||||
import com.renkang.watch.dto.WatchDataWorkoutDTO;
|
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.bodyTemperature.BodyTempStatRes;
|
||||||
import com.renkang.watch.vo.UserData.res.heartRate.WatchDataHeartRateRes;
|
import com.renkang.watch.vo.UserData.res.heartRate.WatchDataHeartRateRes;
|
||||||
import com.renkang.watch.vo.UserData.res.sleep.WatchDataSleepRes;
|
import com.renkang.watch.vo.UserData.res.sleep.WatchDataSleepRes;
|
||||||
|
import com.renkang.watch.vo.WatchH5IndexItemVo;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.util.DateUtils;
|
import org.jeecg.common.util.DateUtils;
|
||||||
|
import org.jeecg.common.util.RedisUtil;
|
||||||
import org.jeecg.global.GlobalUtils;
|
import org.jeecg.global.GlobalUtils;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class WatchDataApiServiceImpl implements WatchDataApiService {
|
public class WatchDataApiServiceImpl implements WatchDataApiService {
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WatchDataWorkoutMapper watchDataWorkoutMapper;
|
private WatchDataWorkoutMapper watchDataWorkoutMapper;
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private WatchStatUserInfoDayHeartRateMapper watchStatUserInfoDayHeartRateMapper;
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WatchDataHeartRateMapper watchDataHeartRateMapper;
|
private WatchDataHeartRateMapper watchDataHeartRateMapper;
|
||||||
|
|
||||||
@@ -60,9 +65,6 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private WatchStatUserInfoDaySpo2ServiceImpl watchStatUserInfoDaySpo2ServiceImpl;
|
private WatchStatUserInfoDaySpo2ServiceImpl watchStatUserInfoDaySpo2ServiceImpl;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private WatchStatUserInfoDaySleepMapper watchStatUserInfoDaySleepMapper;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WatchDataSleepNewMinuteMapper watchDataSleepNewMinuteMapper;
|
private WatchDataSleepNewMinuteMapper watchDataSleepNewMinuteMapper;
|
||||||
|
|
||||||
@@ -96,7 +98,6 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IWatchDataTemperatureService watchDataTemperatureService;
|
private IWatchDataTemperatureService watchDataTemperatureService;
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IWatchDataStressService watchDataStressService;
|
private IWatchDataStressService watchDataStressService;
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -106,12 +107,47 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private WatchUserDataMapper watchUserDataMapper;
|
private WatchUserDataMapper watchUserDataMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private WatchStatUserInfoDayHeartRateMapper watchStatUserInfoDayHeartRateMapper;
|
||||||
|
@Autowired
|
||||||
|
private WatchStatUserInfoDaySleepMapper watchStatUserInfoDaySleepMapper;
|
||||||
|
@Autowired
|
||||||
private WatchStatUserInfoDaySpo2Mapper watchStatUserInfoDaySpo2Mapper;
|
private WatchStatUserInfoDaySpo2Mapper watchStatUserInfoDaySpo2Mapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private WatchStatUserInfoDayStressMapper watchStatUserInfoDayStressMapper;
|
private WatchStatUserInfoDayStressMapper watchStatUserInfoDayStressMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private WatchStatUserInfoDayTempMapper watchStatUserInfoDayTempMapper;
|
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
|
@Override
|
||||||
public Result<Map<String,Object>> selectSportCostListApi() {
|
public Result<Map<String,Object>> selectSportCostListApi() {
|
||||||
LoginUser loginUser = GlobalUtils.getLoginUser();
|
LoginUser loginUser = GlobalUtils.getLoginUser();
|
||||||
@@ -834,20 +870,171 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
String userId = GlobalUtils.getLoginUser().getId();
|
String userId = GlobalUtils.getLoginUser().getId();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "0":
|
case "0":
|
||||||
return handleHeartRate(userId);
|
return handleHeartRate(userId,null);
|
||||||
case "1":
|
case "1":
|
||||||
return handleBloodOxygen(userId);
|
return handleBloodOxygen(userId,null);
|
||||||
case "2":
|
case "2":
|
||||||
return handleStress(userId);
|
return handleStress(userId,null);
|
||||||
case "3":
|
case "3":
|
||||||
return handleBodyTemperature(userId);
|
return handleBodyTemperature(userId,null);
|
||||||
case "4":
|
case "4":
|
||||||
return handleSleep(userId);
|
return handleSleep(userId,null);
|
||||||
default:
|
default:
|
||||||
return null;
|
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
|
@Override
|
||||||
public Boolean userHaveWatch(String userId) {
|
public Boolean userHaveWatch(String userId) {
|
||||||
if (StrUtil.isBlank(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)
|
LambdaQueryWrapper<WatchStatUserInfoDaySleep> queryWrapper = Wrappers.lambdaQuery(WatchStatUserInfoDaySleep.class)
|
||||||
.select(WatchStatUserInfoDaySleep::getId,
|
.select(WatchStatUserInfoDaySleep::getId,
|
||||||
WatchStatUserInfoDaySleep::getDeepSleepTimes,
|
WatchStatUserInfoDaySleep::getDeepSleepTimes,
|
||||||
@@ -893,13 +1080,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
WatchStatUserInfoDaySleep::getDeepLightTimes,
|
WatchStatUserInfoDaySleep::getDeepLightTimes,
|
||||||
WatchStatUserInfoDaySleep::getDataDate)
|
WatchStatUserInfoDaySleep::getDataDate)
|
||||||
.eq(WatchStatUserInfoDaySleep::getUserId, userId)
|
.eq(WatchStatUserInfoDaySleep::getUserId, userId)
|
||||||
.orderByDesc(WatchStatUserInfoDaySleep::getDataDate)
|
// 传了日期则精确匹配,否则取最新一条
|
||||||
|
.eq(date != null, WatchStatUserInfoDaySleep::getDataDate, date)
|
||||||
|
.orderByDesc(date == null, WatchStatUserInfoDaySleep::getDataDate)
|
||||||
.last("limit 1");
|
.last("limit 1");
|
||||||
|
|
||||||
WatchStatUserInfoDaySleep data = watchStatUserInfoDaySleepMapper.selectOne(queryWrapper);
|
WatchStatUserInfoDaySleep data = watchStatUserInfoDaySleepMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return null;
|
return new UserStat();
|
||||||
}
|
}
|
||||||
|
|
||||||
UserStat stat = new UserStat();
|
UserStat stat = new UserStat();
|
||||||
@@ -915,15 +1104,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserStat handleBodyTemperature(String userId) {
|
private UserStat handleBodyTemperature(String userId, Date date) {
|
||||||
int type = 5;
|
int type = 5;
|
||||||
int scale = 1;
|
int scale = 1;
|
||||||
// 最新数据
|
// 最新数据
|
||||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||||
if (userStat == null) {
|
if (userStat == null) {
|
||||||
return null;
|
return new UserStat();
|
||||||
}
|
}
|
||||||
LambdaQueryWrapper<WatchStatUserInfoDayTemp> queryWrapper = getQueryWrapper(userId);
|
LambdaQueryWrapper<WatchStatUserInfoDayTemp> queryWrapper = getQueryWrapper(userId, date);
|
||||||
WatchStatUserInfoDayTemp data = watchStatUserInfoDayTempMapper.selectOne(queryWrapper);
|
WatchStatUserInfoDayTemp data = watchStatUserInfoDayTempMapper.selectOne(queryWrapper);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return userStat;
|
return userStat;
|
||||||
@@ -933,15 +1122,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
return userStat;
|
return userStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserStat handleStress(String userId) {
|
private UserStat handleStress(String userId, Date date) {
|
||||||
int type = 3;
|
int type = 3;
|
||||||
int scale = 0;
|
int scale = 0;
|
||||||
// 最新数据
|
// 最新数据
|
||||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||||
if (userStat == null) {
|
if (userStat == null) {
|
||||||
return null;
|
return new UserStat();
|
||||||
}
|
}
|
||||||
LambdaQueryWrapper<WatchStatUserInfoDayStress> queryWrapper = getQueryWrapper(userId);
|
LambdaQueryWrapper<WatchStatUserInfoDayStress> queryWrapper = getQueryWrapper(userId, date);
|
||||||
WatchStatUserInfoDayStress data = watchStatUserInfoDayStressMapper.selectOne(queryWrapper);
|
WatchStatUserInfoDayStress data = watchStatUserInfoDayStressMapper.selectOne(queryWrapper);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return userStat;
|
return userStat;
|
||||||
@@ -951,15 +1140,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
return userStat;
|
return userStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserStat handleBloodOxygen(String userId) {
|
private UserStat handleBloodOxygen(String userId, Date date) {
|
||||||
int type = 2;
|
int type = 2;
|
||||||
int scale = 0;
|
int scale = 0;
|
||||||
// 最新数据
|
// 最新数据
|
||||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||||
if (userStat == null) {
|
if (userStat == null) {
|
||||||
return null;
|
return new UserStat();
|
||||||
}
|
}
|
||||||
LambdaQueryWrapper<WatchStatUserInfoDaySpo2> queryWrapper = getQueryWrapper(userId);
|
LambdaQueryWrapper<WatchStatUserInfoDaySpo2> queryWrapper = getQueryWrapper(userId, date);
|
||||||
WatchStatUserInfoDaySpo2 data = watchStatUserInfoDaySpo2Mapper.selectOne(queryWrapper);
|
WatchStatUserInfoDaySpo2 data = watchStatUserInfoDaySpo2Mapper.selectOne(queryWrapper);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return userStat;
|
return userStat;
|
||||||
@@ -969,15 +1158,15 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
return userStat;
|
return userStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserStat handleHeartRate(String userId) {
|
private UserStat handleHeartRate(String userId, Date date) {
|
||||||
int type = 1;
|
int type = 1;
|
||||||
int scale = 0;
|
int scale = 0;
|
||||||
// 最新数据
|
// 最新数据
|
||||||
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
UserStat userStat = userWatchDataLatest(userId, type, scale);
|
||||||
if (userStat == null) {
|
if (userStat == null) {
|
||||||
return null;
|
return new UserStat();
|
||||||
}
|
}
|
||||||
LambdaQueryWrapper<WatchStatUserInfoDayHeartRate> queryWrapper = getQueryWrapper(userId);
|
LambdaQueryWrapper<WatchStatUserInfoDayHeartRate> queryWrapper = getQueryWrapper(userId, date);
|
||||||
WatchStatUserInfoDayHeartRate data = watchStatUserInfoDayHeartRateMapper.selectOne(queryWrapper);
|
WatchStatUserInfoDayHeartRate data = watchStatUserInfoDayHeartRateMapper.selectOne(queryWrapper);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return userStat;
|
return userStat;
|
||||||
@@ -1029,10 +1218,12 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> LambdaQueryWrapper<T> getQueryWrapper(String userId) {
|
private <T> LambdaQueryWrapper<T> getQueryWrapper(String userId, Date date) {
|
||||||
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("user_id", userId)
|
queryWrapper.eq("user_id", userId)
|
||||||
.orderByDesc("data_date")
|
// 传了日期则精确匹配,否则取最新一条
|
||||||
|
.eq(date != null, "data_date", date)
|
||||||
|
.orderByDesc(date == null, "data_date")
|
||||||
.last("limit 1");
|
.last("limit 1");
|
||||||
return queryWrapper.lambda();
|
return queryWrapper.lambda();
|
||||||
}
|
}
|
||||||
@@ -1051,4 +1242,17 @@ public class WatchDataApiServiceImpl implements WatchDataApiService {
|
|||||||
return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP);
|
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.setBindDate(watchDevice.getBindDate()==null?new Date():watchDevice.getBindDate());
|
||||||
updatedWatchDevice.setUpdateDate(new Date());
|
updatedWatchDevice.setUpdateDate(new Date());
|
||||||
updatedWatchDevice.setUpdateBy(sysUser.getId());
|
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);
|
updateList.add(updatedWatchDevice);
|
||||||
}
|
}
|
||||||
info1.setHandleMsg(index + "/" + count);
|
info1.setHandleMsg(index + "/" + count);
|
||||||
|
|||||||
+15
@@ -38,6 +38,21 @@ public class WatchApiController {
|
|||||||
return watchMonitorDataService.notice(json);
|
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")
|
@GetMapping("/setData")
|
||||||
public AjaxJson setData() {
|
public AjaxJson setData() {
|
||||||
AjaxJson ajaxJson = new AjaxJson();
|
AjaxJson ajaxJson = new AjaxJson();
|
||||||
|
|||||||
+2
-4
@@ -11,10 +11,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.jeecg.common.api.vo.Result;
|
import org.jeecg.common.api.vo.Result;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -327,4 +324,5 @@ public class WatchController {
|
|||||||
// }
|
// }
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-1
@@ -185,6 +185,12 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
|||||||
return watchDeviceService.bindUser(dto);
|
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 = "解绑用户")
|
@Operation(summary = "解绑用户", description = "解绑用户")
|
||||||
@PostMapping(value = "/unbundleUser")
|
@PostMapping(value = "/unbundleUser")
|
||||||
@RequiresPermissions("watch:watch_device:unbindUser")
|
@RequiresPermissions("watch:watch_device:unbindUser")
|
||||||
public Result unbundleUser(@RequestBody() BindUserDTO dto) throws ParseException {
|
public Result unbundleUser(@RequestBody() BindUserDTO dto) {
|
||||||
return watchDeviceService.unbundleUser(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")
|
@DeleteMapping(value = "/delete")
|
||||||
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
|
||||||
WatchDevice watchDevice = watchDeviceService.getById(id);
|
WatchDevice watchDevice = watchDeviceService.getById(id);
|
||||||
|
// 已绑定用户的手表不允许删除
|
||||||
|
if (StringUtils.hasLength(watchDevice.getBindUserId())) {
|
||||||
|
return Result.error("该手表已绑定用户,请先解绑后再删除!");
|
||||||
|
}
|
||||||
watchDeviceService.removeById(id);
|
watchDeviceService.removeById(id);
|
||||||
// 删除开关
|
// 删除开关
|
||||||
LambdaQueryWrapper<WatchDeviceSwitch> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<WatchDeviceSwitch> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
@@ -422,6 +440,8 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
|
|||||||
}
|
}
|
||||||
if (!StringUtils.hasLength(deptId) || "undefined".equals(deptId)) {
|
if (!StringUtils.hasLength(deptId) || "undefined".equals(deptId)) {
|
||||||
throw new JeecgBootException("部门不能为空");
|
throw new JeecgBootException("部门不能为空");
|
||||||
|
//可为空,空则算入新疆油田公司
|
||||||
|
// deptId = sysCache.getDepartIdByOrgCode("A01");
|
||||||
}
|
}
|
||||||
String sheetName = "设备导入";
|
String sheetName = "设备导入";
|
||||||
String fileName = sheetName + "_" + DateUtils.getDate("yyyyMMddHHmmss");
|
String fileName = sheetName + "_" + DateUtils.getDate("yyyyMMddHHmmss");
|
||||||
|
|||||||
+5
-1
@@ -43,12 +43,16 @@ public interface IWatchDeviceService extends IService<WatchDevice> {
|
|||||||
*/
|
*/
|
||||||
Result bindUser(BindUserDTO dto) throws ParseException;
|
Result bindUser(BindUserDTO dto) throws ParseException;
|
||||||
|
|
||||||
|
Result bindDevice(String watchNo) throws ParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绑定用户
|
* 绑定用户
|
||||||
* @param dto
|
* @param dto
|
||||||
* @return
|
* @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);
|
AjaxJson notice(JSONObject jsonObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新告警事件GPS位置信息
|
||||||
|
* 根据 watchNo + eventId 联合定位已有告警数据;
|
||||||
|
* 缺失任一定位字段则忽略;已存在GPS信息则忽略;
|
||||||
|
* 仅更新数据,不推送大屏与短信。
|
||||||
|
*
|
||||||
|
* @param jsonObject 入参(含 watchNo、eventId、lon、lat、address、locateTime、gpsErrorMsg 等)
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
AjaxJson updateMonitorGps(JSONObject jsonObject);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理历史数据 orgCode为空的问题
|
* 处理历史数据 orgCode为空的问题
|
||||||
* @return
|
* @return
|
||||||
|
|||||||
+105
-4
@@ -595,11 +595,32 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
filter.setOrgCodeLeaf("A99A99A99");
|
filter.setOrgCodeLeaf("A99A99A99");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询部门统计数据(分页)
|
||||||
|
*
|
||||||
|
* @param filter 查询过滤条件,包含部门编码、时间范围、分页参数等
|
||||||
|
* @return 分页的部门统计数据列表
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IPage<DeptStatData> getDeptData(DepartStatsFilter filter) {
|
public IPage<DeptStatData> getDeptData(DepartStatsFilter filter) {
|
||||||
return getDeptData(filter, true).pagedData();
|
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) {
|
private DepartStatTransfer getDeptData(DepartStatsFilter filter, boolean paged) {
|
||||||
authCheck(filter);
|
authCheck(filter);
|
||||||
DepartStatTransfer transfer = new DepartStatTransfer()
|
DepartStatTransfer transfer = new DepartStatTransfer()
|
||||||
@@ -639,12 +660,29 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
return transfer;
|
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) {
|
private DeptStatData transferData(SysDepart depart, DepartStatTransfer transfer) {
|
||||||
DeptStatData deptStatData = new DeptStatData();
|
DeptStatData deptStatData = new DeptStatData();
|
||||||
String orgCode = null != depart ? depart.getOrgCode() : null;
|
String orgCode = null != depart ? depart.getOrgCode() : null;
|
||||||
boolean allSecond = StrUtil.isEmpty(transfer.filter().getOrgCode());
|
boolean allSecond = StrUtil.isEmpty(transfer.filter().getOrgCode());
|
||||||
|
boolean isFirst = StrUtil.isNotEmpty(orgCode) && orgCode.length() == 3;
|
||||||
boolean isSecond = StrUtil.isNotEmpty(orgCode) && orgCode.length() == 6;
|
boolean isSecond = StrUtil.isNotEmpty(orgCode) && orgCode.length() == 6;
|
||||||
if (isSecond) {
|
if (isFirst || isSecond) {
|
||||||
|
// 一级/二级部门行:展示自身信息,叶子名称占位
|
||||||
deptStatData.setOrgCode(orgCode);
|
deptStatData.setOrgCode(orgCode);
|
||||||
deptStatData.setOrgCodeLeaf(orgCode);
|
deptStatData.setOrgCodeLeaf(orgCode);
|
||||||
deptStatData.setOrgCodeName(depart.getDepartName());
|
deptStatData.setOrgCodeName(depart.getDepartName());
|
||||||
@@ -655,6 +693,7 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
.getDeviceCount()
|
.getDeviceCount()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// 三级部门行:展示所属上级名称 + 自身名称
|
||||||
deptStatData.setOrgCode(Optional.ofNullable(transfer.secondDepart()).map(SysDepart::getOrgCode).orElse(""));
|
deptStatData.setOrgCode(Optional.ofNullable(transfer.secondDepart()).map(SysDepart::getOrgCode).orElse(""));
|
||||||
deptStatData.setOrgCodeLeaf(orgCode);
|
deptStatData.setOrgCodeLeaf(orgCode);
|
||||||
deptStatData.setOrgCodeName(Optional.ofNullable(transfer.secondDepart()).map(SysDepart::getDepartName).orElse(""));
|
deptStatData.setOrgCodeName(Optional.ofNullable(transfer.secondDepart()).map(SysDepart::getDepartName).orElse(""));
|
||||||
@@ -662,15 +701,21 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
}
|
}
|
||||||
fillWatchNums(
|
fillWatchNums(
|
||||||
deptStatData,
|
deptStatData,
|
||||||
(!allSecond && isSecond)
|
(!allSecond && (isFirst || isSecond))
|
||||||
?
|
?
|
||||||
transfer.secondDepartBindAndWearNum()
|
transfer.secondDepartBindAndWearNum()
|
||||||
:
|
:
|
||||||
transfer.departBindAndWearNumMap().getOrDefault(orgCode, new DepartDeviceNum())
|
transfer.departBindAndWearNumMap().getOrDefault(orgCode, new DepartDeviceNum())
|
||||||
);
|
);
|
||||||
|
// 未分配设备数 = 入库设备数 - 已分配设备数,仅一、二级部门有入库数据
|
||||||
|
if (deptStatData.getTotalAssignedDeviceNums() != null) {
|
||||||
|
deptStatData.setUndistributedDeviceNums(
|
||||||
|
deptStatData.getTotalAssignedDeviceNums() - deptStatData.getAssignedDeviceNums()
|
||||||
|
);
|
||||||
|
}
|
||||||
fillWarningNums(
|
fillWarningNums(
|
||||||
deptStatData,
|
deptStatData,
|
||||||
(!allSecond && isSecond)
|
(!allSecond && (isFirst || isSecond))
|
||||||
?
|
?
|
||||||
transfer.secondDepartAbnormalCount()
|
transfer.secondDepartAbnormalCount()
|
||||||
:
|
:
|
||||||
@@ -679,12 +724,30 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
return deptStatData;
|
return deptStatData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充手表绑定与佩戴相关数量
|
||||||
|
* <p>
|
||||||
|
* assignedDeviceNums = 已绑定设备数
|
||||||
|
* assignedUserNums = 已佩戴人数
|
||||||
|
* undistributedUserNums = 已绑定但未佩戴数(bindCount - wearCount)
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
private void fillWatchNums(DeptStatData deptStatData, DepartDeviceNum departDeviceNum) {
|
private void fillWatchNums(DeptStatData deptStatData, DepartDeviceNum departDeviceNum) {
|
||||||
deptStatData.setAssignedDeviceNums(Optional.ofNullable(departDeviceNum.getBindCount()).orElse(0));
|
deptStatData.setAssignedDeviceNums(Optional.ofNullable(departDeviceNum.getBindCount()).orElse(0));
|
||||||
deptStatData.setAssignedUserNums(Optional.ofNullable(departDeviceNum.getWearCount()).orElse(0));
|
deptStatData.setAssignedUserNums(Optional.ofNullable(departDeviceNum.getWearCount()).orElse(0));
|
||||||
deptStatData.setUndistributedUserNums(deptStatData.getAssignedDeviceNums() - deptStatData.getAssignedUserNums());
|
deptStatData.setUndistributedUserNums(deptStatData.getAssignedDeviceNums() - deptStatData.getAssignedUserNums());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充各健康指标的预警数量
|
||||||
|
* <p>
|
||||||
|
* 从 abnormalCountMap 中按事件类型取值:
|
||||||
|
* stress → 压力预警数
|
||||||
|
* heart_rate → 心率预警数
|
||||||
|
* spo2 → 血氧预警数
|
||||||
|
* temperature_anomalies → 体温预警数
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
private void fillWarningNums(DeptStatData deptStatData, Map<String, AbnormalCount> abnormalCountMap) {
|
private void fillWarningNums(DeptStatData deptStatData, Map<String, AbnormalCount> abnormalCountMap) {
|
||||||
deptStatData.setStressNums(Optional.ofNullable(abnormalCountMap.get("stress")).map(AbnormalCount::getWarningCount).orElse(0));
|
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));
|
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());
|
return sysCache.getDepartByOrgCode(filter.getOrgCodeLeaf());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询各部门的设备总数 Map(orgCode → 设备数)
|
||||||
|
* 当过滤条件指定了三级部门(orgCodeLeaf 不为空)时,设备总数无需展示,直接返回空 Map
|
||||||
|
*/
|
||||||
private Map<String, DepartDeviceNum> getDepartDeviceNumMap(DepartStatsFilter filter) {
|
private Map<String, DepartDeviceNum> getDepartDeviceNumMap(DepartStatsFilter filter) {
|
||||||
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf())) {
|
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf())) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
@@ -732,6 +799,9 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
.collect(Collectors.toMap(DepartDeviceNum::getOrgCode, Function.identity()));
|
.collect(Collectors.toMap(DepartDeviceNum::getOrgCode, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询各部门的设备绑定数与佩戴数 Map(orgCode → 绑定/佩戴数)
|
||||||
|
*/
|
||||||
private Map<String, DepartDeviceNum> getDepartBindAndWearNumMap(DepartStatsFilter filter) {
|
private Map<String, DepartDeviceNum> getDepartBindAndWearNumMap(DepartStatsFilter filter) {
|
||||||
return watchStatisticsMapper.deviceBindAndWearNums(filter)
|
return watchStatisticsMapper.deviceBindAndWearNums(filter)
|
||||||
.stream()
|
.stream()
|
||||||
@@ -739,6 +809,10 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
.collect(Collectors.toMap(DepartDeviceNum::getOrgCode, Function.identity()));
|
.collect(Collectors.toMap(DepartDeviceNum::getOrgCode, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询各部门各事件类型的异常预警数量,结构为双层 Map:
|
||||||
|
* orgCode → (eventType → AbnormalCount)
|
||||||
|
*/
|
||||||
private Map<String, Map<String, AbnormalCount>> getDepartAbnormalCountMap(DepartStatsFilter filter) {
|
private Map<String, Map<String, AbnormalCount>> getDepartAbnormalCountMap(DepartStatsFilter filter) {
|
||||||
return watchStatisticsMapper.abnormalWarningCount(filter)
|
return watchStatisticsMapper.abnormalWarningCount(filter)
|
||||||
.stream()
|
.stream()
|
||||||
@@ -746,6 +820,12 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
.collect(Collectors.groupingBy(AbnormalCount::getOrgCode, Collectors.toMap(AbnormalCount::getEventType, Function.identity())));
|
.collect(Collectors.groupingBy(AbnormalCount::getOrgCode, Collectors.toMap(AbnormalCount::getEventType, Function.identity())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇总二级部门下所有三级部门的绑定/佩戴数,用于二级部门行的数据展示
|
||||||
|
* <p>
|
||||||
|
* 仅在"指定了二级部门但未指定三级部门"时有效,其他情况返回 null
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
private DepartDeviceNum getSecondDepartBindAndWearNum(DepartStatsFilter filter,
|
private DepartDeviceNum getSecondDepartBindAndWearNum(DepartStatsFilter filter,
|
||||||
Map<String, DepartDeviceNum> departBindAndWearNumMap) {
|
Map<String, DepartDeviceNum> departBindAndWearNumMap) {
|
||||||
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf()) || StrUtil.isEmpty(filter.getOrgCode())) {
|
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf()) || StrUtil.isEmpty(filter.getOrgCode())) {
|
||||||
@@ -764,6 +844,13 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
return n1;
|
return n1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇总二级部门下所有三级部门的异常预警数,用于二级部门行的数据展示
|
||||||
|
* <p>
|
||||||
|
* 仅在"指定了二级部门但未指定三级部门"时有效,其他情况返回 null
|
||||||
|
* 合并时按 eventType 累加 warningCount
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
private Map<String, AbnormalCount> getSecondDepartAbnormalCount(DepartStatsFilter filter,
|
private Map<String, AbnormalCount> getSecondDepartAbnormalCount(DepartStatsFilter filter,
|
||||||
Map<String, Map<String, AbnormalCount>> departAbnormalCountMap) {
|
Map<String, Map<String, AbnormalCount>> departAbnormalCountMap) {
|
||||||
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf()) || StrUtil.isEmpty(filter.getOrgCode())) {
|
if (StrUtil.isNotEmpty(filter.getOrgCodeLeaf()) || StrUtil.isEmpty(filter.getOrgCode())) {
|
||||||
@@ -778,6 +865,14 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
return n1;
|
return n1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据过滤条件确定最终需要展示的部门列表,分三种情况:
|
||||||
|
* <ul>
|
||||||
|
* <li>指定三级部门(orgCodeLeaf 不为空):仅返回该三级部门(无绑定数据则返回空)</li>
|
||||||
|
* <li>指定二级部门(orgCode 不为空):返回该部门本身 + 其下有绑定数据的子部门,按层级(orgCode 长度)升序后再按排序字段排序</li>
|
||||||
|
* <li>全量(orgCode 为空):返回所有有设备数据的部门,同样按层级升序后再按排序字段排序,确保一级部门排在最前</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
private List<SysDepart> getFinalDeparts(DepartStatsFilter filter,
|
private List<SysDepart> getFinalDeparts(DepartStatsFilter filter,
|
||||||
Map<String, DepartDeviceNum> departDeviceNumMap,
|
Map<String, DepartDeviceNum> departDeviceNumMap,
|
||||||
Map<String, DepartDeviceNum> departBindAndWearNumMap) {
|
Map<String, DepartDeviceNum> departBindAndWearNumMap) {
|
||||||
@@ -804,7 +899,10 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
return departDeviceNumMap.keySet()
|
return departDeviceNumMap.keySet()
|
||||||
.stream()
|
.stream()
|
||||||
.map(sysBaseAPI::getDepartIdsByOrgCodeNew)
|
.map(sysBaseAPI::getDepartIdsByOrgCodeNew)
|
||||||
.sorted(this::departComparator)
|
.sorted(
|
||||||
|
Comparator.<SysDepart>comparingInt(sysDepart -> sysDepart.getOrgCode().length())
|
||||||
|
.thenComparing(this::departComparator)
|
||||||
|
)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -825,6 +923,9 @@ public class DeptStatisticDataServiceImpl extends ServiceImpl<DeptStatisticDataM
|
|||||||
return order1.compareTo(order2);
|
return order1.compareTo(order2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对内存中的部门列表执行分页截取
|
||||||
|
*/
|
||||||
private List<SysDepart> getPagedDeparts(List<SysDepart> sysDeparts, DepartStatsFilter filter) {
|
private List<SysDepart> getPagedDeparts(List<SysDepart> sysDeparts, DepartStatsFilter filter) {
|
||||||
return getSubList(sysDeparts, filter.getPageNo(), filter.getPageSize());
|
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.collection.CollectionUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@@ -87,6 +88,7 @@ public class WatchBindHisServiceImpl extends ServiceImpl<WatchBindHisMapper, Wat
|
|||||||
@Override
|
@Override
|
||||||
public IPage<WatchBindHisVo> queryPageList(IPage<WatchBindHisVo> page, WatchBindHisDTO dto) {
|
public IPage<WatchBindHisVo> queryPageList(IPage<WatchBindHisVo> page, WatchBindHisDTO dto) {
|
||||||
if (StringUtils.hasLength(dto.getBindRealName())) {
|
if (StringUtils.hasLength(dto.getBindRealName())) {
|
||||||
|
dto.setBindRealName(HtmlUtil.unescape(dto.getBindRealName()));
|
||||||
List<String> userIdsByDepartAndUsername = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", "");
|
List<String> userIdsByDepartAndUsername = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", "");
|
||||||
if (CollectionUtil.isEmpty(userIdsByDepartAndUsername)) {
|
if (CollectionUtil.isEmpty(userIdsByDepartAndUsername)) {
|
||||||
return new Page<>();
|
return new Page<>();
|
||||||
@@ -372,6 +374,7 @@ public class WatchBindHisServiceImpl extends ServiceImpl<WatchBindHisMapper, Wat
|
|||||||
String orgCode = StrUtil.isNotBlank(dto.getThirdDepart())?dto.getThirdDepart() : dto.getSecondDepart();
|
String orgCode = StrUtil.isNotBlank(dto.getThirdDepart())?dto.getThirdDepart() : dto.getSecondDepart();
|
||||||
List<String> userIds = new ArrayList<>();
|
List<String> userIds = new ArrayList<>();
|
||||||
if (StrUtil.isNotBlank(dto.getBindRealName())) {
|
if (StrUtil.isNotBlank(dto.getBindRealName())) {
|
||||||
|
dto.setBindRealName(HtmlUtil.unescape(dto.getBindRealName()));
|
||||||
userIds = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", "");
|
userIds = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", "");
|
||||||
if (CollectionUtil.isEmpty(userIds)) {
|
if (CollectionUtil.isEmpty(userIds)) {
|
||||||
return new Page<>();
|
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.ObjUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@@ -104,6 +105,9 @@ public class WatchDataServiceImpl extends ServiceImpl<WatchDataMapper, WatchData
|
|||||||
}
|
}
|
||||||
|
|
||||||
String sql = QueryGenerator.installAuthJdbc(WatchData.class);
|
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);
|
IPage<WatchData> pageList = watchDataMapper.pageCustom(page, watchData, sql);
|
||||||
if (CollectionUtil.isNotEmpty(pageList.getRecords())) {
|
if (CollectionUtil.isNotEmpty(pageList.getRecords())) {
|
||||||
List<LoginUserNew> loginUserNews = null;
|
List<LoginUserNew> loginUserNews = null;
|
||||||
|
|||||||
+57
-5
@@ -7,6 +7,7 @@ import cn.hutool.core.date.DateUnit;
|
|||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HtmlUtil;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
@@ -167,6 +168,9 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
@Override
|
@Override
|
||||||
public IPage<WatchDeviceVo> queryPageList(IPage<WatchDeviceVo> page, WatchDeviceDTO dto) {
|
public IPage<WatchDeviceVo> queryPageList(IPage<WatchDeviceVo> page, WatchDeviceDTO dto) {
|
||||||
if (StrUtil.isNotBlank(dto.getBindRealName()) || StrUtil.isNotBlank(dto.getWorkNo())) {
|
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());
|
List<String> userIds = watchCloudManager.getUserIdsByDepartAndUsername("", dto.getBindRealName(), "", dto.getWorkNo());
|
||||||
if (CollectionUtil.isEmpty(userIds)) {
|
if (CollectionUtil.isEmpty(userIds)) {
|
||||||
return new Page<>();
|
return new Page<>();
|
||||||
@@ -291,10 +295,20 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
}
|
}
|
||||||
WatchDevice watchDevice = getDeviceByWatchNo(dto.getWatchNo());
|
WatchDevice watchDevice = getDeviceByWatchNo(dto.getWatchNo());
|
||||||
if (watchDevice == null) {
|
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)) {
|
if (isDeviceAlreadyBound(watchDevice, dto)) {
|
||||||
return Result.error("该设备已绑定该员工");
|
return Result.error("该设备已被绑定");
|
||||||
}
|
}
|
||||||
// 查询员工是否有绑定
|
// 查询员工是否有绑定
|
||||||
List<WatchDevice> deviceByUserIdList = getDeviceByUserId(dto.getBindUserId());
|
List<WatchDevice> deviceByUserIdList = getDeviceByUserId(dto.getBindUserId());
|
||||||
@@ -317,14 +331,20 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
}
|
}
|
||||||
// 更新工具信息
|
// 更新工具信息
|
||||||
watchDevice.setBindDate(dto.getBindDate());
|
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);
|
bindUserToDevice(watchDevice, dto);
|
||||||
if (StrUtil.isNotBlank(dto.getBindUserId())) {
|
if (StrUtil.isNotBlank(dto.getBindUserId())) {
|
||||||
// 初始化开关数据
|
// 初始化开关数据
|
||||||
unbindUserAndInitDeviceSwitch(dto);
|
unbindUserAndInitDeviceSwitch(dto);
|
||||||
}
|
}
|
||||||
LoginUser userById = sysBaseAPI.getUserById(dto.getBindUserId());
|
|
||||||
// 此处修改 只有是职工的是才赋值单位信息
|
// 此处修改 只有是职工的是才赋值单位信息
|
||||||
if (dto.getIsEmployee()){
|
if (dto.getIsEmployee()) {
|
||||||
SysDepart inDepart = sysCache.getDepartByOrgCode(userById.getOrgCode());
|
SysDepart inDepart = sysCache.getDepartByOrgCode(userById.getOrgCode());
|
||||||
dto.setOrgCode(inDepart.getOrgCode());
|
dto.setOrgCode(inDepart.getOrgCode());
|
||||||
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(userById.getOrgCode()));
|
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(userById.getOrgCode()));
|
||||||
@@ -350,6 +370,17 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
return Result.ok("操作成功");
|
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
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Result unbundleUser(BindUserDTO dto) throws ParseException {
|
public Result unbundleUser(BindUserDTO dto){
|
||||||
WatchDevice watchDevice = getDeviceByWatchNo(dto.getWatchNo());
|
WatchDevice watchDevice = getDeviceByWatchNo(dto.getWatchNo());
|
||||||
if (watchDevice == null) {
|
if (watchDevice == null) {
|
||||||
return Result.error("该设备不存在");
|
return Result.error("该设备不存在");
|
||||||
@@ -409,6 +440,17 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
return Result.ok("操作成功");
|
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.eq("id", watchDevice.getId());
|
||||||
updateWrapper.set("bind_user_id", dto.getBindUserId());
|
updateWrapper.set("bind_user_id", dto.getBindUserId());
|
||||||
updateWrapper.set("bind_date", watchDevice.getBindDate());
|
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);
|
this.update(updateWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,6 +521,8 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
switchUpdateWrapper.eq(WatchDeviceSwitch::getWatchNo, dto.getWatchNo());
|
switchUpdateWrapper.eq(WatchDeviceSwitch::getWatchNo, dto.getWatchNo());
|
||||||
watchDeviceSwitchService.update(switchUpdateWrapper);
|
watchDeviceSwitchService.update(switchUpdateWrapper);
|
||||||
String[] wdTypes = {"heart_rate", "spo2", "stress", "temperature"};
|
String[] wdTypes = {"heart_rate", "spo2", "stress", "temperature"};
|
||||||
|
// 异步调用外部系统,防止外部系统异常导致功能异常,解绑这里非必要
|
||||||
|
asyncTaskExecutor.execute(() -> {
|
||||||
for (int i = 0; i < wdTypes.length; i++) {
|
for (int i = 0; i < wdTypes.length; i++) {
|
||||||
WarnSwitch warnSwitch = new WarnSwitch();
|
WarnSwitch warnSwitch = new WarnSwitch();
|
||||||
warnSwitch.setWatchNo(dto.getWatchNo());
|
warnSwitch.setWatchNo(dto.getWatchNo());
|
||||||
@@ -481,6 +532,7 @@ public class WatchDeviceServiceImpl extends ServiceImpl<WatchDeviceMapper, Watch
|
|||||||
}
|
}
|
||||||
//通知更新
|
//通知更新
|
||||||
dataCenterExecutor.cmdUpdateSwitch(dto.getWatchNo());
|
dataCenterExecutor.cmdUpdateSwitch(dto.getWatchNo());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+99
@@ -265,6 +265,102 @@ public class WatchMonitorDataServiceImpl extends ServiceImpl<WatchMonitorDataMap
|
|||||||
return ajaxJson;
|
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 异常数据
|
* @return 异常数据
|
||||||
*/
|
*/
|
||||||
private WatchMonitorData createMonitorData(WatchNotifyVo data) {
|
private WatchMonitorData createMonitorData(WatchNotifyVo data) {
|
||||||
|
|
||||||
log.info("开始构建异常数据 => {}", JSONObject.toJSONString(data));
|
log.info("开始构建异常数据 => {}", JSONObject.toJSONString(data));
|
||||||
WatchMonitorData monitorDataEntity = new WatchMonitorData();
|
WatchMonitorData monitorDataEntity = new WatchMonitorData();
|
||||||
monitorDataEntity.setAddress(data.getAddress());
|
monitorDataEntity.setAddress(data.getAddress());
|
||||||
monitorDataEntity.setWatchNo(data.getWatchNo());
|
monitorDataEntity.setWatchNo(data.getWatchNo());
|
||||||
monitorDataEntity.setLon(data.getLon());
|
monitorDataEntity.setLon(data.getLon());
|
||||||
monitorDataEntity.setLat(data.getLat());
|
monitorDataEntity.setLat(data.getLat());
|
||||||
|
monitorDataEntity.setLocateTime(data.getLocateTime());
|
||||||
monitorDataEntity.setEventType(data.getEventType());
|
monitorDataEntity.setEventType(data.getEventType());
|
||||||
|
monitorDataEntity.setEventId(data.getEventId());
|
||||||
monitorDataEntity.setDataValue(data.getDataValue());
|
monitorDataEntity.setDataValue(data.getDataValue());
|
||||||
monitorDataEntity.setCreateDate(data.getCreateTime());
|
monitorDataEntity.setCreateDate(data.getCreateTime());
|
||||||
monitorDataEntity.setWarnTime(data.getWarnTime());
|
monitorDataEntity.setWarnTime(data.getWarnTime());
|
||||||
|
|||||||
@@ -721,4 +721,9 @@ public interface CommonConstant {
|
|||||||
* 多选
|
* 多选
|
||||||
*/
|
*/
|
||||||
String PD = "PD";
|
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.constant.CommonConstant;
|
||||||
import org.jeecg.common.exception.ExceptionAssertsUtil;
|
import org.jeecg.common.exception.ExceptionAssertsUtil;
|
||||||
import org.jeecg.common.util.filter.StrAttackFilter;
|
import org.jeecg.common.util.filter.StrAttackFilter;
|
||||||
|
import org.jeecg.util.GlobalManager;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -51,7 +52,9 @@ public class MyUploadUtil {
|
|||||||
private static String muFileListUploadPath;
|
private static String muFileListUploadPath;
|
||||||
private static String muDownPath;
|
private static String muDownPath;
|
||||||
private static String fileTypeWhiteList;
|
private static String fileTypeWhiteList;
|
||||||
// private static final int timeout = 10000;
|
/** 全局配置管理器,用于读取 Redis 中的动态配置 */
|
||||||
|
private static GlobalManager globalManager;
|
||||||
|
// private static final int timeout = 10000;
|
||||||
//有些大文件可能需要比较长的时间,这里改成5分钟
|
//有些大文件可能需要比较长的时间,这里改成5分钟
|
||||||
private static final int socketTimeout = 10*60*1000;
|
private static final int socketTimeout = 10*60*1000;
|
||||||
private static final int connectTimeout = 60*1000;
|
private static final int connectTimeout = 60*1000;
|
||||||
@@ -108,6 +111,10 @@ public class MyUploadUtil {
|
|||||||
MyUploadUtil.fileTypeWhiteList = fileTypeWhiteList;
|
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 {
|
public static String upload(MultipartFile file, String bizPath) throws Exception {
|
||||||
String fileUrl = "";
|
|
||||||
String fileName = file.getOriginalFilename();// 获取文件名
|
String fileName = file.getOriginalFilename();// 获取文件名
|
||||||
checkFileLegitimacy(fileName);
|
checkFileLegitimacy(fileName);
|
||||||
//???先屏蔽了
|
|
||||||
// if (StringUtils.hasText(file.getOriginalFilename())) {
|
|
||||||
// String originalFilename = file.getOriginalFilename();
|
|
||||||
// String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
|
|
||||||
// fileName = fileName + ext;
|
|
||||||
// }
|
|
||||||
bizPath = StrAttackFilter.filter(bizPath);
|
bizPath = StrAttackFilter.filter(bizPath);
|
||||||
|
// .tar 文件固定走 MyUpload
|
||||||
|
if (fileName != null && (fileName.endsWith(".tar") || fileName.endsWith(".apk"))) {
|
||||||
|
log.info("上传文件为tar/apk文件,使用MyUploadUtil上传");
|
||||||
try {
|
try {
|
||||||
fileUrl = upload(file.getInputStream(), bizPath, fileName);
|
return upload(file.getInputStream(), bizPath, fileName,true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
fileUrl = null;
|
log.error("upload error {}", e.getMessage(), e);
|
||||||
log.error("upload error", 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 {
|
||||||
|
return upload(file.getInputStream(), bizPath, fileName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("upload error {}", e.getMessage(), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return fileUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* 上传文件 带缩略图
|
// * 上传文件 带缩略图
|
||||||
*
|
// *
|
||||||
* @param file
|
// * @param file
|
||||||
* @return
|
// * @return
|
||||||
*/
|
// */
|
||||||
public static String uploadImg(MultipartFile file, String bizPath, Boolean needThumbnailFlag) throws Exception {
|
// public static String uploadImg(MultipartFile file, String bizPath, Boolean needThumbnailFlag) throws Exception {
|
||||||
String fileUrl = "";
|
// String fileUrl = "";
|
||||||
String fileName = file.getOriginalFilename();// 获取文件名
|
// String fileName = file.getOriginalFilename();// 获取文件名
|
||||||
checkFileLegitimacy(fileName);
|
// checkFileLegitimacy(fileName);
|
||||||
bizPath = StrAttackFilter.filter(bizPath);
|
// bizPath = StrAttackFilter.filter(bizPath);
|
||||||
try {
|
// try {
|
||||||
fileUrl = uploadImg(file.getInputStream(), bizPath, fileName, needThumbnailFlag);
|
// fileUrl = uploadImg(file.getInputStream(), bizPath, fileName, needThumbnailFlag);
|
||||||
} catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
log.error("upload error", e);
|
// log.error("upload error", e);
|
||||||
}
|
// }
|
||||||
return fileUrl;
|
// return fileUrl;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public static String upload(InputStream stream, String bizPath, String fileName, boolean check) throws Exception {
|
public static String upload(InputStream stream, String bizPath, String fileName, boolean check) throws Exception {
|
||||||
if (check) {
|
if (check) {
|
||||||
@@ -220,145 +236,152 @@ public class MyUploadUtil {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String upload(InputStream stream, String bizPath, String fileName) throws Exception {
|
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);
|
return upload(stream, bizPath, fileName, true);
|
||||||
}
|
}
|
||||||
/**
|
// /**
|
||||||
* 上传文件到myupload
|
// * 上传文件到myupload
|
||||||
*
|
// *
|
||||||
* @param stream
|
// * @param stream
|
||||||
* @return
|
// * @return
|
||||||
*/
|
// */
|
||||||
public static String uploadImg(InputStream stream, String bizPath, String fileName, Boolean needThumbnailFlag) throws Exception {
|
// public static String uploadImg(InputStream stream, String bizPath, String fileName, Boolean needThumbnailFlag) throws Exception {
|
||||||
//避免重复文件覆盖,这里处理一下
|
// //避免重复文件覆盖,这里处理一下
|
||||||
if (fileName.indexOf(".") > -1) {
|
// if (fileName.indexOf(".") > -1) {
|
||||||
//String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
|
// //String fileNameLeft = fileName.substring(0, fileName.lastIndexOf("."));
|
||||||
String fileNameLeft = UUID.randomUUID().toString();
|
// String fileNameLeft = UUID.randomUUID().toString();
|
||||||
fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
|
// fileName = fileNameLeft + "_" + System.currentTimeMillis() + fileName.substring(fileName.lastIndexOf("."));
|
||||||
} else {
|
// } else {
|
||||||
fileName += "_" + System.currentTimeMillis();
|
// fileName += "_" + System.currentTimeMillis();
|
||||||
}
|
// }
|
||||||
String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
// String nowday = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||||
String path = bizPath + File.separator + nowday;
|
// String path = bizPath + File.separator + nowday;
|
||||||
String url = muUrl + "/" + muFileUploadPath;
|
// String url = muUrl + "/" + muFileUploadPath;
|
||||||
String fileUrl = "";
|
// String fileUrl = "";
|
||||||
//创建post方法连接实例,在post方法中传入待连接地址
|
// //创建post方法连接实例,在post方法中传入待连接地址
|
||||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
// CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||||
CloseableHttpResponse httpResponse = null;
|
// 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);
|
// public String getMuFileExistPath() {
|
||||||
|
// return muFileExistPath;
|
||||||
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 static void setMuFileExistPath(String muFileExistPath) {
|
public static void setMuFileExistPath(String muFileExistPath) {
|
||||||
MyUploadUtil.muFileExistPath = 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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.jeecg.common.util.MyUploadUtil;
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -29,6 +31,9 @@ public class MyUploadConfig {
|
|||||||
@Value(value = "${jeecg.myupload.mu_file_type_white_list}")
|
@Value(value = "${jeecg.myupload.mu_file_type_white_list}")
|
||||||
private String fileTypeWhiteList;
|
private String fileTypeWhiteList;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private GlobalManager globalManager;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public void initMu() {
|
public void initMu() {
|
||||||
MyUploadUtil.setMuUrl(muUrl);
|
MyUploadUtil.setMuUrl(muUrl);
|
||||||
@@ -39,6 +44,7 @@ public class MyUploadConfig {
|
|||||||
MyUploadUtil.setMuFileListUploadPath(muFileListUploadPath);
|
MyUploadUtil.setMuFileListUploadPath(muFileListUploadPath);
|
||||||
MyUploadUtil.setMuDownPath(muDownPath);
|
MyUploadUtil.setMuDownPath(muDownPath);
|
||||||
MyUploadUtil.setFileTypeWhiteList(fileTypeWhiteList);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,7 @@ public class ShiroConfig {
|
|||||||
filterChainDefinitionMap.put("/api/qimo/callback", "anon"); // 云坐席推送不鉴权
|
filterChainDefinitionMap.put("/api/qimo/callback", "anon"); // 云坐席推送不鉴权
|
||||||
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
|
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
|
||||||
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
|
||||||
|
filterChainDefinitionMap.put("/consultation/conSession/getPicUrl", "anon"); // 移动端首页获取图片接口排除
|
||||||
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
|
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
|
||||||
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
|
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
|
||||||
filterChainDefinitionMap.put("/sys/loginAnYan", "anon"); //登录接口排除(三方授权)
|
filterChainDefinitionMap.put("/sys/loginAnYan", "anon"); //登录接口排除(三方授权)
|
||||||
@@ -184,6 +185,8 @@ public class ShiroConfig {
|
|||||||
|
|
||||||
filterChainDefinitionMap.put("/version/getSysVersionDetailByPackageName", "anon");
|
filterChainDefinitionMap.put("/version/getSysVersionDetailByPackageName", "anon");
|
||||||
filterChainDefinitionMap.put("/sys/api/notice/selectIosUpdate", "anon");
|
filterChainDefinitionMap.put("/sys/api/notice/selectIosUpdate", "anon");
|
||||||
|
// filterChainDefinitionMap.put("/sys/upload/down/**", "anon");
|
||||||
|
// filterChainDefinitionMap.put("/sys/upload/show/**", "anon");
|
||||||
|
|
||||||
// 添加自己的过滤器并且取名为jwt
|
// 添加自己的过滤器并且取名为jwt
|
||||||
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||||
|
|||||||
@@ -10,9 +10,21 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum ExportExcelEnum {
|
public enum ExportExcelEnum {
|
||||||
/**
|
/**
|
||||||
* 积分规则
|
* 员工积分信息
|
||||||
*/
|
*/
|
||||||
BANK_POINTS_RULE("bcsStationExportCode", "站点数据", "/bcs/station", null),
|
BANK_USER_POINTS("bankUserPointsExportCode", "员工积分信息", "/bank/userPoints", null),
|
||||||
|
/**
|
||||||
|
* 员工积分明细
|
||||||
|
*/
|
||||||
|
BANK_POINTS_DETAIL("bankPointsDetailExportCode", "员工积分明细", "/bank/pointsDetail", null),
|
||||||
|
/**
|
||||||
|
* 员工打卡监测
|
||||||
|
*/
|
||||||
|
BANK_CLOCK_IN_RECORD("bankClockInRecordExportCode", "员工打卡监测", "/bank/clockInRecord", null),
|
||||||
|
/**
|
||||||
|
* 员工积分排名
|
||||||
|
*/
|
||||||
|
BANK_USER_RANKING("bankUserRankingExportCode", "员工积分排名", "/bank/userRanking", null),
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,7 +49,12 @@ public enum ServerEnum {
|
|||||||
/**
|
/**
|
||||||
* 医疗点模块
|
* 医疗点模块
|
||||||
*/
|
*/
|
||||||
WATCH("health-watch", "watchCenter");
|
WATCH("health-watch", "watchCenter"),
|
||||||
|
/**
|
||||||
|
* 健康银行模块
|
||||||
|
*/
|
||||||
|
BANK("health-bank", "bank"),
|
||||||
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务名
|
* 服务名
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package org.jeecg.util;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ContainerMetricsUtil {
|
||||||
|
|
||||||
|
// 缓存上一次 CPU 使用数据(用于计算使用率)
|
||||||
|
private static final Map<String, Long> LAST_CPU_USAGE = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<String, Long> LAST_CPU_TIME = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 判断是否为 cgroup v2
|
||||||
|
private static boolean isCgroupV2() {
|
||||||
|
return Files.exists(Paths.get("/sys/fs/cgroup/cgroup.controllers"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用读取第一行
|
||||||
|
private static String readFirstLine(String path) throws IOException {
|
||||||
|
java.nio.file.Path p = Paths.get(path);
|
||||||
|
if (!Files.exists(p)) {
|
||||||
|
throw new IOException("文件不存在: " + path);
|
||||||
|
}
|
||||||
|
List<String> lines = Files.readAllLines(p);
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
throw new IOException("文件为空: " + path);
|
||||||
|
}
|
||||||
|
return lines.get(0).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// 内存指标
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
public static long getMemoryLimitBytes() {
|
||||||
|
try {
|
||||||
|
if (isCgroupV2()) {
|
||||||
|
String max = readFirstLine("/sys/fs/cgroup/memory.max");
|
||||||
|
if ("max".equals(max)) return -1; // 无限制
|
||||||
|
return Long.parseLong(max);
|
||||||
|
} else {
|
||||||
|
long limit = Long.parseLong(readFirstLine("/sys/fs/cgroup/memory/memory.limit_in_bytes"));
|
||||||
|
// 过滤“无限制”的极大值(如 9223372036854771712)
|
||||||
|
return (limit > (1L << 40)) ? -1 : limit;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("无法读取内存限制", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getMemoryUsageBytes() {
|
||||||
|
try {
|
||||||
|
if (isCgroupV2()) {
|
||||||
|
return Long.parseLong(readFirstLine("/sys/fs/cgroup/memory.current"));
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(readFirstLine("/sys/fs/cgroup/memory/memory.usage_in_bytes"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("无法读取内存使用量", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// CPU 指标
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 CPU 限制(核数),-1 表示无限制
|
||||||
|
*/
|
||||||
|
public static double getCpuLimitCores() {
|
||||||
|
try {
|
||||||
|
if (isCgroupV2()) {
|
||||||
|
String cpuMax = readFirstLine("/sys/fs/cgroup/cpu.max");
|
||||||
|
String[] parts = cpuMax.split(" ");
|
||||||
|
if ("max".equals(parts[0])) return -1;
|
||||||
|
long quota = Long.parseLong(parts[0]);
|
||||||
|
long period = Long.parseLong(parts[1]);
|
||||||
|
return (double) quota / period;
|
||||||
|
} else {
|
||||||
|
long quota = Long.parseLong(readFirstLine("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"));
|
||||||
|
if (quota == -1) return -1;
|
||||||
|
long period = Long.parseLong(readFirstLine("/sys/fs/cgroup/cpu/cpu.cfs_period_us"));
|
||||||
|
return (double) quota / period;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("无法读取 CPU 限制", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 CPU 使用率(百分比,单核基准)
|
||||||
|
* 首次调用返回 0,后续调用返回基于时间差的使用率
|
||||||
|
*/
|
||||||
|
public static double getCpuUsagePercent() {
|
||||||
|
try {
|
||||||
|
long currentUsage;
|
||||||
|
if (isCgroupV2()) {
|
||||||
|
// 读取 /sys/fs/cgroup/cpu.stat
|
||||||
|
String content = readFirstLine("/sys/fs/cgroup/cpu.stat");
|
||||||
|
String[] lines = content.split("\n");
|
||||||
|
long usageUsec = -1;
|
||||||
|
for (String line : lines) {
|
||||||
|
if (line.startsWith("usage_usec")) {
|
||||||
|
String[] parts = line.trim().split("\\s+");
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
usageUsec = Long.parseLong(parts[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (usageUsec == -1) {
|
||||||
|
log.warn("cgroup v2: 未找到 usage_usec,无法计算 CPU 使用率");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
currentUsage = usageUsec * 1000L; // 转为纳秒
|
||||||
|
} else {
|
||||||
|
// cgroup v1
|
||||||
|
currentUsage = Long.parseLong(readFirstLine("/sys/fs/cgroup/cpu/cpuacct.usage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentTime = System.nanoTime();
|
||||||
|
String key = isCgroupV2() ? "v2" : "v1";
|
||||||
|
|
||||||
|
Long lastUsage = LAST_CPU_USAGE.get(key);
|
||||||
|
Long lastTime = LAST_CPU_TIME.get(key);
|
||||||
|
|
||||||
|
if (lastUsage == null || lastTime == null) {
|
||||||
|
LAST_CPU_USAGE.put(key, currentUsage);
|
||||||
|
LAST_CPU_TIME.put(key, currentTime);
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long usageDelta = currentUsage - lastUsage;
|
||||||
|
long timeDeltaNs = currentTime - lastTime;
|
||||||
|
|
||||||
|
if (timeDeltaNs <= 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double cpuPercent = (usageDelta * 100.0) / timeDeltaNs;
|
||||||
|
|
||||||
|
// 归一化(可选)
|
||||||
|
double limit = getCpuLimitCores();
|
||||||
|
if (limit > 0 && limit < 1.0) {
|
||||||
|
cpuPercent = Math.min(cpuPercent / limit, 100.0);
|
||||||
|
} else {
|
||||||
|
cpuPercent = Math.min(cpuPercent, 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
LAST_CPU_USAGE.put(key, currentUsage);
|
||||||
|
LAST_CPU_TIME.put(key, currentTime);
|
||||||
|
|
||||||
|
return Math.max(0.0, cpuPercent);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("计算 CPU 使用率失败", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// 综合方法:返回所有指标
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
public static Map<String, Object> getContainerMetrics() {
|
||||||
|
Map<String, Object> metrics = new HashMap<>();
|
||||||
|
|
||||||
|
// 内存
|
||||||
|
long memLimit = getMemoryLimitBytes();
|
||||||
|
long memUsage = getMemoryUsageBytes();
|
||||||
|
metrics.put("memoryLimitBytes", memLimit > 0 ? memLimit : null);
|
||||||
|
metrics.put("memoryUsedBytes", memUsage > 0 ? memUsage : null);
|
||||||
|
if (memLimit > 0 && memUsage > 0) {
|
||||||
|
double memPercent = Math.round((memUsage * 100.0 / memLimit) * 100) / 100.0;
|
||||||
|
metrics.put("memoryPercent", memPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
double cpuLimit = getCpuLimitCores();
|
||||||
|
double cpuUsage = getCpuUsagePercent();
|
||||||
|
metrics.put("cpuLimitCores", cpuLimit > 0 ? cpuLimit : null);
|
||||||
|
metrics.put("cpuUsagePercent", cpuUsage >= 0 ? cpuUsage : null);
|
||||||
|
|
||||||
|
// JVM 堆内存
|
||||||
|
long heapUsed = java.lang.management.ManagementFactory.getMemoryMXBean()
|
||||||
|
.getHeapMemoryUsage().getUsed();
|
||||||
|
metrics.put("jvmHeapUsedBytes", heapUsed);
|
||||||
|
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助:格式化字节(仅用于日志,不放入返回值)
|
||||||
|
private static String formatBytes(long bytes) {
|
||||||
|
if (bytes <= 0) return "0 B";
|
||||||
|
if (bytes < 1024 * 1024) {
|
||||||
|
return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||||
|
} else if (bytes < 1024 * 1024 * 1024) {
|
||||||
|
return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||||
|
} else {
|
||||||
|
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 便捷方法:记录日志 + 返回数据
|
||||||
|
public static Map<String, Object> logAndCollectMetrics() {
|
||||||
|
Map<String, Object> m = getContainerMetrics();
|
||||||
|
log.info(
|
||||||
|
"容器资源 | 内存: {} / {} ({}%) | CPU: {}% (限制: {}核) | JVM堆: {}",
|
||||||
|
m.get("memoryUsedBytes") != null ? formatBytes((Long) m.get("memoryUsedBytes")) : "N/A",
|
||||||
|
m.get("memoryLimitBytes") != null ? formatBytes((Long) m.get("memoryLimitBytes")) : "N/A",
|
||||||
|
m.get("memoryPercent"),
|
||||||
|
m.get("cpuUsagePercent"),
|
||||||
|
m.get("cpuLimitCores"),
|
||||||
|
formatBytes((Long) m.get("jvmHeapUsedBytes"))
|
||||||
|
);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user