feat(device): 实现设备与账号绑定管理机制
- 引入 ClientAccountDevice 表管理设备与账号绑定关系 - 重构设备注册逻辑,支持多账号绑定同一设备 - 新增设备配额检查,基于账号维度限制设备数量 -优化设备移除逻辑,仅解除绑定而非物理删除- 改进设备列表查询,通过账号ID关联获取设备信息 - 更新心跳任务,支持向设备绑定的所有账号发送心跳 - 调整设备API参数,增加username字段用于权限校验 -修复HTTP请求编码问题,统一使用UTF-8字符集 - 增强错误处理,携带错误码信息便于前端识别 - 移除设备表中的username字段,解耦设备与用户名关联
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.system.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 客户端账号-设备关联对象 client_account_device
|
||||
*/
|
||||
public class ClientAccountDevice extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Long accountId;
|
||||
private String deviceId;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date bindTime;
|
||||
private String status; // active/removed
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public void setAccountId(Long accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public Date getBindTime() {
|
||||
return bindTime;
|
||||
}
|
||||
|
||||
public void setBindTime(Date bindTime) {
|
||||
this.bindTime = bindTime;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ public class ClientDevice extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
@Excel(name = "用户名")
|
||||
private String username;
|
||||
@Excel(name = "设备ID")
|
||||
private String deviceId;
|
||||
@Excel(name = "设备名")
|
||||
@@ -36,8 +34,6 @@ public class ClientDevice extends BaseEntity {
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getDeviceId() { return deviceId; }
|
||||
public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
|
||||
public String getName() { return name; }
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.system.mapper;
|
||||
|
||||
import com.ruoyi.system.domain.ClientAccountDevice;
|
||||
import com.ruoyi.system.domain.ClientDevice;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客户端账号-设备关联Mapper接口
|
||||
*/
|
||||
public interface ClientAccountDeviceMapper {
|
||||
/**
|
||||
* 根据账号ID查询已绑定的设备列表
|
||||
*/
|
||||
List<ClientDevice> selectDevicesByAccountId(@Param("accountId") Long accountId);
|
||||
|
||||
/**
|
||||
* 根据设备ID查询绑定的账号ID列表
|
||||
*/
|
||||
List<Long> selectAccountIdsByDeviceId(@Param("deviceId") String deviceId);
|
||||
|
||||
/**
|
||||
* 查询账号绑定的设备数量(不包括已移除的)
|
||||
*/
|
||||
int countActiveDevicesByAccountId(@Param("accountId") Long accountId);
|
||||
|
||||
/**
|
||||
* 检查账号和设备是否已绑定
|
||||
*/
|
||||
ClientAccountDevice selectByAccountIdAndDeviceId(@Param("accountId") Long accountId, @Param("deviceId") String deviceId);
|
||||
|
||||
/**
|
||||
* 插入账号-设备绑定
|
||||
*/
|
||||
int insert(ClientAccountDevice binding);
|
||||
|
||||
/**
|
||||
* 更新绑定状态
|
||||
*/
|
||||
int updateStatus(@Param("accountId") Long accountId, @Param("deviceId") String deviceId, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 删除绑定
|
||||
*/
|
||||
int delete(@Param("accountId") Long accountId, @Param("deviceId") String deviceId);
|
||||
}
|
||||
|
||||
@@ -7,14 +7,10 @@ import java.util.List;
|
||||
|
||||
public interface ClientDeviceMapper {
|
||||
ClientDevice selectByDeviceId(@Param("deviceId") String deviceId);
|
||||
ClientDevice selectByDeviceIdAndUsername(@Param("deviceId") String deviceId, @Param("username") String username);
|
||||
List<ClientDevice> selectByUsername(@Param("username") String username);
|
||||
List<ClientDevice> selectOnlineDevices();
|
||||
int insert(ClientDevice device);
|
||||
int updateByDeviceId(ClientDevice device);
|
||||
int updateByDeviceIdAndUsername(ClientDevice device);
|
||||
int deleteByDeviceId(@Param("deviceId") String deviceId);
|
||||
int countByUsername(@Param("username") String username);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import com.ruoyi.system.domain.BanmaAccount;
|
||||
import com.ruoyi.system.domain.ClientAccount;
|
||||
import com.ruoyi.system.mapper.BanmaAccountMapper;
|
||||
import com.ruoyi.system.mapper.ClientAccountMapper;
|
||||
import com.ruoyi.system.service.IBanmaAccountService;
|
||||
|
||||
/**
|
||||
@@ -21,9 +23,21 @@ public class BanmaAccountServiceImpl implements IBanmaAccountService {
|
||||
|
||||
@Autowired
|
||||
private BanmaAccountMapper mapper;
|
||||
@Autowired
|
||||
private ClientAccountMapper clientAccountMapper;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private static final String LOGIN_URL = "https://banma365.cn/api/login";
|
||||
|
||||
private int getAccountLimit(String clientUsername) {
|
||||
if (clientUsername == null) return 3;
|
||||
ClientAccount client = clientAccountMapper.selectClientAccountByUsername(clientUsername);
|
||||
if (client == null) return 1;
|
||||
if ("paid".equals(client.getAccountType()) && client.getExpireTime() != null && new Date().before(client.getExpireTime())) {
|
||||
return 3;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BanmaAccount> listSimple() {
|
||||
return listSimple(null);
|
||||
@@ -51,6 +65,17 @@ public class BanmaAccountServiceImpl implements IBanmaAccountService {
|
||||
entity.setClientUsername(clientUsername);
|
||||
}
|
||||
|
||||
// 新增时检查数量限制
|
||||
if (entity.getId() == null && entity.getClientUsername() != null) {
|
||||
int limit = getAccountLimit(entity.getClientUsername());
|
||||
BanmaAccount query = new BanmaAccount();
|
||||
query.setClientUsername(entity.getClientUsername());
|
||||
int count = mapper.selectList(query).size();
|
||||
if (count >= limit) {
|
||||
throw new RuntimeException("账号数量已达上限(" + limit + "个),请升级订阅或删除其他账号");
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.getId() == null) {
|
||||
mapper.insert(entity);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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.system.mapper.ClientAccountDeviceMapper">
|
||||
|
||||
<resultMap id="ClientAccountDeviceMap" type="com.ruoyi.system.domain.ClientAccountDevice">
|
||||
<id property="id" column="id"/>
|
||||
<result property="accountId" column="account_id"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="bindTime" column="bind_time"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="ClientDeviceResultMap" type="com.ruoyi.system.domain.ClientDevice">
|
||||
<id property="id" column="id"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="os" column="os"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="ip" column="ip"/>
|
||||
<result property="location" column="location"/>
|
||||
<result property="lastActiveAt" column="last_active_at"/>
|
||||
<result property="trialExpireTime" column="trial_expire_time"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据账号ID查询已绑定的设备列表 -->
|
||||
<select id="selectDevicesByAccountId" resultMap="ClientDeviceResultMap">
|
||||
SELECT
|
||||
d.id, d.device_id, d.name, d.os, d.status, d.ip, d.location,
|
||||
d.last_active_at, d.trial_expire_time, d.create_time, d.update_time
|
||||
FROM client_device d
|
||||
INNER JOIN client_account_device ad ON d.device_id = ad.device_id
|
||||
WHERE ad.account_id = #{accountId}
|
||||
AND ad.status = 'active'
|
||||
ORDER BY d.last_active_at DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据设备ID查询绑定的账号ID列表 -->
|
||||
<select id="selectAccountIdsByDeviceId" resultType="java.lang.Long">
|
||||
SELECT account_id
|
||||
FROM client_account_device
|
||||
WHERE device_id = #{deviceId}
|
||||
AND status = 'active'
|
||||
</select>
|
||||
|
||||
<!-- 查询账号绑定的设备数量(不包括已移除的) -->
|
||||
<select id="countActiveDevicesByAccountId" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM client_account_device
|
||||
WHERE account_id = #{accountId}
|
||||
AND status = 'active'
|
||||
</select>
|
||||
|
||||
<!-- 检查账号和设备是否已绑定 -->
|
||||
<select id="selectByAccountIdAndDeviceId" resultMap="ClientAccountDeviceMap">
|
||||
SELECT *
|
||||
FROM client_account_device
|
||||
WHERE account_id = #{accountId}
|
||||
AND device_id = #{deviceId}
|
||||
</select>
|
||||
|
||||
<!-- 插入账号-设备绑定 -->
|
||||
<insert id="insert" parameterType="com.ruoyi.system.domain.ClientAccountDevice" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO client_account_device(account_id, device_id, bind_time, status, create_time, update_time)
|
||||
VALUES(#{accountId}, #{deviceId}, #{bindTime}, #{status}, now(), now())
|
||||
</insert>
|
||||
|
||||
<!-- 更新绑定状态 -->
|
||||
<update id="updateStatus">
|
||||
UPDATE client_account_device
|
||||
SET status = #{status}, update_time = now()
|
||||
WHERE account_id = #{accountId}
|
||||
AND device_id = #{deviceId}
|
||||
</update>
|
||||
|
||||
<!-- 删除绑定 -->
|
||||
<delete id="delete">
|
||||
DELETE FROM client_account_device
|
||||
WHERE account_id = #{accountId}
|
||||
AND device_id = #{deviceId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<mapper namespace="com.ruoyi.system.mapper.ClientDeviceMapper">
|
||||
<resultMap id="ClientDeviceMap" type="com.ruoyi.system.domain.ClientDevice">
|
||||
<id property="id" column="id"/>
|
||||
<result property="username" column="username"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="os" column="os"/>
|
||||
@@ -17,38 +16,16 @@
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByDeviceId" resultMap="ClientDeviceMap">
|
||||
select * from client_device where device_id = #{deviceId}
|
||||
</select>
|
||||
|
||||
<select id="selectByDeviceIdAndUsername" resultMap="ClientDeviceMap">
|
||||
select * from client_device where device_id = #{deviceId} and username = #{username}
|
||||
</select>
|
||||
|
||||
<select id="selectByUsername" resultMap="ClientDeviceMap">
|
||||
select * from client_device where username = #{username} and status != 'removed' order by update_time desc
|
||||
SELECT * FROM client_device WHERE device_id = #{deviceId}
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.ruoyi.system.domain.ClientDevice" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into client_device(username, device_id, name, os, status, ip, location, last_active_at, trial_expire_time, create_time, update_time)
|
||||
values(#{username}, #{deviceId}, #{name}, #{os}, #{status}, #{ip}, #{location}, #{lastActiveAt}, #{trialExpireTime}, now(), now())
|
||||
INSERT INTO client_device(device_id, name, os, status, ip, location, last_active_at, trial_expire_time, create_time, update_time)
|
||||
VALUES(#{deviceId}, #{name}, #{os}, #{status}, #{ip}, #{location}, #{lastActiveAt}, #{trialExpireTime}, now(), now())
|
||||
</insert>
|
||||
|
||||
<update id="updateByDeviceId" parameterType="com.ruoyi.system.domain.ClientDevice">
|
||||
update client_device
|
||||
set username = #{username},
|
||||
name = #{name},
|
||||
os = #{os},
|
||||
status = #{status},
|
||||
ip = #{ip},
|
||||
location = #{location},
|
||||
last_active_at = #{lastActiveAt},
|
||||
trial_expire_time = #{trialExpireTime},
|
||||
update_time = now()
|
||||
where device_id = #{deviceId}
|
||||
</update>
|
||||
|
||||
<update id="updateByDeviceIdAndUsername" parameterType="com.ruoyi.system.domain.ClientDevice">
|
||||
update client_device
|
||||
UPDATE client_device
|
||||
<set>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="os != null">os = #{os},</if>
|
||||
@@ -59,19 +36,15 @@
|
||||
<if test="trialExpireTime != null">trial_expire_time = #{trialExpireTime},</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
where device_id = #{deviceId} and username = #{username}
|
||||
WHERE device_id = #{deviceId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteByDeviceId">
|
||||
delete from client_device where device_id = #{deviceId}
|
||||
DELETE FROM client_device WHERE device_id = #{deviceId}
|
||||
</delete>
|
||||
|
||||
<select id="countByUsername" resultType="int">
|
||||
select count(1) from client_device where username = #{username}
|
||||
</select>
|
||||
|
||||
<select id="selectOnlineDevices" resultMap="ClientDeviceMap">
|
||||
select * from client_device where status = 'online' order by last_active_at desc
|
||||
SELECT * FROM client_device WHERE status = 'online' ORDER BY last_active_at DESC
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -169,21 +169,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="selectClientInfoList" parameterType="ClientInfo" resultMap="ClientInfoResult">
|
||||
select
|
||||
d.device_id as client_id,
|
||||
d.username,
|
||||
a.username,
|
||||
d.os as os_name,
|
||||
d.ip as ip_address,
|
||||
d.last_active_at as last_active_time,
|
||||
d.create_time as auth_time,
|
||||
ad.bind_time as auth_time,
|
||||
CASE WHEN d.status = 'online' THEN '1' ELSE '0' END as online,
|
||||
a.account_name as hostname,
|
||||
'' as app_version,
|
||||
'' as os_version,
|
||||
'' as java_version
|
||||
from client_device d
|
||||
left join client_account a on d.username COLLATE utf8mb4_unicode_ci = a.username
|
||||
left join client_account_device ad on d.device_id = ad.device_id and ad.status = 'active'
|
||||
left join client_account a on ad.account_id = a.id
|
||||
<where>
|
||||
<if test="clientId != null and clientId != ''">AND d.device_id like concat('%', #{clientId}, '%')</if>
|
||||
<if test="username != null and username != ''">AND d.username like concat('%', #{username}, '%')</if>
|
||||
<if test="username != null and username != ''">AND a.username like concat('%', #{username}, '%')</if>
|
||||
<if test="osName != null and osName != ''">AND d.os like concat('%', #{osName}, '%')</if>
|
||||
<if test="online != null and online != ''">
|
||||
AND d.status =
|
||||
|
||||
Reference in New Issue
Block a user