@@ -41,6 +41,20 @@ func (h *UserDeviceHandler) UnbindDevice(c *gin.Context) {
4141
4242 binding .Status = false
4343 h .db .Save (& binding )
44+
45+ // When a user explicitly unbinds a device they own, also clear the
46+ // device-level logged_in flag so the device is not shown as an active
47+ // login anywhere. Only clear if the device is still owned by this user
48+ // (don't stomp on a concurrent takeover).
49+ h .db .Model (& models.Device {}).
50+ Where ("device_id = ? AND user_id = ?" , req .DeviceID , authedUserID ).
51+ Update ("logged_in" , false )
52+
53+ h .notifySync (authedUserID , gin.H {
54+ "type" : "device_logged_out" ,
55+ "device_id" : req .DeviceID ,
56+ })
57+
4458 recomputeDeviceCount (h .db , authedUserID )
4559
4660 c .JSON (http .StatusOK , gin.H {"message" : "设备解绑成功" })
@@ -200,17 +214,37 @@ func (h *UserDeviceHandler) AutoBindDevice(c *gin.Context) {
200214 return
201215 }
202216
217+ // Account takeover: if the device is currently bound to a different
218+ // user (e.g. previous user crashed without logging out, or the device
219+ // is being re-used by another account), transfer ownership and notify
220+ // the previous owner that the device is no longer logged in for them.
221+ // This is the canonical way to recover from "zombie logged_in=true"
222+ // states left behind by abnormal exits.
223+ var previousUserID uint
203224 if device .UserID != nil && * device .UserID != 0 && * device .UserID != authedUserID {
204- c .JSON (http .StatusConflict , gin.H {"error" : "设备已绑定其他用户" })
205- return
225+ previousUserID = * device .UserID
226+ // Deactivate the previous user's binding row so it stops
227+ // appearing in their device list.
228+ h .db .Model (& models.UserDevice {}).
229+ Where ("user_id = ? AND device_id = ?" , previousUserID , req .DeviceID ).
230+ Update ("status" , false )
231+ recomputeDeviceCount (h .db , previousUserID )
206232 }
207233
208- // Update device ownership
234+ // Update device ownership and mark as logged in for the new user.
209235 h .db .Model (& models.Device {}).Where ("device_id = ?" , req .DeviceID ).Updates (map [string ]interface {}{
210236 "user_id" : authedUserID ,
211237 "logged_in" : true ,
212238 })
213239
240+ // Notify the previous owner (if any) that the device left their account.
241+ if previousUserID != 0 {
242+ h .notifySync (previousUserID , gin.H {
243+ "type" : "device_logged_out" ,
244+ "device_id" : req .DeviceID ,
245+ })
246+ }
247+
214248 // Upsert UserDevice: reactivate if exists but inactive, create otherwise
215249 var existing models.UserDevice
216250 result := h .db .Where ("user_id = ? AND device_id = ?" , authedUserID , req .DeviceID ).First (& existing )
@@ -434,60 +468,3 @@ func (h *UserDeviceHandler) RemoveFavorite(c *gin.Context) {
434468
435469 c .JSON (http .StatusOK , gin.H {"message" : "取消收藏成功" })
436470}
437-
438- // DeviceLogin handles POST /api/v1/user/devices/:device_id/login
439- // Called when a user logs in on a device, marks the device as actively logged in.
440- func (h * UserDeviceHandler ) DeviceLogin (c * gin.Context ) {
441- deviceID := c .Param ("device_id" )
442- userIDVal , _ := c .Get ("authed_user_id" )
443- authedUserID := userIDVal .(uint )
444-
445- var device models.Device
446- if result := h .db .Where ("device_id = ?" , deviceID ).First (& device ); result .Error != nil {
447- c .JSON (http .StatusNotFound , gin.H {"error" : "设备不存在" })
448- return
449- }
450-
451- if device .UserID != nil && * device .UserID != 0 && * device .UserID != authedUserID {
452- c .JSON (http .StatusForbidden , gin.H {"error" : "无权操作此设备" })
453- return
454- }
455-
456- result := h .db .Model (& models.Device {}).Where ("device_id = ?" , deviceID ).Update ("logged_in" , true )
457- log .Printf ("DeviceLogin: device_id=%s, rows_affected=%d, error=%v" , deviceID , result .RowsAffected , result .Error )
458-
459- h .notifySync (authedUserID , gin.H {
460- "type" : "device_logged_in" ,
461- "device_id" : deviceID ,
462- })
463-
464- c .JSON (http .StatusOK , gin.H {"message" : "设备登录状态已更新" })
465- }
466-
467- // DeviceLogout handles POST /api/v1/user/devices/:device_id/logout
468- // Called when a user logs out on a device, marks the device as not logged in.
469- func (h * UserDeviceHandler ) DeviceLogout (c * gin.Context ) {
470- deviceID := c .Param ("device_id" )
471- userIDVal , _ := c .Get ("authed_user_id" )
472- authedUserID := userIDVal .(uint )
473-
474- var device models.Device
475- if result := h .db .Where ("device_id = ?" , deviceID ).First (& device ); result .Error != nil {
476- c .JSON (http .StatusNotFound , gin.H {"error" : "设备不存在" })
477- return
478- }
479-
480- if device .UserID != nil && * device .UserID != 0 && * device .UserID != authedUserID {
481- c .JSON (http .StatusForbidden , gin.H {"error" : "无权操作此设备" })
482- return
483- }
484-
485- h .db .Model (& models.Device {}).Where ("device_id = ?" , deviceID ).Update ("logged_in" , false )
486-
487- h .notifySync (authedUserID , gin.H {
488- "type" : "device_logged_out" ,
489- "device_id" : deviceID ,
490- })
491-
492- c .JSON (http .StatusOK , gin.H {"message" : "设备登出状态已更新" })
493- }
0 commit comments