Kaynağa Gözat

医健宝-医健宝流量扣除

chenguo 1 gün önce
ebeveyn
işleme
fb6c670a44

+ 9 - 5
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -1,10 +1,6 @@
 package com.fs.common.core.redis;
 
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.BoundSetOperations;
@@ -236,4 +232,12 @@ public class RedisCache
     public Boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
         return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
     }
+
+    public Long decr(String key, Long delta) {
+        return redisTemplate.opsForValue().decrement(key, delta);
+    }
+
+    public Long incr(String key, Long delta) {
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/company/constant/CompanyTrafficConstants.java

@@ -0,0 +1,5 @@
+package com.fs.company.constant;
+
+public class CompanyTrafficConstants {
+    public static final String CACHE_KEY = "company:traffic:record:";
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyTrafficRecord.java

@@ -24,6 +24,8 @@ public class CompanyTrafficRecord {
     private Long id;
     @Excel(name = "公司id")
     private Long companyId;
+    @Excel(name = "部门id")
+    private Long deptId;
     @Excel(name="公司名称")
     private String companyName;
     @Excel(name = "流量余额")

+ 1 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyTrafficRecordLog.java

@@ -39,7 +39,7 @@ public class CompanyTrafficRecordLog {
     private Long balance;     // 单位: KB
     @Excel(name = "备注")
     private String remark;
-    @Excel(name = "创建时间")
+    @Excel(name = "创建时间" , width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 }

+ 4 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyTrafficRecordMapper.java

@@ -3,8 +3,12 @@ package com.fs.company.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.CompanyTrafficRecord;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 @Mapper
 public interface CompanyTrafficRecordMapper extends BaseMapper<CompanyTrafficRecord> {
 
+    @Select("SELECT SUM(ctr.balance) FROM company_traffic_record ctr inner join company c on ctr.company_id = c.company_id and c.dept_id = #{deptId}")
+    Long calculateTotalTrafficByDeptId(@Param("deptId") Long deptId);
 }

+ 6 - 1
fs-service/src/main/java/com/fs/company/param/CompanyTrafficRecordChargeParam.java

@@ -1,12 +1,12 @@
 package com.fs.company.param;
 
+import com.fs.company.domain.Company;
 import lombok.Data;
 
 @Data
 public class CompanyTrafficRecordChargeParam {
 
     private Long companyId;
-    private String companyName;
     private Integer operationType; // 1-充值 2-扣费
     //充值金额
     private Long chargeAmount;
@@ -15,4 +15,9 @@ public class CompanyTrafficRecordChargeParam {
     private String remark;
     private Long userId;
     private String userName;
+
+    /**
+     *公司实例
+     * */
+    private Company company;
 }

+ 82 - 24
fs-service/src/main/java/com/fs/company/service/impl/CompanyTrafficRecordServiceImpl.java

@@ -2,9 +2,9 @@ package com.fs.company.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.company.constant.CompanyTrafficConstants;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyTrafficRecord;
 import com.fs.company.domain.CompanyTrafficRecordLog;
@@ -18,9 +18,11 @@ import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.annotation.PostConstruct;
 import java.util.Date;
 import java.util.List;
 
@@ -42,15 +44,48 @@ public class CompanyTrafficRecordServiceImpl extends ServiceImpl<CompanyTrafficR
     @Autowired
     private ICompanyTrafficRecordLogService companyTrafficRecordLogService;
 
+    //启动时初始化流量
+    @PostConstruct
+    public void init() {
+        List<CompanyTrafficRecord> companyTrafficRecords = baseMapper.selectList(new LambdaQueryWrapper<>());
+        for (CompanyTrafficRecord companyTrafficRecord : companyTrafficRecords) {
+            // 获取公司流量
+            Long balance = companyTrafficRecord.getBalance();
+            // 获取部门ID
+            Long deptId = companyTrafficRecord.getDeptId();
+            // 获取公司ID
+            Long companyId = companyTrafficRecord.getCompanyId();
+            // 缓存 key=CACHE_KEY:deptId:companyId  value=剩余流量
+            redisCache.setCacheObject(CompanyTrafficConstants.CACHE_KEY + ":" + deptId + ":" + companyId, balance);
+            // 缓存 key=CACHE_KEY:deptId  value=部门ID
+            calculateTotalTrafficByDeptId(deptId);
+        }
+    }
+
+    @Scheduled(cron = "0 0 3 * * ?")
+    public void refreshTraffic() {
+        List<CompanyTrafficRecord> companyTrafficRecords = baseMapper.selectList(new LambdaQueryWrapper<>());
+        for (CompanyTrafficRecord companyTrafficRecord : companyTrafficRecords) {
+            updateRedisCache(companyTrafficRecord);
+        }
+    }
+
+    //通过redis更新表流量并添加扣除日志
+    private void updateRedisCache(CompanyTrafficRecord companyTrafficRecord){
+        Long balance = redisCache.getCacheObject(CompanyTrafficConstants.CACHE_KEY
+                + ":" + companyTrafficRecord.getDeptId()
+                + ":" + companyTrafficRecord.getCompanyId());
+        if(balance != null)
+            baseMapper.updateById(CompanyTrafficRecord.builder().id(companyTrafficRecord.getId()).balance(balance).build());
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean recharge(CompanyTrafficRecordChargeParam record) {
         CompanyTrafficRecord companyTrafficRecord = new CompanyTrafficRecord();
-        //校验参数companyId userId
+        //校验参数companyId
         if (record.getCompanyId() == null || !validCompany(record))
             throw new IllegalArgumentException("销售公司异常");
-        if (record.getUserId() == null)
-            throw new IllegalArgumentException("操作用户异常");
 
         //查询公司流量记录
         CompanyTrafficRecord companyRecord = baseMapper.selectOne(new LambdaQueryWrapper<CompanyTrafficRecord>()
@@ -72,7 +107,8 @@ public class CompanyTrafficRecordServiceImpl extends ServiceImpl<CompanyTrafficR
                 }else{
                     companyTrafficRecord.setBalance(trafficAmount);
                     companyTrafficRecord.setCompanyId(record.getCompanyId());
-                    companyTrafficRecord.setCompanyName(record.getCompanyName());
+                    companyTrafficRecord.setDeptId(record.getCompany().getDeptId());
+                    companyTrafficRecord.setCompanyName(record.getCompany().getCompanyName());
                     companyTrafficRecord.setCreateTime(new Date());
                     companyTrafficRecord.setCreateBy(record.getUserId());
                     baseMapper.insert(companyTrafficRecord);
@@ -92,15 +128,49 @@ public class CompanyTrafficRecordServiceImpl extends ServiceImpl<CompanyTrafficR
             default:
                 throw new IllegalArgumentException("非法的操作类型");
         }
-        //记录日志
+        // 更新Redis缓存
+        updateRedisCache(record, companyTrafficRecord);
+
         return doLog(companyTrafficRecord,record);
     }
 
-    private void updateTraffic(Long id,CompanyTrafficRecord record){
-        //将查询出的数据的is_del设置为1
-        this.update(new LambdaUpdateWrapper<CompanyTrafficRecord>()
-                .eq(CompanyTrafficRecord::getId, id));
-        //更新redis缓存
+    private boolean validCompany(CompanyTrafficRecordChargeParam  record){
+        Company company = new Company();
+        company.setCompanyId(record.getCompanyId());
+        company.setIsDel(0);
+        List<Company> companies = companyService.selectCompanyList(company);
+        if(companies != null && companies.size() == 1){
+            record.setCompany(companies.get(0));
+            return true;
+        }else{
+            return false;
+        }
+    }
+    /**
+     * 更新Redis缓存
+     * @param record 充值参数
+     * @param companyTrafficRecord 公司流量记录
+     */
+    private void updateRedisCache(CompanyTrafficRecordChargeParam record, CompanyTrafficRecord companyTrafficRecord) {
+        if (record.getCompany() != null && record.getCompany().getDeptId() != null) {
+            // 更新缓存 key=CACHE_KEY:deptId:companyId  value=剩余流量
+            redisCache.setCacheObject(CompanyTrafficConstants.CACHE_KEY + ":" + record.getCompany().getDeptId() + ":" + record.getCompanyId(),
+                    companyTrafficRecord.getBalance());
+
+            // 计算该部门下所有公司流量总和
+            calculateTotalTrafficByDeptId(record.getCompany().getDeptId());
+        }else {
+            // 抛出异常
+            throw new IllegalArgumentException("缓存ID异常");
+        }
+    }
+
+    /**
+     * 部门下所有公司流量总和
+     * */
+    private void calculateTotalTrafficByDeptId(Long deptId) {
+        Long totalTraffic = baseMapper.calculateTotalTrafficByDeptId(deptId);
+        redisCache.setCacheObject(CompanyTrafficConstants.CACHE_KEY + ":" + deptId, totalTraffic);
     }
 
     private boolean doLog(CompanyTrafficRecord companyTrafficRecord,CompanyTrafficRecordChargeParam record){
@@ -112,7 +182,6 @@ public class CompanyTrafficRecordServiceImpl extends ServiceImpl<CompanyTrafficR
                         .createTime(new Date())
                         .operationType(record.getOperationType())
                         .userId(record.getUserId())
-                        .userName(record.getUserName())
                         .remark(record.getRemark())
                         .userName(record.getUserName())
                         .build());
@@ -131,18 +200,7 @@ public class CompanyTrafficRecordServiceImpl extends ServiceImpl<CompanyTrafficR
         return baseMapper.selectById(id);
     }
 
-    private boolean validCompany(CompanyTrafficRecordChargeParam  record){
-        Company company = new Company();
-        company.setCompanyId(record.getCompanyId());
-        company.setIsDel(0);
-        List<Company> companies = companyService.selectCompanyList(company);
-        if(companies != null && companies.size() == 1){
-            record.setCompanyName(companies.get(0).getCompanyName());
-            return true;
-        }else{
-            return false;
-        }
-    }
+
     /** 充值金额转换流量KB*/
     @Override
     public Long trafficConversion(Long amount) {

+ 59 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -16,6 +16,7 @@ import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.company.constant.CompanyTrafficConstants;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.domain.CompanyUser;
@@ -46,6 +47,7 @@ import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserWxService;
+import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwExternalContact;
@@ -73,6 +75,7 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -214,6 +217,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private FsCourseAnswerLogsMapper courseAnswerLogsMapper;
 
+    @Autowired
+    ConfigUtil configUtil;
+
 
     /**
      * 查询课堂视频
@@ -779,6 +785,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                 logger.error("zyp \n【缓冲值空】参数: {}",param);
                 return R.error("缓冲值空");
             }
+
             FsCourseTrafficLog trafficLog = new FsCourseTrafficLog();
             trafficLog.setQwExternalContactId(param.getQwExternalId());
             trafficLog.setCreateTime(new Date());
@@ -788,6 +795,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             if (video == null) {
                 return R.error("视频不存在");
             }
+            Company company = companyMapper.selectCompanyById(param.getCompanyId());
+            if (company==null){
+                return R.error("公司不存在");
+            }
 
             // 计算流量
             BigDecimal result = param.getBufferRate().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
@@ -801,6 +812,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 //                logger.error("zyp \n【插入或更新流量】:{}",trafficLog);
                 fsCourseTrafficLogMapper.insertOrUpdateTrafficLog(trafficLog);
             }
+            //扣除流量
+            asyncDeductTraffic(company, roundedResult);
         } catch (Exception e) {
             e.printStackTrace();
             // 打印参数param和异常信息
@@ -810,6 +823,52 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return R.ok();
     }
 
+    @Async
+    public void asyncDeductTraffic(Company company, Long traffic) {
+        try {
+            // 扣除流量
+            Long remainingTraffic = updateRedisCache(company, traffic);
+
+            if ("1".equals(configUtil.generateConfigByKey("watch.course.config").getString("doNotPlay")) && remainingTraffic <= 0) {
+                logger.warn("公司ID: {} 流量不足,当前剩余: {}", company.getCompanyId(), remainingTraffic);
+                throw new Exception("流量不足");
+            }
+
+            logger.info("异步扣除流量成功 - 公司ID: {}, 扣除流量: {}, 剩余流量: {}",
+                    company.getCompanyId(), traffic, remainingTraffic);
+        } catch (Exception e) {
+            logger.error("异步扣除流量失败 - 公司ID: {}, 扣除流量: {}, 错误信息: {}",
+                    company.getCompanyId(), traffic, e.getMessage(), e);
+        }
+    }
+
+    //
+    private Long updateRedisCache(Company company, Long traffic) throws Exception {
+        // 分布式扣除流量
+        String companyKey = CompanyTrafficConstants.CACHE_KEY + ":" + company.getDeptId() + ":" + company.getCompanyId();
+        String deptKey = CompanyTrafficConstants.CACHE_KEY + ":" + company.getCompanyId();
+        String lockKey = companyKey + ":lock";
+        //销售公司剩余流量
+        Long companyTraffic = redisCache.getCacheObject(companyKey);
+        if(companyTraffic == null || companyTraffic<0){
+            return companyTraffic == null?0:companyTraffic;
+        }
+        try {
+            if (redisCache.setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS)) {
+                try {
+                   companyTraffic = redisCache.decr(companyKey, traffic);
+                   redisCache.decr(deptKey, traffic);
+                } finally {
+                    redisCache.deleteObject(lockKey); // 释放锁
+                }
+            }
+        } catch (Exception e) {
+            logger.error("【更新Redis缓存失败】企业ID: {}, 流量: {}, 错误信息:{}", company.getCompanyId(), traffic, e.getMessage(), e);
+            throw e;
+        }
+        return companyTraffic;
+    }
+
 
     @Override
     public R getIntegralByH5Video(FsUserCourseVideoFinishUParam param) {

+ 0 - 2
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -19,8 +19,6 @@ import com.fs.course.vo.FsUserCourseVideoH5VO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.enums.FsUserOperationEnum;
-import com.fs.his.service.IFsUserService;
-import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;