feat(device): 增加设备信息持久化和OTA升级功能
- 新增设备持久化定时任务,每5分钟将Redis中采集的设备信息同步数据库 - 新增UserDevice和UserActive模型及相应数据库表和操作接口 - 设备信息上报中获取客户端公网IP,替代原X-Forwarded-For头部 - 新建OTA功能模块,实现OTA固件升级信息查询接口 - 支持OTA黑名单过滤与定向升级设备判断 - 设计OTA相关数据库结构:ota、black_list、ota_target_device表 - 缓存预热新增按品牌分组预热型号缓存 - 依赖注入新增OTA Repository及Handler路由配置 - 实现客户端公网IP提取逻辑,支持多种代理头部优先级识别
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxsin/app-api/internal/model"
|
||||
)
|
||||
|
||||
type DeviceRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDeviceRepository(db *sql.DB) *DeviceRepository {
|
||||
return &DeviceRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *DeviceRepository) FindDeviceByMac(ctx context.Context, macAddr string) (*model.UserDevice, error) {
|
||||
const query = `SELECT id, mac_addr, model, add_time, ver FROM user_device WHERE mac_addr = ?`
|
||||
|
||||
var d model.UserDevice
|
||||
var ver sql.NullString
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, macAddr).Scan(&d.ID, &d.MacAddr, &d.Model, &d.AddTime, &ver)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query user_device: %w", err)
|
||||
}
|
||||
|
||||
if ver.Valid {
|
||||
d.Ver = &ver.String
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepository) InsertDevice(ctx context.Context, d model.UserDevice) error {
|
||||
const query = `INSERT INTO user_device (mac_addr, model, ver) VALUES (?, ?, ?)`
|
||||
_, err := r.db.ExecContext(ctx, query, d.MacAddr, d.Model, d.Ver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert user_device: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepository) UpdateDeviceVer(ctx context.Context, id int, ver *string) error {
|
||||
const query = `UPDATE user_device SET ver = ? WHERE id = ?`
|
||||
_, err := r.db.ExecContext(ctx, query, ver, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update user_device: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepository) FindActiveByMacAndDate(ctx context.Context, macAddr, activeDate string) (*model.UserActive, error) {
|
||||
const query = `SELECT id, mac_addr, model, active_date, ip_addr, create_at FROM user_active WHERE mac_addr = ? AND active_date = ?`
|
||||
|
||||
var a model.UserActive
|
||||
err := r.db.QueryRowContext(ctx, query, macAddr, activeDate).Scan(&a.ID, &a.MacAddr, &a.Model, &a.ActiveDate, &a.IpAddr, &a.CreateAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query user_active: %w", err)
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepository) InsertActive(ctx context.Context, a model.UserActive) error {
|
||||
const query = `INSERT INTO user_active (mac_addr, model, active_date, ip_addr) VALUES (?, ?, ?, ?)`
|
||||
_, err := r.db.ExecContext(ctx, query, a.MacAddr, a.Model, a.ActiveDate, a.IpAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert user_active: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepository) UpdateActiveIp(ctx context.Context, id int, ipAddr string) error {
|
||||
const query = `UPDATE user_active SET ip_addr = ? WHERE id = ?`
|
||||
_, err := r.db.ExecContext(ctx, query, ipAddr, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update user_active: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxsin/app-api/internal/model"
|
||||
)
|
||||
|
||||
type OTARepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewOTARepository(db *sql.DB) *OTARepository {
|
||||
return &OTARepository{db: db}
|
||||
}
|
||||
|
||||
// GetLatestOTA 按 model+hw+beta+status=1 查询最新一条 OTA 记录(verCode 降序)
|
||||
func (r *OTARepository) GetLatestOTA(ctx context.Context, modelVal string, hw, beta int) (*model.OTA, error) {
|
||||
const query = `SELECT id, verCode, verName, url, md5, force, desc, model, hw, target, beta,
|
||||
pawVerCode, pawVerName, pawUrl, pawMd5, startTime, endTime, status
|
||||
FROM ota WHERE model = ? AND hw = ? AND beta = ? AND status = 1
|
||||
ORDER BY verCode DESC LIMIT 1`
|
||||
|
||||
var o model.OTA
|
||||
var desc, mdl sql.NullString
|
||||
var startTime, endTime sql.NullTime
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, modelVal, hw, beta).Scan(
|
||||
&o.ID, &o.VerCode, &o.VerName, &o.URL, &o.MD5, &o.Force, &desc, &mdl,
|
||||
&o.HW, &o.Target, &o.Beta,
|
||||
&o.PawVerCode, &o.PawVerName, &o.PawURL, &o.PawMD5, &startTime, &endTime, &o.Status,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query ota: %w", err)
|
||||
}
|
||||
|
||||
if desc.Valid {
|
||||
o.Desc = &desc.String
|
||||
}
|
||||
if mdl.Valid {
|
||||
o.Model = &mdl.String
|
||||
}
|
||||
if startTime.Valid {
|
||||
o.StartTime = &startTime.Time
|
||||
}
|
||||
if endTime.Valid {
|
||||
o.EndTime = &endTime.Time
|
||||
}
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
// GetLatestOTANotInBlackList 查询最新一条不在黑名单中的 OTA 记录
|
||||
func (r *OTARepository) GetLatestOTANotInBlackList(ctx context.Context, modelVal string, hw, beta int, mac string) (*model.OTA, error) {
|
||||
const query = `SELECT id, verCode, verName, url, md5, force, desc, model, hw, target, beta,
|
||||
pawVerCode, pawVerName, pawUrl, pawMd5, startTime, endTime, status
|
||||
FROM ota WHERE status = 1 AND model = ? AND hw = ? AND beta = ?
|
||||
AND id NOT IN (SELECT ota_id FROM black_list WHERE mac = ?)
|
||||
ORDER BY verCode DESC LIMIT 1`
|
||||
|
||||
var o model.OTA
|
||||
var desc, mdl sql.NullString
|
||||
var startTime, endTime sql.NullTime
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, modelVal, hw, beta, mac).Scan(
|
||||
&o.ID, &o.VerCode, &o.VerName, &o.URL, &o.MD5, &o.Force, &desc, &mdl,
|
||||
&o.HW, &o.Target, &o.Beta,
|
||||
&o.PawVerCode, &o.PawVerName, &o.PawURL, &o.PawMD5, &startTime, &endTime, &o.Status,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query ota not in blacklist: %w", err)
|
||||
}
|
||||
|
||||
if desc.Valid {
|
||||
o.Desc = &desc.String
|
||||
}
|
||||
if mdl.Valid {
|
||||
o.Model = &mdl.String
|
||||
}
|
||||
if startTime.Valid {
|
||||
o.StartTime = &startTime.Time
|
||||
}
|
||||
if endTime.Valid {
|
||||
o.EndTime = &endTime.Time
|
||||
}
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
// GetLatestOTANotTarget 查询最新一条非定向的 OTA 记录
|
||||
func (r *OTARepository) GetLatestOTANotTarget(ctx context.Context, modelVal string, hw, beta int) (*model.OTA, error) {
|
||||
const query = `SELECT id, verCode, verName, url, md5, force, desc, model, hw, target, beta,
|
||||
pawVerCode, pawVerName, pawUrl, pawMd5, startTime, endTime, status
|
||||
FROM ota WHERE status = 1 AND model = ? AND hw = ? AND beta = ? AND target = 0
|
||||
ORDER BY verCode DESC LIMIT 1`
|
||||
|
||||
var o model.OTA
|
||||
var desc, mdl sql.NullString
|
||||
var startTime, endTime sql.NullTime
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, modelVal, hw, beta).Scan(
|
||||
&o.ID, &o.VerCode, &o.VerName, &o.URL, &o.MD5, &o.Force, &desc, &mdl,
|
||||
&o.HW, &o.Target, &o.Beta,
|
||||
&o.PawVerCode, &o.PawVerName, &o.PawURL, &o.PawMD5, &startTime, &endTime, &o.Status,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query ota not target: %w", err)
|
||||
}
|
||||
|
||||
if desc.Valid {
|
||||
o.Desc = &desc.String
|
||||
}
|
||||
if mdl.Valid {
|
||||
o.Model = &mdl.String
|
||||
}
|
||||
if startTime.Valid {
|
||||
o.StartTime = &startTime.Time
|
||||
}
|
||||
if endTime.Valid {
|
||||
o.EndTime = &endTime.Time
|
||||
}
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
// IsInBlackList 检查 mac 是否在指定 ota_id 的黑名单中
|
||||
func (r *OTARepository) IsInBlackList(ctx context.Context, otaID int, mac string) (bool, error) {
|
||||
const query = `SELECT COUNT(*) FROM black_list WHERE ota_id = ? AND mac = ?`
|
||||
|
||||
var count int
|
||||
if err := r.db.QueryRowContext(ctx, query, otaID, mac).Scan(&count); err != nil {
|
||||
return false, fmt.Errorf("query black_list: %w", err)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// FindTargetDevice 检查 mac 是否在指定 ota_id 的定向设备中
|
||||
func (r *OTARepository) FindTargetDevice(ctx context.Context, otaID int, mac string) (*model.OTATargetDevice, error) {
|
||||
const query = `SELECT id, ota_id, mac_addr, type, create_at FROM ota_target_device WHERE ota_id = ? AND mac_addr = ?`
|
||||
|
||||
var d model.OTATargetDevice
|
||||
err := r.db.QueryRowContext(ctx, query, otaID, mac).Scan(&d.ID, &d.OTAID, &d.MacAddr, &d.Type, &d.CreateAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query ota_target_device: %w", err)
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
Reference in New Issue
Block a user