384 lines
10 KiB
Go
384 lines
10 KiB
Go
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
|
|
}
|
|
}
|