chore: 同步本地 v9 整改与运营能力
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"silk-server-go/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const defaultOrgCode = "default"
|
||||
|
||||
// currentUserRole 从 JWT context 中取当前角色。
|
||||
func currentUserRole(c *gin.Context) string {
|
||||
user, ok := c.Get("user")
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
m, ok := user.(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
role, _ := m["role"].(string)
|
||||
return role
|
||||
}
|
||||
|
||||
// hasGlobalAccess admin 拥有跨组织全部对象访问权。
|
||||
func hasGlobalAccess(c *gin.Context) bool {
|
||||
return currentUserRole(c) == model.RoleAdmin
|
||||
}
|
||||
|
||||
// applyOrgScope 对组织归属列(如 rooms.org_id)追加当前用户可见组织过滤。
|
||||
func applyOrgScope(q *gorm.DB, c *gin.Context, column string) *gorm.DB {
|
||||
if hasGlobalAccess(c) {
|
||||
return q
|
||||
}
|
||||
userID := currentUserID(c)
|
||||
if userID == nil {
|
||||
return q.Where("1 = 0")
|
||||
}
|
||||
return q.Where("CAST("+column+" AS text) IN (SELECT organization_id::text FROM organization_members WHERE user_id = ?)", *userID)
|
||||
}
|
||||
|
||||
// applyRoomScope 对业务记录的房间列追加当前用户可见房间过滤。
|
||||
func applyRoomScope(q *gorm.DB, c *gin.Context, column string) *gorm.DB {
|
||||
if hasGlobalAccess(c) {
|
||||
return q
|
||||
}
|
||||
userID := currentUserID(c)
|
||||
if userID == nil {
|
||||
return q.Where("1 = 0")
|
||||
}
|
||||
return q.Where("CAST("+column+" AS text) IN ("+
|
||||
"SELECT r.id::text FROM rooms r "+
|
||||
"JOIN organization_members om ON om.organization_id = r.org_id "+
|
||||
"WHERE om.user_id = ?)", *userID)
|
||||
}
|
||||
|
||||
// canAccessOrganization 判断当前用户是否属于指定组织。
|
||||
func canAccessOrganization(db *gorm.DB, c *gin.Context, orgID string) bool {
|
||||
if hasGlobalAccess(c) {
|
||||
return true
|
||||
}
|
||||
userID := currentUserID(c)
|
||||
if userID == nil {
|
||||
return false
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.OrganizationMember{}).
|
||||
Where("organization_id = ? AND user_id = ?", orgID, *userID).
|
||||
Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// canAccessRoom 判断当前用户是否能访问指定房间。
|
||||
func canAccessRoom(db *gorm.DB, c *gin.Context, roomID *string) bool {
|
||||
if roomID == nil || *roomID == "" {
|
||||
return false
|
||||
}
|
||||
if hasGlobalAccess(c) {
|
||||
return true
|
||||
}
|
||||
userID := currentUserID(c)
|
||||
if userID == nil {
|
||||
return false
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.Room{}).
|
||||
Joins("JOIN organization_members om ON om.organization_id = rooms.org_id").
|
||||
Where("rooms.id = ? AND om.user_id = ?", *roomID, *userID).
|
||||
Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// canAccessDeviceKey 判断当前用户是否能访问指定 deviceKey 对应的设备。
|
||||
func canAccessDeviceKey(db *gorm.DB, c *gin.Context, deviceKey string) bool {
|
||||
if hasGlobalAccess(c) {
|
||||
return true
|
||||
}
|
||||
if deviceKey == "" {
|
||||
return false
|
||||
}
|
||||
if currentUserID(c) == nil {
|
||||
return false
|
||||
}
|
||||
var device model.Device
|
||||
if db.Where("device_key = ?", deviceKey).First(&device).Error != nil {
|
||||
return false
|
||||
}
|
||||
return canAccessRoom(db, c, &device.RoomID)
|
||||
}
|
||||
|
||||
// requireDeviceKeyAccess 统一校验 deviceKey 对象授权;无权返回 403。
|
||||
func requireDeviceKeyAccess(c *gin.Context, db *gorm.DB, deviceKey string) bool {
|
||||
if canAccessDeviceKey(db, c, deviceKey) {
|
||||
return true
|
||||
}
|
||||
if deviceKey == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "deviceKey 不能为空"})
|
||||
return false
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.Device{}).Where("device_key = ?", deviceKey).Count(&count)
|
||||
if count == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
|
||||
return false
|
||||
}
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该设备"})
|
||||
return false
|
||||
}
|
||||
|
||||
// requireObjectAccess 统一对象授权检查;无权返回 403。
|
||||
func requireObjectAccess(c *gin.Context, db *gorm.DB, entityType, id string) bool {
|
||||
if hasGlobalAccess(c) {
|
||||
return true
|
||||
}
|
||||
roomID, ok := objectRoomID(db, entityType, id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": entityType + " not found"})
|
||||
return false
|
||||
}
|
||||
if !canAccessRoom(db, c, roomID) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该对象"})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// objectRoomID 返回实体归属房间;实体不存在时返回 false。
|
||||
func objectRoomID(db *gorm.DB, entityType, id string) (*string, bool) {
|
||||
switch entityType {
|
||||
case "room":
|
||||
var room model.Room
|
||||
if db.Where("id = ?", id).First(&room).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &room.ID, true
|
||||
case "device":
|
||||
var device model.Device
|
||||
if db.Where("id = ?", id).First(&device).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &device.RoomID, true
|
||||
case "sensor":
|
||||
var sensor model.Sensor
|
||||
if db.Where("id = ?", id).First(&sensor).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
var device model.Device
|
||||
if db.Where("id = ?", sensor.DeviceID).First(&device).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &device.RoomID, true
|
||||
case "threshold":
|
||||
var threshold model.Threshold
|
||||
if db.Where("id = ?", id).First(&threshold).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
var sensor model.Sensor
|
||||
if db.Where("id = ?", threshold.SensorID).First(&sensor).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
var device model.Device
|
||||
if db.Where("id = ?", sensor.DeviceID).First(&device).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &device.RoomID, true
|
||||
case "alarm":
|
||||
var alarm model.Alarm
|
||||
if db.Where("id = ?", id).First(&alarm).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
if alarm.DeviceKey == nil || *alarm.DeviceKey == "" {
|
||||
return nil, false
|
||||
}
|
||||
var device model.Device
|
||||
if db.Where("device_key = ?", *alarm.DeviceKey).First(&device).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &device.RoomID, true
|
||||
case "device_maintenance_record":
|
||||
var rec model.DeviceMaintenanceRecord
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
var device model.Device
|
||||
if db.Where("id = ?", rec.DeviceID).First(&device).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &device.RoomID, true
|
||||
case "camera":
|
||||
var camera model.Camera
|
||||
if db.Where("id = ?", id).First(&camera).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return camera.RoomID, true
|
||||
case "video_clip":
|
||||
var clip model.VideoClip
|
||||
if db.Where("id = ?", id).First(&clip).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return clip.RoomID, true
|
||||
case "tray":
|
||||
var tray model.Tray
|
||||
if db.Where("id = ?", id).First(&tray).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &tray.RoomID, true
|
||||
case "batch":
|
||||
var batch model.Batch
|
||||
if db.Where("id = ?", id).First(&batch).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &batch.RoomID, true
|
||||
case "rearing_record":
|
||||
var rec model.RearingRecord
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return batchRoomID(db, &rec.BatchID)
|
||||
case "inspection":
|
||||
var rec model.InspectionRecord
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "lamp_test":
|
||||
var rec model.LampTest
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "detection_task":
|
||||
var rec model.DetectionTask
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "consultation":
|
||||
var rec model.Consultation
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "trace_record":
|
||||
var rec model.TraceRecord
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "disease_event":
|
||||
var rec model.DiseaseEvent
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "sample":
|
||||
var rec model.Sample
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return rec.RoomID, true
|
||||
case "seed_source":
|
||||
var rec model.SeedSource
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return batchRoomID(db, rec.BatchID)
|
||||
case "disinfection_record":
|
||||
var rec model.DisinfectionRecord
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
if rec.RoomID != nil {
|
||||
return rec.RoomID, true
|
||||
}
|
||||
return batchRoomID(db, rec.BatchID)
|
||||
case "production_loss_record":
|
||||
var rec model.ProductionLossRecord
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
if rec.RoomID != nil {
|
||||
return rec.RoomID, true
|
||||
}
|
||||
return batchRoomID(db, rec.BatchID)
|
||||
case "laboratory_result":
|
||||
var rec model.LaboratoryResult
|
||||
if db.Where("id = ?", id).First(&rec).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
if rec.RoomID != nil {
|
||||
return rec.RoomID, true
|
||||
}
|
||||
if rec.BatchID != nil {
|
||||
return batchRoomID(db, rec.BatchID)
|
||||
}
|
||||
if rec.TraceRecordID != nil {
|
||||
var trace model.TraceRecord
|
||||
if db.Where("id = ?", *rec.TraceRecordID).First(&trace).Error == nil {
|
||||
return trace.RoomID, true
|
||||
}
|
||||
}
|
||||
return nil, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func batchRoomID(db *gorm.DB, batchID *string) (*string, bool) {
|
||||
if batchID == nil {
|
||||
return nil, false
|
||||
}
|
||||
var batch model.Batch
|
||||
if db.Where("id = ?", *batchID).First(&batch).Error != nil {
|
||||
return nil, false
|
||||
}
|
||||
return &batch.RoomID, true
|
||||
}
|
||||
|
||||
// defaultOrgID 返回当前用户第一个组织;管理员未指定时返回默认组织。
|
||||
func defaultOrgID(db *gorm.DB, c *gin.Context) *string {
|
||||
if userID := currentUserID(c); userID != nil {
|
||||
var member model.OrganizationMember
|
||||
if db.Where("user_id = ?", *userID).Order("created_at ASC").First(&member).Error == nil {
|
||||
return &member.OrganizationID
|
||||
}
|
||||
}
|
||||
var org model.Organization
|
||||
if db.Where("code = ?", defaultOrgCode).First(&org).Error == nil {
|
||||
return &org.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// firstAccessibleRoomID 返回当前用户可见的第一个房间;用于无显式 roomId 的历史创建流程。
|
||||
func firstAccessibleRoomID(db *gorm.DB, c *gin.Context) *string {
|
||||
var room model.Room
|
||||
q := db.Model(&model.Room{})
|
||||
if !hasGlobalAccess(c) {
|
||||
q = applyOrgScope(q, c, "org_id")
|
||||
}
|
||||
if q.Order("created_at ASC").First(&room).Error == nil {
|
||||
return &room.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// assignDefaultOrganization 注册新用户时补齐默认组织成员关系。
|
||||
func assignDefaultOrganization(db *gorm.DB, userID string) {
|
||||
var org model.Organization
|
||||
if db.Where("code = ?", defaultOrgCode).First(&org).Error != nil {
|
||||
return
|
||||
}
|
||||
var count int64
|
||||
db.Model(&model.OrganizationMember{}).
|
||||
Where("organization_id = ? AND user_id = ?", org.ID, userID).
|
||||
Count(&count)
|
||||
if count == 0 {
|
||||
_ = db.Create(&model.OrganizationMember{OrganizationID: org.ID, UserID: userID, Role: "member"}).Error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user