BUG修复:支付、时长、套餐

This commit is contained in:
墨大叔 2024-07-18 17:40:30 +08:00
parent bd428814a3
commit d8b9992b23
40 changed files with 1073 additions and 135 deletions

View File

@ -41,4 +41,8 @@ public class CacheConstants
* 登录账户密码错误次数 redis key
*/
public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
public static final String ACCESS_KEY_PREFIX = "access:";
public static final String USER_PREFIX = "user:";
}

View File

@ -193,4 +193,13 @@ public class Constants
public static final TimeUnit BILL_UNPAID_TIMEUNIT = TimeUnit.MINUTES;
/**
* accessKey
*/
public static final String HEADER_ACCESS_KEY = "accessKey";
/**
* accessSecret
*/
public static final String HEADER_ACCESS_SECRET = "accessSecret";
}

View File

@ -33,11 +33,11 @@ public class RedisLock {
private Integer retry;
public boolean unlock(RedisLockKey redisLockKey, String key) {
public boolean unlock(RedisLockKey redisLockKey, Object key) {
return unlock(redisLockKey.getKey() + ":" + key);
}
public boolean lock(RedisLockKey redisLockKey, String key) {
public boolean lock(RedisLockKey redisLockKey, Object key) {
return lock(redisLockKey.getKey() + ":" + key);
}

View File

@ -15,7 +15,9 @@ public enum RedisLockKey {
ADD_WITHDRAW("addWithdraw", "提现申请"),
APPROVAL_WITHDRAW("approvalWithdraw", "提现审核"),
PAY_WITHDRAW("payWithdraw", "提现打款"),
PAY_NOTIFY("payNotify", "支付通知");
PAY_NOTIFY("payNotify", "支付通知"),
APPLY_ACCESS("apply_access", "申请秘钥"),
RECHARGE_DEVICE("recharge_device", "设备充值");
private final String key;

View File

@ -1,5 +1,7 @@
package com.ruoyi.common.utils;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@ -197,4 +199,14 @@ public class SecurityUtils
}
}
/**
* 安全的生成秘钥
* @param length 秘钥长度
*/
public static String genSecret(int length) {
SecureRandom secureRandom = new SecureRandom();
byte[] accessSecretBytes = new byte[length];
secureRandom.nextBytes(accessSecretBytes);
return Base64.getEncoder().encodeToString(accessSecretBytes);
}
}

View File

@ -0,0 +1,19 @@
package com.ruoyi.framework.security.domain;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.ss.access.domain.AccessVO;
import lombok.Data;
/**
* @author wjh
* 2024/7/18
*/
@Data
public class AccessLoginUser {
// 秘钥
private AccessVO access;
// 登录用户信息
private LoginUser loginUser;
}

View File

@ -1,10 +1,20 @@
package com.ruoyi.framework.security.filter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.framework.security.domain.AccessLoginUser;
import com.ruoyi.ss.access.domain.AccessVO;
import com.ruoyi.ss.access.service.AccessService;
import com.ruoyi.ss.user.domain.SmUserVo;
import com.ruoyi.ss.user.service.ISmUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@ -27,18 +37,79 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
@Autowired
private TokenService tokenService;
@Autowired
private AccessService accessService;
@Autowired
private ISmUserService userService;
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 判断上下文是否已经存在用户信息
if (StringUtils.isNull(SecurityUtils.getAuthentication())) {
// 优先尝试使用token来获取用户信息
LoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null) {
tokenService.verifyToken(loginUser);
}
// 若token未获取到信息则使用accessKey来获取用户信息
else {
loginUser = this.getLoginUserByAccessKey(request);
}
// 最后判断用户是否为空并将其写入到安全上下文
if (loginUser != null) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
chain.doFilter(request, response);
}
private LoginUser getLoginUserByAccessKey(HttpServletRequest request) {
String accessKey = request.getHeader(Constants.HEADER_ACCESS_KEY);
String accessSecret = request.getHeader(Constants.HEADER_ACCESS_SECRET);
if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(accessSecret)) {
return null;
}
// 从缓存中获取数据
String redisKey = CacheConstants.ACCESS_KEY_PREFIX + accessKey;
AccessLoginUser accessLoginUser = redisCache.getCacheObject(redisKey);
// 缓存未命中查询获取数据
if (accessLoginUser == null) {
accessLoginUser = new AccessLoginUser();
// 获取access信息
AccessVO access = accessService.selectByAccessKey(accessKey);
if (access == null) {
return null;
}
accessLoginUser.setAccess(access);
// 获取用户信息
SmUserVo user = userService.selectSmUserByUserId(accessLoginUser.getAccess().getUserId());
if (user == null) {
return null;
}
LoginUser loginUser = new LoginUser(user.getUserId(), user);
accessLoginUser.setLoginUser(loginUser);
// 放入缓存中
redisCache.setCacheObject(redisKey, accessLoginUser, 30, TimeUnit.MINUTES);
}
// 校验秘钥
if (!SecurityUtils.matchesPassword(accessSecret, accessLoginUser.getAccess().getAccessSecret())) {
return null;
}
return accessLoginUser.getLoginUser();
}
}

View File

@ -0,0 +1,37 @@
package com.ruoyi.ss.access.domain;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.enums.DesensitizedType;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* 第三方API秘钥对对象 ss_access
*
* @author ruoyi
* @date 2024-07-17
*/
@Data
public class Access extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 秘钥对ID */
private Long accessId;
/** 申请用户ID */
@Excel(name = "申请用户ID")
private Long userId;
/** 秘钥键 */
@Excel(name = "秘钥键")
private String accessKey;
/** 秘钥(加密) */
@Excel(name = "秘钥")
@Sensitive(desensitizedType = DesensitizedType.PASSWORD)
private String accessSecret;
}

View File

@ -0,0 +1,17 @@
package com.ruoyi.ss.access.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author wjh
* 2024/7/17
*/
@Data
public class AccessQuery extends Access{
@ApiModelProperty("密钥对id列表")
private List<Long> accessIds;
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.ss.access.domain;
import lombok.Data;
/**
* @author wjh
* 2024/7/17
*/
@Data
public class AccessVO extends Access{
}

View File

@ -0,0 +1,20 @@
package com.ruoyi.ss.access.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
/**
* @author wjh
* 2024/7/17
*/
@Data
public class AccessLoginDTO {
@ApiModelProperty("秘钥键")
private String accessKey;
@ApiModelProperty("秘钥")
private String accessSecret;
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.ss.access.domain.vo;
import com.ruoyi.ss.access.domain.Access;
import com.ruoyi.ss.access.domain.AccessVO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 申请的密钥对返回值
* @author wjh
* 2024/7/17
*/
@Data
public class AccessApplyVO {
@ApiModelProperty("秘钥键")
private String accessKey;
@ApiModelProperty("秘钥")
private String accessSecret;
public AccessApplyVO(Access access) {
this.accessKey = access.getAccessKey();
this.accessSecret = access.getAccessSecret();
}
}

View File

@ -0,0 +1,74 @@
package com.ruoyi.ss.access.mapper;
import java.util.List;
import com.ruoyi.ss.access.domain.Access;
import com.ruoyi.ss.access.domain.AccessQuery;
import com.ruoyi.ss.access.domain.AccessVO;
import org.apache.ibatis.annotations.Param;
/**
* 第三方API秘钥对Mapper接口
*
* @author ruoyi
* @date 2024-07-17
*/
public interface AccessMapper
{
/**
* 查询第三方API秘钥对
*
* @param accessId 第三方API秘钥对主键
* @return 第三方API秘钥对
*/
public AccessVO selectAccessByAccessId(Long accessId);
/**
* 查询第三方API秘钥对列表
*
* @param access 第三方API秘钥对
* @return 第三方API秘钥对集合
*/
public List<AccessVO> selectAccessList(AccessQuery access);
/**
* 新增第三方API秘钥对
*
* @param access 第三方API秘钥对
* @return 结果
*/
public int insertAccess(Access access);
/**
* 修改第三方API秘钥对
*
* @param access 第三方API秘钥对
* @return 结果
*/
public int updateAccess(Access access);
/**
* 删除第三方API秘钥对
*
* @param accessId 第三方API秘钥对主键
* @return 结果
*/
public int deleteAccessByAccessId(Long accessId);
/**
* 批量删除第三方API秘钥对
*
* @param accessIds 需要删除的数据主键集合
* @return 结果
*/
public int deleteAccessByAccessIds(@Param("ids") List<Long> accessIds);
/**
* 根据用户ID查询数量
*/
int selectCountByUserId(Long userId);
/**
* 根据accessKey查询密钥对
*/
AccessVO selectByAccessKey(String accessKey);
}

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ss.access.mapper.AccessMapper">
<resultMap type="AccessVO" id="AccessResult" autoMapping="true"/>
<sql id="selectAccessVo">
select
sa.access_id,
sa.user_id,
sa.access_key,
sa.access_secret,
sa.create_time
from ss_access sa
</sql>
<select id="selectAccessList" parameterType="AccessQuery" resultMap="AccessResult">
<include refid="selectAccessVo"/>
<where>
<if test="accessId != null "> and sa.access_id = #{accessId}</if>
<if test="userId != null "> and sa.user_id = #{userId}</if>
<if test="accessKey != null and accessKey != ''"> and sa.access_key = #{accessKey}</if>
<if test="accessSecret != null and accessSecret != ''"> and sa.access_secret = #{accessSecret}</if>
<if test="accessIds != null and accessIds.size() > 0">
and sa.access_id in
<foreach collection="accessIds" item="accessId" open="(" separator="," close=")">
#{accessId}
</foreach>
</if>
</where>
</select>
<select id="selectAccessByAccessId" parameterType="Long" resultMap="AccessResult">
<include refid="selectAccessVo"/>
where sa.access_id = #{accessId}
</select>
<select id="selectCountByUserId" resultType="java.lang.Integer">
select count(sa.access_id)
from ss_access sa
where sa.user_id = #{userId}
</select>
<select id="selectByAccessKey" resultType="com.ruoyi.ss.access.domain.AccessVO">
<include refid="selectAccessVo"/>
where sa.access_key = #{accessKey}
</select>
<insert id="insertAccess" parameterType="Access" useGeneratedKeys="true" keyProperty="accessId">
insert into ss_access
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="accessKey != null and accessKey != ''">access_key,</if>
<if test="accessSecret != null and accessSecret != ''">access_secret,</if>
<if test="createTime != null">create_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="accessKey != null and accessKey != ''">#{accessKey},</if>
<if test="accessSecret != null and accessSecret != ''">#{accessSecret},</if>
<if test="createTime != null">#{createTime},</if>
</trim>
</insert>
<update id="updateAccess" parameterType="Access">
update ss_access
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="accessKey != null and accessKey != ''">access_key = #{accessKey},</if>
<if test="accessSecret != null and accessSecret != ''">access_secret = #{accessSecret},</if>
<if test="createTime != null">create_time = #{createTime},</if>
</trim>
where access_id = #{accessId}
</update>
<delete id="deleteAccessByAccessId" parameterType="Long">
delete from ss_access where access_id = #{accessId}
</delete>
<delete id="deleteAccessByAccessIds" parameterType="String">
delete from ss_access where access_id in
<foreach item="accessId" collection="ids" open="(" separator="," close=")">
#{accessId}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,92 @@
package com.ruoyi.ss.access.service;
import java.util.List;
import com.ruoyi.ss.access.domain.Access;
import com.ruoyi.ss.access.domain.AccessQuery;
import com.ruoyi.ss.access.domain.AccessVO;
import com.ruoyi.ss.access.domain.vo.AccessApplyVO;
import com.ruoyi.ss.user.domain.SmUserVo;
/**
* 第三方API秘钥对Service接口
*
* @author ruoyi
* @date 2024-07-17
*/
public interface AccessService
{
/**
* 查询第三方API秘钥对
*
* @param accessId 第三方API秘钥对主键
* @return 第三方API秘钥对
*/
public AccessVO selectAccessByAccessId(Long accessId);
/**
* 查询第三方API秘钥对列表
*
* @param access 第三方API秘钥对
* @return 第三方API秘钥对集合
*/
public List<AccessVO> selectAccessList(AccessQuery access);
/**
* 新增第三方API秘钥对
*
* @param access 第三方API秘钥对
* @return 结果
*/
public int insertAccess(Access access);
/**
* 修改第三方API秘钥对
*
* @param access 第三方API秘钥对
* @return 结果
*/
public int updateAccess(Access access);
/**
* 批量删除第三方API秘钥对
*
* @param accessIds 需要删除的第三方API秘钥对主键集合
* @return 结果
*/
public int deleteAccessByAccessIds(List<Long> accessIds);
/**
* 删除第三方API秘钥对信息
*
* @param accessId 第三方API秘钥对主键
* @return 结果
*/
public int deleteAccessByAccessId(Long accessId);
/**
* 用户申请新的秘钥对
*
* @param userId 用户ID
*/
AccessApplyVO apply(Long userId);
/**
* 查询用户秘钥对数量
*/
int selectCountByUserId(Long userId);
SmUserVo selectUserByAccess(String accessKey);
/**
* 根据accessKey查询秘钥对
*/
AccessVO selectByAccessKey(String accessKey);
/**
* 删除缓存
*/
boolean deleteCache(String accessKey);
AccessApplyVO reset(Long accessId);
}

View File

@ -0,0 +1,214 @@
package com.ruoyi.ss.access.service.impl;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.ss.access.domain.AccessQuery;
import com.ruoyi.ss.access.domain.AccessVO;
import com.ruoyi.ss.access.domain.vo.AccessApplyVO;
import com.ruoyi.ss.user.domain.SmUserVo;
import com.ruoyi.ss.user.service.ISmUserService;
import org.eclipse.jetty.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.ruoyi.ss.access.mapper.AccessMapper;
import com.ruoyi.ss.access.domain.Access;
import com.ruoyi.ss.access.service.AccessService;
/**
* 第三方API秘钥对Service业务层处理
*
* @author ruoyi
* @date 2024-07-17
*/
@Service
public class AccessServiceImpl implements AccessService
{
@Autowired
private AccessMapper accessMapper;
@Value("${access.maxCount}")
private Integer maxCount;
@Autowired
private RedisLock redisLock;
@Autowired
private ISmUserService userService;
@Autowired
private RedisCache redisCache;
/**
* 查询第三方API秘钥对
*
* @param accessId 第三方API秘钥对主键
* @return 第三方API秘钥对
*/
@Override
public AccessVO selectAccessByAccessId(Long accessId)
{
return accessMapper.selectAccessByAccessId(accessId);
}
/**
* 查询第三方API秘钥对列表
*
* @param access 第三方API秘钥对
* @return 第三方API秘钥对
*/
@Override
public List<AccessVO> selectAccessList(AccessQuery access)
{
return accessMapper.selectAccessList(access);
}
/**
* 新增第三方API秘钥对
*
* @param access 第三方API秘钥对
* @return 结果
*/
@Override
public int insertAccess(Access access)
{
access.setCreateTime(DateUtils.getNowDate());
return accessMapper.insertAccess(access);
}
/**
* 修改第三方API秘钥对
*
* @param data 第三方API秘钥对
* @return 结果
*/
@Override
public int updateAccess(Access data)
{
int update = accessMapper.updateAccess(data);
if (update == 1) {
// 刷新缓存
AccessVO access = selectAccessByAccessId(data.getAccessId());
this.deleteCache(access.getAccessKey());
}
return update;
}
/**
* 批量删除第三方API秘钥对
*
* @param accessIds 需要删除的第三方API秘钥对主键
* @return 结果
*/
@Override
public int deleteAccessByAccessIds(List<Long> accessIds)
{
if (CollectionUtils.isEmptyElement(accessIds)) {
return 0;
}
AccessQuery query = new AccessQuery();
query.setAccessIds(accessIds);
List<AccessVO> accessList = selectAccessList(query);
int delete = accessMapper.deleteAccessByAccessIds(accessIds);
// 清除缓存
for (AccessVO access : accessList) {
this.deleteCache(access.getAccessKey());
}
return delete;
}
/**
* 删除第三方API秘钥对信息
*
* @param accessId 第三方API秘钥对主键
* @return 结果
*/
@Override
public int deleteAccessByAccessId(Long accessId)
{
return accessMapper.deleteAccessByAccessId(accessId);
}
@Override
public AccessApplyVO apply(Long userId) {
if (userId == null) {
return null;
}
ServiceUtil.assertion(!redisLock.lock(RedisLockKey.APPLY_ACCESS, userId), "请勿重复提交申请");
try {
int count = this.selectCountByUserId(userId);
ServiceUtil.assertion(count >= maxCount, "用户密钥对超过最大数量,最大允许数量:" + maxCount);
// 生成原始密钥对
Access access = this.genAccess(userId);
AccessApplyVO result = new AccessApplyVO(access);
// 加密秘钥
access.setAccessSecret(SecurityUtils.encryptPassword(access.getAccessSecret()));
// 插入加密后的秘钥
int insert = this.insertAccess(access);
ServiceUtil.assertion(insert != 1, "生成密钥对失败");
// 返回未加密的密钥对
return result;
} finally {
redisLock.unlock(RedisLockKey.APPLY_ACCESS, userId);
}
}
// 生成一个密钥对
private Access genAccess(Long userId) {
if (userId == null) {
return null;
}
Access access = new Access();
access.setUserId(userId);
access.setAccessKey(StringUtil.valueOf(SnowFlakeUtil.newId()));
access.setAccessSecret(SecurityUtils.genSecret(64));
return access;
}
@Override
public int selectCountByUserId(Long userId) {
if (userId == null) {
return 0;
}
return accessMapper.selectCountByUserId(userId);
}
@Override
public SmUserVo selectUserByAccess(String accessKey) {
AccessVO access = this.selectByAccessKey(accessKey);
if (access == null) {
return null;
}
return userService.selectSmUserByUserId(access.getUserId());
}
@Override
public AccessVO selectByAccessKey(String accessKey) {
return accessMapper.selectByAccessKey(accessKey);
}
@Override
public boolean deleteCache(String accessKey) {
if (StringUtils.isBlank(accessKey)) {
return false;
}
return redisCache.deleteObject(CacheConstants.ACCESS_KEY_PREFIX + accessKey);
}
@Override
public AccessApplyVO reset(Long accessId) {
return null;
}
}

View File

@ -229,11 +229,10 @@ public class DeviceServiceImpl implements DeviceService
}
Map<Long, SmDeviceVO> deviceMap = selectMap(dto);
deviceAssembler.assembleTenant(new ArrayList<>(deviceMap.values()));
for (Long deviceId : deviceIds) {
SmDeviceVO device = deviceMap.get(deviceId);
ServiceUtil.assertion(device == null, String.format("设备“%s”不存在请刷新后重试", deviceId));
ServiceUtil.assertion(!CollectionUtils.isEmpty(device.getTenantIds()), String.format("设备“%s”还有租户,请解绑后再进行删除", device.getDeviceName()));
ServiceUtil.assertion(DeviceStatus.USING.getStatus().equals(device.getStatus()), String.format("设备“%s”正在使用中,无法删除", deviceId));
}
}
@ -264,18 +263,19 @@ public class DeviceServiceImpl implements DeviceService
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean addTimeByUser(Long deviceId, long seconds, boolean withIot, String reason) {
boolean b = this.addTime(deviceId, seconds, withIot);
boolean addTime = this.addTime(deviceId, seconds, withIot);
// 记录下操作日志
LoginUser loginUser = SecurityUtils.getLoginUser();
scheduledExecutorService.schedule(() -> {
SmDeviceVO device = smDeviceMapper.selectSmDeviceByDeviceId(deviceId);
recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, seconds, reason, loginUser));
}, 0, TimeUnit.SECONDS);
if (addTime) {
// 记录下操作日志
LoginUser loginUser = SecurityUtils.getLoginUser();
scheduledExecutorService.schedule(() -> {
SmDeviceVO device = smDeviceMapper.selectSmDeviceByDeviceId(deviceId);
recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, seconds, reason, loginUser));
}, 0, TimeUnit.SECONDS);
}
return b;
return addTime;
}
@Override
@ -328,7 +328,6 @@ public class DeviceServiceImpl implements DeviceService
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean addTime(Long deviceId, long seconds, boolean withIot) {
ServiceUtil.assertion( seconds < 0, "增加的时长不允许小于0");
@ -337,22 +336,26 @@ public class DeviceServiceImpl implements DeviceService
ServiceUtil.assertion(!StringUtils.hasText(device.getMac()), "设备MAC号为空");
ServiceUtil.assertion(DeviceStatus.FIXING.getStatus().equals(device.getStatus()), "设备正在维修中,无法使用");
// 更新数据库时长
int updateCount = smDeviceMapper.addTime(deviceId, seconds);
ServiceUtil.assertion(updateCount != 1, "增加时长失败,请刷新后重试");
transactionTemplate.execute(status -> {
// 更新数据库时长
int updateCount = smDeviceMapper.addTime(deviceId, seconds);
ServiceUtil.assertion(updateCount != 1, "增加时长失败,请刷新后重试");
// 修改状态为使用中
changeStatus(deviceId, DeviceStatus.USING);
// 修改状态为使用中
changeStatus(deviceId, DeviceStatus.USING);
// 物联网设备增加时长
if (withIot) {
SmDeviceVO newDevice = selectSmDeviceByDeviceId(deviceId);
long betweenSeconds = Duration.between(LocalDateTime.now(), newDevice.getExpireTime()).getSeconds();
if (betweenSeconds > 0) {
CommandResponse rechargeResult = iotService.setTime(device.getMac(), betweenSeconds);
ServiceUtil.assertion(!rechargeResult.isSuccess(), "设备充值失败,请检查设备是否在线或联系管理员");
// 物联网设备增加时长
if (withIot) {
SmDeviceVO newDevice = selectSmDeviceByDeviceId(deviceId);
long betweenSeconds = Duration.between(LocalDateTime.now(), newDevice.getExpireTime()).getSeconds();
if (betweenSeconds > 0) {
CommandResponse rechargeResult = iotService.setTime(device.getMac(), betweenSeconds);
ServiceUtil.assertion(!rechargeResult.isSuccess(), "设备充值失败,请检查设备是否在线或联系管理员");
}
}
}
return Boolean.TRUE;
});
// 拉取设备信息
this.pullDeviceInfoAsync(Collections.singletonList(deviceId), 3, TimeUnit.SECONDS);
@ -360,7 +363,7 @@ public class DeviceServiceImpl implements DeviceService
// 时长结束后修改设备状态
scheduledExecutorService.schedule(()-> {
freshStatus(deviceId);
}, seconds, TimeUnit.MINUTES);
}, seconds, TimeUnit.SECONDS);
return true;
}
@ -634,12 +637,15 @@ public class DeviceServiceImpl implements DeviceService
CommandResponse commandResponse = iotService.setTime(device.getMac(), 0L);
ServiceUtil.assertion(!commandResponse.isSuccess(), "设备归零失败,请检查设备是否在线或联系管理员");
// 强制关闭
iotService.close(device.getMac());
// 归零记录
LoginUser loginUser = SecurityUtils.getLoginUser();
scheduledExecutorService.schedule(() -> {
// 设备剩余时长
Duration duration = Duration.between(now, device.getExpireTime());
recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, -duration.toMinutes(), "设备归零", loginUser));
recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, -duration.getSeconds(), "设备归零", loginUser));
}, 0, TimeUnit.SECONDS);
return true;

View File

@ -2,7 +2,9 @@ package com.ruoyi.ss.record.time.service;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.ss.device.domain.SmDevice;
import com.ruoyi.ss.device.domain.vo.SmDeviceVO;
import com.ruoyi.ss.record.time.domain.RecordTime;
import com.ruoyi.ss.user.domain.SmUserVo;
/**
* @author wjh
@ -12,4 +14,5 @@ public interface RecordTimeConverter {
RecordTime toRecordTime(SmDevice device, long seconds, String reason, LoginUser loginUser);
RecordTime toRecordTime(SmDeviceVO device, long secondSuitTime, String reason, SmUserVo user);
}

View File

@ -3,9 +3,11 @@ package com.ruoyi.ss.record.time.service.impl;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.LoginType;
import com.ruoyi.ss.device.domain.SmDevice;
import com.ruoyi.ss.device.domain.vo.SmDeviceVO;
import com.ruoyi.ss.record.time.domain.RecordTime;
import com.ruoyi.ss.record.time.domain.enums.OperatorType;
import com.ruoyi.ss.record.time.service.RecordTimeConverter;
import com.ruoyi.ss.user.domain.SmUserVo;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@ -22,14 +24,31 @@ public class RecordTimeConverterImpl implements RecordTimeConverter {
record.setDeviceId(device == null ? null : device.getDeviceId());
record.setAmount(seconds);
record.setReason(reason);
record.setOperatorId(loginUser.getUserId());
record.setOperatorName(loginUser.getUsername());
record.setOperatorType(toOperatorType(loginUser.getLoginType()));
if (loginUser != null) {
record.setOperatorId(loginUser.getUserId());
record.setOperatorName(loginUser.getUsername());
record.setOperatorType(toOperatorType(loginUser.getLoginType()));
}
record.setOperatorTime(LocalDateTime.now());
return record;
}
@Override
public RecordTime toRecordTime(SmDeviceVO device, long seconds, String reason, SmUserVo user) {
RecordTime record = new RecordTime();
record.setDeviceId(device == null ? null : device.getDeviceId());
record.setAmount(seconds);
record.setReason(reason);
if (user != null) {
record.setOperatorId(user.getUserId());
record.setOperatorName(user.getUserName());
record.setOperatorType(OperatorType.USER.getType());
}
record.setOperatorTime(LocalDateTime.now());
return record;
}
private String toOperatorType(LoginType loginType) {
return OperatorType.parse(loginType).getType();
}

View File

@ -77,4 +77,7 @@ public class Suit extends BaseEntity
@NotBlank(message = "套餐时长单位不允许为空", groups = {ValidGroup.Create.class, ValidGroup.FrontCreate.class})
@DictValid(type = DictTypeConstants.SUIT_TIME_UNIT, message = "非法的套餐时长单位")
private String timeUnit;
@ApiModelProperty("用户ID")
private Long userId;
}

View File

@ -21,6 +21,7 @@ public class SuitBO extends Suit {
bo.setPrice(getPrice());
bo.setDescription(getDescription());
bo.setTimeUnit(getTimeUnit());
bo.setUserId(getUserId());
return bo;
}
@ -31,7 +32,6 @@ public class SuitBO extends Suit {
public SuitBO filterUpdateByApp() {
SuitBO bo = new SuitBO();
bo.setSuitId(getSuitId());
bo.setDeviceId(getDeviceId());
bo.setName(getName());
bo.setValue(getValue());
bo.setPrice(getPrice());

View File

@ -21,6 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ss.deleted,
ss.source_id,
ss.time_unit,
ss.user_id,
sd.device_name as device_name,
su.user_name as user_name,
store.name as store_name
@ -41,7 +42,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deleted == null"> and ss.deleted = false</if>
<if test="deleted != null"> and ss.deleted = #{deleted}</if>
<if test="deviceName != null"> and sd.device_name like concat('%', #{deviceName}, '%')</if>
<if test="mchId != null"> and sd.user_id = #{mchId}</if>
<if test="mchId != null"> and ss.user_id = #{mchId}</if>
<if test="userId != null"> and ss.user_id = #{userId}</if>
<if test="needSource != null">
<if test="needSource">
and ss.source_id is not null
@ -104,6 +106,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deleted != null">deleted,</if>
<if test="sourceId != null">source_id,</if>
<if test="timeUnit != null">time_unit,</if>
<if test="userId != null">user_id,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="deviceId != null">#{deviceId},</if>
@ -118,6 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deleted != null">#{deleted},</if>
<if test="sourceId != null">#{sourceId},</if>
<if test="timeUnit != null">#{timeUnit},</if>
<if test="userId != null">#{userId},</if>
</trim>
</insert>
@ -133,11 +137,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update_time,
update_by,
deleted,
source_id
source_id,
time_unit,
user_id
)
values
<foreach collection="list" item="i" separator=",">
<trim prefix="(" suffix=")">
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="i.deviceId != null">#{i.deviceId},</if>
<if test="i.deviceId == null">default,</if>
<if test="i.name != null and i.name != ''">#{i.name},</if>
@ -158,10 +164,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="i.updateBy == null">default,</if>
<if test="i.deleted != null">#{i.deleted},</if>
<if test="i.deleted == null">default,</if>
<if test="i.sourceId != null">#{i.sourceId}</if>
<if test="i.sourceId != null">#{i.sourceId},</if>
<if test="i.sourceId == null">default,</if>
<if test="i.timeUnit != null">#{i.timeUnit}</if>
<if test="i.timeUnit != null">#{i.timeUnit},</if>
<if test="i.timeUnit == null">default,</if>
<if test="i.userId != null">#{i.userId},</if>
<if test="i.userId == null">default,</if>
</trim>
</foreach>
@ -185,6 +193,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="sourceId != null">source_id = #{sourceId},</if>
<if test="timeUnit != null">time_unit = #{timeUnit},</if>
<if test="userId != null">user_id = #{userId},</if>
</sql>
<update id="logicDel">

View File

@ -50,6 +50,8 @@ public class SuitConverterImpl implements SuitConverter {
data.setPrice(suit.getPrice());
data.setDescription(suit.getDescription());
data.setSourceId(suit.getSuitId());
data.setTimeUnit(suit.getTimeUnit());
data.setUserId(suit.getUserId());
list.add(data);
}

View File

@ -17,6 +17,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -184,7 +185,9 @@ public class SuitServiceImpl implements SuitService
if (CollectionUtils.isEmptyElement(list)) {
return Collections.emptyMap();
}
return list.stream().collect(Collectors.groupingBy(keyMapper));
return list.stream()
.filter(item -> Objects.nonNull(keyMapper.apply(item)))
.collect(Collectors.groupingBy(keyMapper));
}
/**

View File

@ -136,15 +136,19 @@ public class TransactionBill extends BaseEntity
@ApiModelProperty("套餐时长单位")
@NotBlank(message = "套餐时长单位不允许为空", groups = {ValidGroup.Recharge.class})
@DictValid(type = DictTypeConstants.SUIT_TIME_UNIT, message = "非法的套餐时长单位", groups = {ValidGroup.Recharge.class})
@JsonView(JsonViewProfile.AppUser.class)
private String suitTimeUnit;
@ApiModelProperty("套餐开始使用时间")
@JsonView(JsonViewProfile.App.class)
private LocalDateTime suitStartTime;
@ApiModelProperty("套餐结束使用时间")
@JsonView(JsonViewProfile.App.class)
private LocalDateTime suitEndTime;
@ApiModelProperty("套餐到期时间")
@JsonView(JsonViewProfile.App.class)
private LocalDateTime suitExpireTime;
@ApiModelProperty("店铺ID")

View File

@ -32,6 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
stb.device_recharge_status,
stb.suit_id,
stb.suit_time,
stb.suit_time_unit,
stb.suit_start_time,
stb.suit_end_time,
stb.suit_expire_time,
@ -218,6 +219,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceRechargeStatus != null ">device_recharge_status,</if>
<if test="suitId != null ">suit_id,</if>
<if test="suitTime != null ">suit_time,</if>
<if test="suitTimeUnit != null ">suit_time_unit,</if>
<if test="suitStartTime != null ">suit_start_time,</if>
<if test="suitEndTime != null ">suit_end_time,</if>
<if test="suitExpireTime != null ">suit_expire_time,</if>
@ -255,6 +257,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="deviceRechargeStatus != null">#{deviceRechargeStatus},</if>
<if test="suitId != null">#{suitId},</if>
<if test="suitTime != null">#{suitTime},</if>
<if test="suitTimeUnit != null">#{suitTimeUnit},</if>
<if test="suitStartTime != null">#{suitStartTime},</if>
<if test="suitEndTime != null">#{suitEndTime},</if>
<if test="suitExpireTime != null">#{suitExpireTime},</if>
@ -309,6 +312,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="data.channelCost != null">channel_cost = #{data.channelCost},</if>
<if test="data.suitId != null">suit_id = #{data.suitId},</if>
<if test="data.suitTime != null">suit_time = #{data.suitTime},</if>
<if test="data.suitTimeUnit != null">suit_time_unit = #{data.suitTimeUnit},</if>
<if test="data.suitStartTime != null">suit_start_time = #{data.suitStartTime},</if>
<if test="data.suitEndTime != null">suit_end_time = #{data.suitEndTime},</if>
<if test="data.suitExpireTime != null">suit_expire_time = #{data.suitExpireTime},</if>

View File

@ -1,7 +1,6 @@
package com.ruoyi.ss.transactionBill.service.impl;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.exception.ServiceException;
@ -46,8 +45,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@ -547,34 +544,50 @@ public class TransactionBillServiceImpl implements TransactionBillService {
* @param billId
*/
@Override
@Transactional
public boolean rechargeDevice(Long billId) {
ServiceUtil.assertion(billId == null, "参数错误,billId不允许为空");
TransactionBillVo bill = transactionBillMapper.selectSmTransactionBillByBillId(billId);
ServiceUtil.assertion(bill == null || !TransactionBillType.RECHARGE.getType().equals(bill.getType()), "不存在的充值订单");
ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "订单未支付");
ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.SUCCESS.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已充值成功,不允许再次充值");
ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.BLUETOOTH.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已选择蓝牙充值,请使用蓝牙进行充值");
// 电表时长增加,四舍五入,保留1位小数
boolean success = false;
ServiceUtil.assertion(!redisLock.lock(RedisLockKey.RECHARGE_DEVICE, billId), "当前设备充值请求过于频繁,请等待");
try {
success = deviceService.addTime(bill.getDeviceId(), bill.toSecondSuitTime(), true);
} catch (Exception e) {
this.handleDeviceRechargeFail(billId);
TransactionBillVo bill = transactionBillMapper.selectSmTransactionBillByBillId(billId);
ServiceUtil.assertion(bill == null || !TransactionBillType.RECHARGE.getType().equals(bill.getType()), "不存在的充值订单");
ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "订单未支付");
ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.SUCCESS.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已充值成功,不允许再次充值");
ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.BLUETOOTH.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已选择蓝牙充值,请使用蓝牙进行充值");
Boolean result = transactionTemplate.execute(status -> {
// 更新套餐生效时间
SmDeviceVO device = deviceService.selectSmDeviceByDeviceId(bill.getDeviceId());
this.updateSuitTimeBeforeDevice(billId, device.getExpireTime());
// 修改设备充值状态成功
int updateRecharge = transactionBillMapper.updateDeviceRechargeStatus(bill.getBillId(), TransactionBillDeviceRechargeStatus.SUCCESS.getStatus());
ServiceUtil.assertion(updateRecharge != 1, "订单状态发生变化,请稍后重试");
try {
// 设备充值
return deviceService.addTime(bill.getDeviceId(), bill.toSecondSuitTime(), true);
} catch (Exception e) {
// 失败回滚
status.setRollbackOnly();
return false;
}
});
// 是否成功
boolean success = result != null && result;
if (success) {
// 记录时长变化
this.recordTime(bill, "用户充值");
} else {
// 修改设备充值状态为失败
transactionBillMapper.updateDeviceRechargeStatus(billId, TransactionBillDeviceRechargeStatus.FAIL.getStatus());
}
return success;
} finally {
redisLock.unlock(RedisLockKey.RECHARGE_DEVICE, billId);
}
// 修改设备充值状态成功
if (success) {
SmDeviceVO device = deviceService.selectSmDeviceByDeviceId(bill.getDeviceId());
this.updateSuitTimeBeforeDevice(billId, device.getExpireTime());
transactionBillMapper.updateDeviceRechargeStatus(bill.getBillId(), TransactionBillDeviceRechargeStatus.SUCCESS.getStatus());
}
return true;
}
/**
@ -588,7 +601,7 @@ public class TransactionBillServiceImpl implements TransactionBillService {
}
// 计算开始使用的时间
LocalDateTime now = LocalDateTime.now();
LocalDateTime startTime = now.isAfter(deviceExpireTime) ? now : deviceExpireTime;
LocalDateTime startTime = deviceExpireTime == null || now.isAfter(deviceExpireTime) ? now : deviceExpireTime;
LocalDateTime endTime = startTime.plusSeconds(bill.toSecondSuitTime());
TransactionBill data = new TransactionBill();
@ -599,22 +612,12 @@ public class TransactionBillServiceImpl implements TransactionBillService {
return transactionBillMapper.updateSmTransactionBill(data);
}
/**
* 处理设备充值失败
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void handleDeviceRechargeFail(Long billId) {
// 修改设备充值状态为失败
transactionBillMapper.updateDeviceRechargeStatus(billId, TransactionBillDeviceRechargeStatus.FAIL.getStatus());
}
/**
* 获取支付结果
* @param billNo 订单编号
* @return 支付结果
*/
@Override
@Transactional
public boolean getPayResult(String billNo) {
// 查询订单
TransactionBill bill = transactionBillMapper.selectSmTransactionBillByBillNo(billNo);
@ -635,26 +638,30 @@ public class TransactionBillServiceImpl implements TransactionBillService {
* @param billNo 充值订单编号
*/
@Override
@Transactional
public void rechargeSuccess(String billNo, Date payTime) {
log.info("充值成功,billNo:{}", billNo);
TransactionBill bill = transactionBillMapper.selectSmTransactionBillByBillNo(billNo);
ServiceUtil.assertion(bill == null || !TransactionBillType.RECHARGE.getType().equals(bill.getType()), "充值订单不存在");
// 修改订单状态
int updateCount = transactionBillMapper.rechargeSuccess(bill.getBillId(), payTime);
ServiceUtil.assertion(updateCount != 1, "修改订单状态失败,请刷新后重试");
Integer result = transactionTemplate.execute(status -> {
// 修改订单状态
int updateCount = transactionBillMapper.rechargeSuccess(bill.getBillId(), payTime);
ServiceUtil.assertion(updateCount != 1, "修改订单状态失败,请刷新后重试");
// 商户余额增加
userService.addBalance(bill.getMchId(), bill.getArrivalAmount());
// 商户余额增加
userService.addBalance(bill.getMchId(), bill.getArrivalAmount());
// 记录下充值后的余额
SmUserVo user = userService.selectSmUserByUserId(bill.getMchId());
updateAfterBalance(bill.getBillId(), user.getBalance());
// 记录下充值后的余额
SmUserVo user = userService.selectSmUserByUserId(bill.getMchId());
updateAfterBalance(bill.getBillId(), user.getBalance());
// 设备充值
rechargeDevice(bill.getBillId());
return updateCount;
});
if (result != null && result == 1) {
// 设备充值
rechargeDevice(bill.getBillId());
}
}
/**
@ -753,7 +760,6 @@ public class TransactionBillServiceImpl implements TransactionBillService {
* @param timeUnit 时间单位
*/
@Override
@Transactional
public void refreshPayResultBeforeExpire(String billNo, int delay, TimeUnit timeUnit) {
TransactionBill bill = transactionBillMapper.selectSmTransactionBillByBillNo(billNo);
if (bill == null) {
@ -833,14 +839,15 @@ public class TransactionBillServiceImpl implements TransactionBillService {
* @return
*/
@Override
@Transactional
public boolean bluetoothRechargeSuccess(String billNo) {
TransactionBillVo bill = selectSmTransactionBillByBillNo(billNo);
ServiceUtil.assertion(bill == null, "订单不存在", 10001);
ServiceUtil.assertion(!TransactionBillType.RECHARGE.getType().equals(bill.getType()), "该订单不是充值订单", 10002);
ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.SUCCESS.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已充值成功,请勿重复充值", 10003);
ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "订单尚未充值成功", 10004);
Boolean execute = transactionTemplate.execute(status -> {
TransactionBillVo bill = selectSmTransactionBillByBillNo(billNo);
ServiceUtil.assertion(bill == null, "订单不存在", 10001);
ServiceUtil.assertion(!TransactionBillType.RECHARGE.getType().equals(bill.getType()), "该订单不是充值订单", 10002);
ServiceUtil.assertion(TransactionBillDeviceRechargeStatus.SUCCESS.getStatus().equals(bill.getDeviceRechargeStatus()), "设备已充值成功,请勿重复充值", 10003);
ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(bill.getStatus()), "订单尚未充值成功", 10004);
// 更新设备充值状态
boolean result = transactionBillMapper.bluetoothRechargeSuccess(billNo) == 1;
ServiceUtil.assertion(!result, "蓝牙充值回调失败");
@ -848,24 +855,34 @@ public class TransactionBillServiceImpl implements TransactionBillService {
SmDeviceVO afterDevice = deviceService.selectSmDeviceByDeviceId(bill.getDeviceId());
this.updateSuitTimeBeforeDevice(bill.getBillId(), afterDevice.getExpireTime());
// 修改剩余时间失败
boolean addTime = deviceService.addTime(bill.getDeviceId(), bill.toSecondSuitTime(), false);
ServiceUtil.assertion(!addTime, "修改剩余时间失败");
// 时长变化记录
LoginUser loginUser = SecurityUtils.getLoginUser();
scheduledExecutorService.schedule(() -> {
SmDeviceVO device = deviceService.selectSmDeviceByDeviceId(bill.getDeviceId());
if (device == null) {
return;
}
recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, bill.getSuitTime(), "蓝牙手动充值", loginUser));
}, 1L, TimeUnit.SECONDS);
return result;
});
// 是否成功
boolean success = execute != null && execute;
// 成功后操作
if (success) {
// 时长变化记录
this.recordTime(bill, "蓝牙手动充值");
}
return execute != null && execute;
return success;
}
// 记录时长变化记录
private void recordTime(TransactionBillVo bill, String reason) {
scheduledExecutorService.schedule(() -> {
SmDeviceVO device = deviceService.selectSmDeviceByDeviceId(bill.getDeviceId());
if (device == null) {
return;
}
SmUserVo user = userService.selectSmUserByUserId(bill.getUserId());
recordTimeService.insertRecordTime(recordTimeConverter.toRecordTime(device, bill.toSecondSuitTime(), reason, user));
}, 1L, TimeUnit.SECONDS);
}
@Override
@ -928,7 +945,6 @@ public class TransactionBillServiceImpl implements TransactionBillService {
}
@Override
@Transactional
public int refreshPayResult(List<TransactionBillVo> billList) {
if (CollectionUtils.isEmptyElement(billList)) {
return 0;

View File

@ -2,6 +2,7 @@ package com.ruoyi.ss.user.service;
import com.ruoyi.common.core.domain.entity.SmUser;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.ss.access.domain.AccessVO;
import com.ruoyi.ss.user.domain.SmUserQuery;
import com.ruoyi.ss.user.domain.SmUserVo;
import com.ruoyi.ss.user.domain.bo.UserUpdateServiceRateBO;

View File

@ -1,6 +1,8 @@
package com.ruoyi.ss.user.service.impl;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.entity.SmUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;

View File

@ -182,7 +182,6 @@ public class WxPayService implements IWxPayService {
* @param request 请求
*/
@Override
@Transactional
public void payNotify(HttpServletRequest request) {
// 获取原始报文body
String body = HttpUtils.getBody(request);
@ -199,8 +198,6 @@ public class WxPayService implements IWxPayService {
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
// 充值成功修改订单状态
transactionBillService.rechargeSuccess(transaction.getOutTradeNo(), DateUtils.getNowDate());
// 保存通知数据
// saveNotifyData(transaction.getOutTradeNo(), notification, transaction);
}
}
}

View File

@ -59,7 +59,6 @@ public class BillDelayed implements Delayed {
/**
* 执行删除订单任务
*/
@Transactional
public void run() {
log.info("执行订单超时任务:" + billNo);

View File

@ -0,0 +1,37 @@
package com.ruoyi.web.controller.api;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.ss.access.domain.AccessVO;
import com.ruoyi.ss.access.service.AccessService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 第三方认证接口
* @author wjh
* 2024/7/17
*/
@RestController
@RequestMapping("/openapi/v1/access")
public class ApiAccessController extends BaseController {
@Autowired
private AccessService accessService;
@ApiOperation("申请accessKey")
@PostMapping
public AjaxResult apply() {
return success(accessService.apply(getUserId()));
}
@ApiOperation("重置accessSecret")
@PutMapping("/{accessId}/reset")
public AjaxResult reset(@PathVariable Long accessId) {
AccessVO access = accessService.selectAccessByAccessId(accessId);
return success(accessService.reset(accessId));
}
}

View File

@ -0,0 +1,17 @@
package com.ruoyi.web.controller.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 第三方API 设备Controller
* @author wjh
* 2024/7/17
*/
@RestController
@RequestMapping("/openapi/v1/device")
public class ApiDeviceController {
}

View File

@ -87,7 +87,7 @@ public class AppDeviceController extends BaseController {
@ApiOperation("获取设备详细信息")
@GetMapping(value = "/{deviceId}")
public AjaxResult getInfo(@PathVariable("deviceId") Long deviceId) {
// ServiceUtil.assertion(!deviceValidator.isBelong(deviceId, getUserId()), "这不是您的设备");
smDeviceService.pullDeviceInfo(Collections.singletonList(deviceId));
SmDeviceVO device = smDeviceService.selectSmDeviceByDeviceId(deviceId);
List<SmDeviceVO> list = Collections.singletonList(device);
deviceAssembler.assembleOrderCountInfo(list); // 订单统计信息
@ -137,13 +137,12 @@ public class AppDeviceController extends BaseController {
@ApiOperation("设备充值时长")
@PutMapping("/addTime/{deviceId}")
public AjaxResult addTime(@PathVariable @ApiParam("设备id") Long deviceId, @ApiParam("电量(度") Long amount)
public AjaxResult addTime(@PathVariable @ApiParam("设备id") Long deviceId, @ApiParam("时长(分钟") Long amount)
{
ServiceUtil.assertion(!deviceValidator.isBelong(deviceId, getUserId()), "这不是您的设备");
SmDeviceVO device = smDeviceService.selectSmDeviceByDeviceId(deviceId);
ServiceUtil.assertion(device == null || !getUserId().equals(device.getUserId()), "设备不存在或您无权充值");
smDeviceService.addTimeByUser(deviceId, amount, true, "商户手动充值");
return success(true);
return toAjax(smDeviceService.addTimeByUser(deviceId, amount * 60, true, "商户手动充值"));
}
@ApiOperation("刷新设备信息")

View File

@ -72,6 +72,7 @@ public class AppSuitController extends BaseController {
@ApiOperation("新增套餐")
@PostMapping
public AjaxResult add(@RequestBody @Validated({ValidGroup.FrontCreate.class}) SuitBO suit) {
suit.setUserId(getUserId());
suit = suit.filterCreateByApp();
ServiceUtil.assertion(suitValidator.preCreateByApp(suit));
return success(suitService.insertSuit(suit));
@ -85,8 +86,7 @@ public class AppSuitController extends BaseController {
@ApiOperation("修改套餐")
@PutMapping
public AjaxResult edit(@RequestBody @Validated({ValidGroup.FrontUpdate.class}) SuitBO suit)
{
public AjaxResult edit(@RequestBody @Validated({ValidGroup.FrontUpdate.class}) SuitBO suit) {
suit = suit.filterUpdateByApp();
ServiceUtil.assertion(suitValidator.preUpdateByApp(suit));
return success(suitService.updateSuit(suit));

View File

@ -0,0 +1,107 @@
package com.ruoyi.web.controller.ss;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.ss.access.domain.AccessQuery;
import com.ruoyi.ss.access.domain.AccessVO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.ss.access.domain.Access;
import com.ruoyi.ss.access.service.AccessService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* 第三方API秘钥对Controller
*
* @author ruoyi
* @date 2024-07-17
*/
@RestController
@RequestMapping("/ss/access")
public class AccessController extends BaseController
{
@Autowired
private AccessService accessService;
/**
* 查询第三方API秘钥对列表
*/
@PreAuthorize("@ss.hasPermi('ss:access:list')")
@GetMapping("/list")
public TableDataInfo list(AccessQuery query)
{
startPage();
List<AccessVO> list = accessService.selectAccessList(query);
return getDataTable(list);
}
/**
* 导出第三方API秘钥对列表
*/
@PreAuthorize("@ss.hasPermi('ss:access:export')")
@Log(title = "第三方API秘钥对", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, AccessQuery query)
{
List<AccessVO> list = accessService.selectAccessList(query);
ExcelUtil<AccessVO> util = new ExcelUtil<AccessVO>(AccessVO.class);
util.exportExcel(response, list, "第三方API秘钥对数据");
}
/**
* 获取第三方API秘钥对详细信息
*/
@PreAuthorize("@ss.hasPermi('ss:access:query')")
@GetMapping(value = "/{accessId}")
public AjaxResult getInfo(@PathVariable("accessId") Long accessId)
{
return success(accessService.selectAccessByAccessId(accessId));
}
/**
* 新增第三方API秘钥对
*/
@PreAuthorize("@ss.hasPermi('ss:access:add')")
@Log(title = "第三方API秘钥对", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody Access access)
{
return toAjax(accessService.insertAccess(access));
}
/**
* 修改第三方API秘钥对
*/
@PreAuthorize("@ss.hasPermi('ss:access:edit')")
@Log(title = "第三方API秘钥对", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody Access access)
{
return toAjax(accessService.updateAccess(access));
}
/**
* 删除第三方API秘钥对
*/
@PreAuthorize("@ss.hasPermi('ss:access:remove')")
@Log(title = "第三方API秘钥对", businessType = BusinessType.DELETE)
@DeleteMapping("/{accessIds}")
public AjaxResult remove(@PathVariable List<Long> accessIds)
{
return toAjax(accessService.deleteAccessByAccessIds(accessIds));
}
}

View File

@ -86,6 +86,7 @@ public class SmDeviceController extends BaseController
@GetMapping(value = "/{deviceId}")
public AjaxResult getInfo(@PathVariable("deviceId") Long deviceId)
{
smDeviceService.pullDeviceInfo(Collections.singletonList(deviceId));
SmDeviceVO device = smDeviceService.selectSmDeviceByDeviceId(deviceId);
List<SmDeviceVO> list = Collections.singletonList(device);
deviceAssembler.assembleIotDeviceInfo(list);
@ -153,8 +154,8 @@ public class SmDeviceController extends BaseController
@ApiOperation("设备充值时长")
@PreAuthorize("@ss.hasPermi('system:device:addTime')")
@PutMapping("/addTime/{deviceId}")
public AjaxResult addTime(@PathVariable @ApiParam("设备id") Long deviceId, @ApiParam("时长(") Long amount) {
return toAjax(smDeviceService.addTimeByUser(deviceId, amount, true, "管理员手动充值"));
public AjaxResult addTime(@PathVariable @ApiParam("设备id") Long deviceId, @ApiParam("时长(") Long amount) {
return toAjax(smDeviceService.addTimeByUser(deviceId, amount * 60, true, "管理员手动充值"));
}
@ApiOperation("设备时长归零")

View File

@ -159,10 +159,8 @@ public class SmTransactionBillController extends BaseController
@ApiModelProperty("手动设备充值")
@GetMapping("/rechargeDevice/{billId}")
@Log(title = "手动设备充值", businessType = BusinessType.UPDATE)
public AjaxResult rechargeDevice(@PathVariable Long billId)
{
smTransactionBillService.rechargeDevice(billId);
return success();
public AjaxResult rechargeDevice(@PathVariable Long billId) {
return toAjax(smTransactionBillService.rechargeDevice(billId));
}
// 订单退款

View File

@ -6,6 +6,8 @@ import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.core.domain.ValidGroup;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.ss.device.domain.vo.SmDeviceVO;
import com.ruoyi.ss.device.service.DeviceService;
import com.ruoyi.ss.suit.domain.SuitBO;
import com.ruoyi.ss.suit.domain.SuitQuery;
import com.ruoyi.ss.suit.domain.SuitVo;
@ -46,6 +48,9 @@ public class SuitController extends BaseController
@Autowired
private SuitValidator suitValidator;
@Autowired
private DeviceService deviceService;
/**
* 查询套餐列表
*/
@ -89,6 +94,11 @@ public class SuitController extends BaseController
@PostMapping
public AjaxResult add(@RequestBody @Validated(ValidGroup.Create.class) SuitBO suit)
{
SmDeviceVO device = deviceService.selectSmDeviceByDeviceId(suit.getDeviceId());
if (device == null) {
return error("设备不存在");
}
suit.setUserId(device.getUserId());
return toAjax(suitService.insertSuit(suit));
}
@ -98,7 +108,7 @@ public class SuitController extends BaseController
@PreAuthorize("@ss.hasPermi('ss:suit:edit')")
@Log(title = "套餐", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody @Validated(ValidGroup.Update.class) Suit suit)
public AjaxResult edit(@RequestBody @Validated(ValidGroup.Update.class) SuitBO suit)
{
return toAjax(suitService.updateSuit(suit));
}

View File

@ -177,3 +177,8 @@ sm:
withdraw:
password:
enable: false
# 对外API设置
access:
# 用户最大密钥对数量
maxCount: 1