【积分银行】积分

This commit is contained in:
2026-02-09 16:30:50 +08:00
parent 03f8be4c98
commit 6e005f2f4e
20 changed files with 719 additions and 48 deletions
@@ -6,6 +6,9 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.jeecg.base.PageInfo;
import java.util.Date;
import java.util.List;
/**
* @author pj
* @version 1.0
@@ -16,7 +19,27 @@ import org.jeecg.base.PageInfo;
@Data
public class UserPointsRankingDTO extends PageInfo<PointsRule> {
@Schema(title = "榜单类型,0榜,1周榜,2月榜")
@Schema(title = "榜单类型,0榜,1周榜,2月榜3季榜,4年榜,5选时排名,10总榜")
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 = "最后修改时间")
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.Data;
import lombok.NoArgsConstructor;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
@@ -40,9 +41,11 @@ public class UserPoints implements Serializable {
@Schema(title = "用户部门")
private String orgCode;
@Excel(name = "当前可用积分", width = 15, orderNum = "7")
@Schema(title = "当前可用积分")
private BigDecimal points;
@Excel(name = "累计获得积分", width = 15, orderNum = "8")
@Schema(title = "历史获得的积分总和")
private BigDecimal addPointsSum;
@@ -55,6 +58,7 @@ public class UserPoints implements Serializable {
/**
* 积分兑换记录总和,只记录积分兑换
*/
@Excel(name = "累计兑换积分", width = 15, orderNum = "9")
@Schema(title = "历史兑换积分总和")
private BigDecimal exchangePointsSum;
@@ -71,6 +75,7 @@ public class UserPoints implements Serializable {
@Schema(title = "最新一天消耗积分的总和")
private BigDecimal latestDeductDaySum;
@Excel(name = "最近一次打卡时间", width = 25, format = "yyyy-MM-dd HH:mm:ss", orderNum = "10")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(title = "最新获得积分的日期")
@@ -2,6 +2,7 @@ package com.renkang.bank.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import java.math.BigDecimal;
@@ -14,27 +15,44 @@ import java.math.BigDecimal;
@Data
public class UserPointRankingVO {
@Excel(name = "排名", width = 15, orderNum = "8")
@Schema(title = "排名")
private Integer ranking;
@Schema(title = "用户id")
private String userId;
@Excel(name = "姓名", width = 15, orderNum = "3")
@Schema(title = "姓名")
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 = "头像")
private String avatar;
@Excel(name = "所属单位", width = 15, orderNum = "1")
@Schema(title = "单位名称")
private String orgName;
@Excel(name = "所属部门", width = 15, orderNum = "2")
@Schema(title = "部门名称")
private String deptName;
@Schema(title = "性别")
private Integer sex;
@Excel(name = "性别", width = 15, orderNum = "5")
@Schema(title = "性别")
private String sexName;
@Excel(name = "日累计积分", width = 15, orderNum = "7")
@Schema(title = "积分")
private BigDecimal pointSum;
}
@@ -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;
}
@@ -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;
}
@@ -4,6 +4,7 @@ import com.renkang.bank.model.po.UserPoints;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.jeecgframework.poi.excel.annotation.Excel;
/**
* @author pj
@@ -15,21 +16,27 @@ import lombok.EqualsAndHashCode;
@Data
public class UserPointsListVO extends UserPoints {
@Excel(name = "姓名", width = 15, orderNum = "3")
@Schema(title = "姓名")
private String realName;
@Excel(name = "员工编号", width = 15, orderNum = "4")
@Schema(title = "员工编号")
private String workNo;
@Excel(name = "所属单位", width = 15, orderNum = "1")
@Schema(title = "所属单位名称")
private String orgName;
@Excel(name = "所属部门", width = 15, orderNum = "2")
@Schema(title = "所属部门名称")
private String deptName;
@Excel(name = "性别", width = 15, orderNum = "5")
@Schema(title = "性别")
private String sexName;
@Excel(name = "年龄", width = 15, orderNum = "6")
@Schema(title = "年龄")
private Integer age;
}
@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.renkang.bank.model.dto.UpdUserPointsDTO;
import com.renkang.bank.model.dto.UserPointsPageDTO;
import com.renkang.bank.model.dto.UserPointsRankingDTO;
import com.renkang.bank.model.po.UserPointsDetails;
import com.renkang.bank.model.vo.*;
import com.renkang.bank.service.IUserPointsService;
import io.swagger.v3.oas.annotations.Operation;
@@ -30,9 +29,9 @@ public class UserPointsController {
private IUserPointsService iUserPointsService;
@Operation(summary = "测试积分变更", description = "测试积分变更")
@PostMapping(value = "/updateUserPoints")
public Result<Boolean> updateUserPoints(@RequestBody UpdUserPointsDTO dto) {
return Result.OK(iUserPointsService.updateUserPoints(dto));
@PostMapping(value = "/testUpdateUserPoints")
public Result<Boolean> testUpdateUserPoints(@RequestBody UpdUserPointsDTO dto) {
return Result.OK(iUserPointsService.testUpdateUserPoints(dto));
}
@Operation(summary = "用户积分信息-分页列表", description = "用户积分信息-分页列表")
@@ -41,12 +40,26 @@ public class UserPointsController {
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 = "用户积分信息-积分明细")
@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));
}
@Operation(summary = "用户积分信息-积分明细(导出)", description = "用户积分信息-积分明细(导出)")
@PostMapping(value = "/exportPointsDetail")
public Result<String> exportPointsDetail(@RequestBody UserPointsPageDTO dto) {
iUserPointsService.exportPointsDetail(dto);
return Result.OK("导出成功,请在导出记录查看");
}
@Operation(summary = "用户积分信息-扣除积分", description = "用户积分信息-扣除积分")
@PostMapping(value = "/deductPoint")
public Result<Boolean> deductPoint(@RequestBody UpdUserPointsDTO dto) {
@@ -59,6 +72,26 @@ public class UserPointsController {
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我的积分")
@GetMapping(value = "/app/myPoints")
public Result<UserPointsInfoVO> myPoints() {
@@ -2,6 +2,7 @@ package com.renkang.bank.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
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.vo.UserPointRankingVO;
import org.apache.ibatis.annotations.Mapper;
@@ -11,6 +12,8 @@ import java.util.Date;
@Mapper
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);
}
@@ -3,6 +3,53 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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">
WITH UserPointsSummary AS (
SELECT
@@ -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);
}
}
@@ -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);
}
}
@@ -11,16 +11,26 @@ import java.util.List;
public interface IUserPointsService {
boolean updateUserPoints(UpdUserPointsDTO dto);
boolean testUpdateUserPoints(UpdUserPointsDTO 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);
IPage<UserPointsMonitorVO> clockInMonitor(UserPointsPageDTO dto);
void exportClockInMonitor(UserPointsPageDTO dto);
IPage<UserPointRankingVO> userRanking(UserPointsRankingDTO dto);
void exportUserRanking(UserPointsRankingDTO dto);
UserPointsInfoVO myPoints();
List<UserPointsTaskVO> pointsTaskList();
@@ -1,5 +1,6 @@
package com.renkang.bank.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
@@ -27,13 +28,18 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.exception.ExceptionAssertsUtil;
import org.jeecg.common.export.GenericExportHandler;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.enums.ExportExcelEnum;
import org.jeecg.global.GlobalUtils;
import org.jeecg.modules.manager.ISysCache;
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 javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@@ -108,28 +114,106 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
return voPage;
}
@Resource
private AsyncTaskExecutor asyncTaskExecutor;
/**
* @description: 导出数据(设备数据)
* @author PengJ
* @date 2025/11/18 14:57
*/
@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())) {
ExceptionAssertsUtil.fail("用户id不能为空");
}
if (ObjectUtil.isNull(dto.getChangeType())) {
ExceptionAssertsUtil.fail("收支类型不能为空");
}
Page<UserPointsDetails> page = new Page<>(dto.getPageNo(), dto.getPageSize());
LambdaQueryWrapper<UserPointsDetails> queryWrapper = new LambdaQueryWrapper<>();
Page<UserPointsDetailsVO> page = new Page<>(dto.getPageNo(), dto.getPageSize());
MPJLambdaWrapper<UserPointsDetails> queryWrapper = new MPJLambdaWrapper<>();
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::getChangeType, dto.getChangeType());
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
public boolean deductPoint(UpdUserPointsDTO dto) {
dto.setSourceType(PointsSourceEnum.TYPE_2_VIOLATION.getSource());
return updateUserPoints(dto);
LoginUser loginUser = GlobalUtils.getLoginUser();
return updateUserPoints(dto, loginUser);
}
@Override
@@ -138,14 +222,12 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
MPJLambdaWrapper<UserPointsDetails> queryWrapper = new MPJLambdaWrapper<>();
queryWrapper.selectAll(UserPointsDetails.class);
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())) {
//根据姓名和工号查询用户
List<String> userIdList = iSysBaseAPI.getUserIdsByDepartAndUsername(null, dto.getRealName(), null, dto.getWorkNo());
queryWrapper.in(!userIdList.isEmpty(), UserPoints::getUserId, userIdList);
}
// 动态排序
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
if (StringUtils.isNotBlank(dto.getOrgCode())) {
queryWrapper.likeRight(UserPoints::getOrgCode, dto.getOrgCode());
@@ -154,6 +236,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
queryWrapper.likeRight(UserPoints::getOrgCode, dto.getDeptCode());
}
queryWrapper.leftJoin(PointsRule.class, PointsRule::getId, UserPointsDetails::getBusinessId);
queryWrapper.orderByDesc(UserPointsDetails::getCreateTime);
Page<UserPointsMonitorVO> voPage = userPointsDetailsMapper.selectJoinPage(page, UserPointsMonitorVO.class, queryWrapper);
List<String> userIds = voPage.getRecords().stream().map(UserPointsMonitorVO::getUserId).collect(Collectors.toList());
if (!userIds.isEmpty()) {
@@ -179,15 +262,140 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
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 (ObjUtil.isNotEmpty(secondDepart)) {
vo.setDeptName(secondDepart.getDepartName());
}
SysDepart thirdDepart = sysCache.getDepartByOrgCode(GlobalUtils.getThirdDepartOrgCode(loginUser.getOrgCode()));
if (ObjUtil.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) {
LoginUser loginUser = GlobalUtils.getLoginUser();
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("用户:{}({})更新积分异常,入参:{}", loginUser.getRealname(), loginUser.getUsername(), dto, e);
}
});
return true;
}
/**
* 更新用户积分
*
* @param dto
* @return
*/
@Override
public boolean updateUserPoints(UpdUserPointsDTO dto) {
LoginUser loginUser = GlobalUtils.getLoginUser();
public boolean updateUserPoints(UpdUserPointsDTO dto, LoginUser loginUser) {
log.info("------用户:{}({})开始更新积分,入参:{}------", loginUser.getRealname(), loginUser.getUsername(), dto);
PointsSourceEnum sourceEnum = PointsSourceEnum.getBySource(dto.getSourceType());
if (sourceEnum == null) {
@@ -203,7 +411,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
//违规扣除
userId = dto.getUserId();
} else {
userId = GlobalUtils.getLoginUser().getId();
userId = loginUser.getId();
}
UserPoints userPoints = baseMapper.selectOne(new LambdaQueryWrapper<UserPoints>()
.eq(UserPoints::getUserId, userId)
@@ -253,7 +461,7 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
List<UserPointsDetails> details = userPointsDetailsMapper.selectList(new LambdaQueryWrapper<UserPointsDetails>()
.eq(UserPointsDetails::getUserId, userId)
.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();
userPoints.setLatestAddDaySum(new BigDecimal(sum).add(pointsRule.getPoints()));
@@ -365,6 +573,11 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
}
}
/**
* app我的积分
*
* @return 积分信息
*/
@Override
public UserPointsInfoVO myPoints() {
String userId = GlobalUtils.getLoginUser().getId();
@@ -380,18 +593,36 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
return vo;
}
/**
* app积分任务
*
* @return 列表
*/
@Override
public List<UserPointsTaskVO> pointsTaskList() {
String userId = GlobalUtils.getLoginUser().getId();
return pointsRuleMapper.pointsTaskList(userId);
}
/**
* app积分打卡
*
* @param dto 参数
* @return 结果
*/
@Override
public boolean pointCheckIn(UpdUserPointsDTO dto) {
dto.setSourceType(PointsSourceEnum.TYPE_1_SURVEY.getSource());
return updateUserPoints(dto);
LoginUser loginUser = GlobalUtils.getLoginUser();
return updateUserPoints(dto, loginUser);
}
/**
* app积分明细
*
* @param dto 参数
* @return 结果
*/
@Override
public IPage<UserPointRecordVO> pointRecord(UserPointsPageDTO dto) {
if (ObjectUtil.isNull(dto.getChangeType())) {
@@ -406,6 +637,12 @@ public class UserPointsServiceImpl extends ServiceImpl<UserPointsMapper, UserPoi
return userPointsDetailsMapper.selectJoinPage(page, UserPointRecordVO.class, wrapper);
}
/**
* app积分榜单
*
* @param dto 参数
* @return 结果
*/
@Override
public IPage<UserPointRankingVO> pointRanking(UserPointsRankingDTO dto) {
String orgCode = GlobalUtils.getLoginUser().getOrgCode();
@@ -10,9 +10,21 @@ import lombok.Getter;
@Getter
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"),
;
/**
* 服务名
@@ -1,9 +1,11 @@
package org.jeecg.modules.api.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.api.service.SysBannerApiService;
import org.jeecg.modules.system.entity.SysBanner;
import org.jeecg.modules.system.entity.SysBannerDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -11,6 +13,7 @@ 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.List;
@Slf4j
@@ -22,10 +25,25 @@ public class SysBannerApiController {
private SysBannerApiService sysBannerApiService;
@Operation(summary = "查询部门", description = "查询轮播图列表")
@Operation(summary = "轮播图-app轮播图列表", description = "查询轮播图列表")
@GetMapping("/selectBannerList")
public Result<List<SysBannerDO>> selectBannerList(@RequestParam(value = "showLocation") String showLocation) {
return sysBannerApiService.selectBannerList(showLocation);
}
@Operation(summary = "轮播图-app轮播图列表", description = "轮播图-app轮播图列表")
@GetMapping(value = "/app/list")
public Result<List<SysBanner>> appBannerList(@RequestParam(name = "showLocation") String showLocation) {
Date date = new Date();
List<SysBanner> list = sysBannerApiService.list(new LambdaQueryWrapper<SysBanner>()
.select(SysBanner::getId, SysBanner::getPhotoUrl, SysBanner::getJumpUrl, SysBanner::getTfJump, SysBanner::getMemo)
.eq(SysBanner::getShowLocation, showLocation)
.eq(SysBanner::getStatus, 1)
.and(wrapper -> wrapper.eq(SysBanner::getIsLongTermEffective, 1)
.or(wrapper1 -> wrapper1.eq(SysBanner::getIsLongTermEffective, 0).ge(SysBanner::getStartTime, date).le(SysBanner::getEndTime, date)))
.orderByDesc(SysBanner::getSort)
);
return Result.OK(list);
}
}
@@ -1,10 +1,12 @@
package org.jeecg.modules.api.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.system.entity.SysBanner;
import org.jeecg.modules.system.entity.SysBannerDO;
import java.util.List;
public interface SysBannerApiService {
public interface SysBannerApiService extends IService<SysBanner> {
Result<List<SysBannerDO>> selectBannerList(String showLocation);
}
@@ -1,6 +1,5 @@
package org.jeecg.modules.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
@@ -19,8 +18,6 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* @Description: 轮播图
@@ -157,19 +154,4 @@ public class SysBannerController extends JeecgController<SysBanner, ISysBannerSe
return super.importExcel(request, response, SysBanner.class);
}
@Operation(summary = "轮播图-app轮播图列表", description = "轮播图-app轮播图列表")
@GetMapping(value = "/app/list")
public Result<List<SysBanner>> appBannerList(@RequestParam(name = "showLocation") String showLocation) {
Date date = new Date();
List<SysBanner> list = sysBannerService.list(new LambdaQueryWrapper<SysBanner>()
.select(SysBanner::getId, SysBanner::getPhotoUrl, SysBanner::getJumpUrl, SysBanner::getTfJump, SysBanner::getMemo)
.eq(SysBanner::getShowLocation, showLocation)
.eq(SysBanner::getStatus, 1)
.and(wrapper -> wrapper.eq(SysBanner::getIsLongTermEffective, 1)
.or(wrapper1 -> wrapper1.eq(SysBanner::getIsLongTermEffective, 0).ge(SysBanner::getStartTime, date).le(SysBanner::getEndTime, date)))
.orderByDesc(SysBanner::getSort)
);
return Result.OK(list);
}
}