@@ -0,0 +1,278 @@
package com.lingniu.ingest.gateway ;
import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands ;
import com.lingniu.ingest.protocol.jt808.model.Jt808Message ;
import com.lingniu.ingest.protocol.jt1078.downlink.Jt1078Commands ;
import com.lingniu.ingest.session.CommandDispatcher ;
import com.lingniu.ingest.session.DeviceSession ;
import com.lingniu.ingest.session.SessionStore ;
import org.springframework.web.bind.annotation.DeleteMapping ;
import org.springframework.web.bind.annotation.GetMapping ;
import org.springframework.web.bind.annotation.PostMapping ;
import org.springframework.web.bind.annotation.PutMapping ;
import org.springframework.web.bind.annotation.RequestBody ;
import org.springframework.web.bind.annotation.RequestMapping ;
import org.springframework.web.bind.annotation.RequestParam ;
import org.springframework.web.bind.annotation.RestController ;
import java.nio.charset.StandardCharsets ;
import java.time.Duration ;
import java.util.List ;
import java.util.Map ;
import java.util.Optional ;
import java.util.concurrent.CompletableFuture ;
import java.util.concurrent.CompletionException ;
import java.util.stream.Collectors ;
/**
* 下行命令 REST 入口。命令通过 {@link CommandDispatcher} 发往协议模块,
* 由 protocol-jt808 的 {@code Jt808CommandDispatcher} 实际发送到终端。
*
* <p>路径参数 {@code clientId} 可以是 sessionId / phone / vin,
* 由 {@link SessionStore} 三索引解析。
*/
@RestController
@RequestMapping ( " /terminal " )
public class TerminalCommandController {
private static final Duration DEFAULT_TIMEOUT = Duration . ofSeconds ( 20 ) ;
private final SessionStore sessions ;
private final CommandDispatcher dispatcher ;
public TerminalCommandController ( SessionStore sessions , CommandDispatcher dispatcher ) {
this . sessions = sessions ;
this . dispatcher = dispatcher ;
}
@GetMapping ( " /session " )
public CommandResult session ( @RequestParam String clientId ) {
Optional < DeviceSession > s = resolveSession ( clientId ) ;
return s . < CommandResult > map ( CommandResult : : ok )
. orElseGet ( ( ) - > CommandResult . failure ( 404 , " session not found: " + clientId ) ) ;
}
@GetMapping ( " /location " )
public CommandResult queryLocation ( @RequestParam String clientId ) {
return awaitSync ( clientId , Jt808Commands . queryLocation ( ) , " query-location " ) ;
}
@GetMapping ( " /parameters " )
public CommandResult queryParameters ( @RequestParam String clientId ) {
return awaitSync ( clientId , Jt808Commands . queryParameters ( ) , " query-parameters " ) ;
}
@PutMapping ( " /parameters " )
public CommandResult setParameters ( @RequestParam String clientId ,
@RequestBody Map < String , String > body ) {
List < Jt808Commands . ParamItem > items = body . entrySet ( ) . stream ( )
. map ( e - > new Jt808Commands . ParamItem (
parseId ( e . getKey ( ) ) ,
e . getValue ( ) = = null ? new byte [ 0 ] : e . getValue ( ) . getBytes ( StandardCharsets . UTF_8 ) ) )
. collect ( Collectors . toList ( ) ) ;
return awaitSync ( clientId , Jt808Commands . setParameters ( items ) , " set-parameters " ) ;
}
@PostMapping ( " /control " )
public CommandResult terminalControl ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
int word = Integer . parseInt ( body . getOrDefault ( " command " , " 0 " ) . toString ( ) ) ;
String params = body . getOrDefault ( " params " , " " ) . toString ( ) ;
return awaitSync ( clientId , Jt808Commands . terminalControl ( word , params ) , " terminal-control " ) ;
}
@PostMapping ( " /ack " )
public CommandResult ack ( @RequestParam String clientId , @RequestBody Map < String , Integer > body ) {
int ackSerial = body . getOrDefault ( " ackSerial " , 0 ) ;
int ackMsgId = body . getOrDefault ( " ackMsgId " , 0 ) ;
int result = body . getOrDefault ( " result " , 0 ) ;
return fireAndForget ( clientId , Jt808Commands . platformAck ( ackSerial , ackMsgId , result ) , " platform-ack " ) ;
}
@GetMapping ( " /attributes " )
public CommandResult attributes ( @RequestParam String clientId ) {
return awaitSync ( clientId , Jt808Commands . queryAttributes ( ) , " query-attributes " ) ;
}
@DeleteMapping ( " /area " )
public CommandResult deleteArea ( @RequestParam String clientId , @RequestParam String ids ) {
return awaitSync ( clientId , Jt808Commands . deleteArea ( parseIds ( ids ) ) , " delete-area " ) ;
}
@PostMapping ( " /alarm_ack " )
public CommandResult alarmAck ( @RequestParam String clientId , @RequestBody Map < String , Object > body ) {
int responseSerialNo = Integer . parseInt ( body . getOrDefault ( " responseSerialNo " , " 0 " ) . toString ( ) ) ;
long type = parseLong ( body . getOrDefault ( " type " , " 0 " ) . toString ( ) ) ;
return fireAndForget ( clientId , Jt808Commands . alarmAck ( responseSerialNo , type ) , " alarm-ack " ) ;
}
@GetMapping ( " /jt1078/attributes " )
public CommandResult jt1078Attributes ( @RequestParam String clientId ) {
return awaitSync ( clientId , Jt1078Commands . queryMediaAttributes ( ) , " jt1078-query-media-attributes " ) ;
}
@PostMapping ( " /jt1078/realtime " )
public CommandResult jt1078RealtimePlay ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . realtimePlay (
stringValue ( body , " ip " , " " ) ,
intValue ( body , " tcpPort " , 0 ) ,
intValue ( body , " udpPort " , 0 ) ,
intValue ( body , " channelNo " , 0 ) ,
intValue ( body , " mediaType " , 0 ) ,
intValue ( body , " streamType " , 0 ) ) , " jt1078-realtime-play " ) ;
}
@PostMapping ( " /jt1078/realtime_control " )
public CommandResult jt1078RealtimeControl ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . realtimeControl (
intValue ( body , " channelNo " , 0 ) ,
intValue ( body , " command " , 0 ) ,
intValue ( body , " closeType " , 0 ) ,
intValue ( body , " streamType " , 0 ) ) , " jt1078-realtime-control " ) ;
}
@PostMapping ( " /jt1078/history " )
public CommandResult jt1078HistoryPlay ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . historyPlay (
stringValue ( body , " ip " , " " ) ,
intValue ( body , " tcpPort " , 0 ) ,
intValue ( body , " udpPort " , 0 ) ,
intValue ( body , " channelNo " , 0 ) ,
intValue ( body , " mediaType " , 0 ) ,
intValue ( body , " streamType " , 0 ) ,
intValue ( body , " storageType " , 0 ) ,
intValue ( body , " playbackMode " , 0 ) ,
intValue ( body , " playbackSpeed " , 0 ) ,
stringValue ( body , " startTime " , " " ) ,
stringValue ( body , " endTime " , " " ) ) , " jt1078-history-play " ) ;
}
@PostMapping ( " /jt1078/history_control " )
public CommandResult jt1078HistoryControl ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . historyControl (
intValue ( body , " channelNo " , 0 ) ,
intValue ( body , " playbackMode " , 0 ) ,
intValue ( body , " playbackSpeed " , 0 ) ,
stringValue ( body , " playbackTime " , " " ) ) , " jt1078-history-control " ) ;
}
@PostMapping ( " /jt1078/resource_search " )
public CommandResult jt1078ResourceSearch ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . resourceSearch (
intValue ( body , " channelNo " , 0 ) ,
stringValue ( body , " startTime " , " " ) ,
stringValue ( body , " endTime " , " " ) ,
longValue ( body , " warnBit1 " , 0 ) ,
longValue ( body , " warnBit2 " , 0 ) ,
intValue ( body , " mediaType " , 0 ) ,
intValue ( body , " streamType " , 0 ) ,
intValue ( body , " storageType " , 0 ) ) , " jt1078-resource-search " ) ;
}
@PostMapping ( " /jt1078/file_upload " )
public CommandResult jt1078FileUpload ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . fileUpload (
stringValue ( body , " ip " , " " ) ,
intValue ( body , " port " , 0 ) ,
stringValue ( body , " username " , " " ) ,
stringValue ( body , " password " , " " ) ,
stringValue ( body , " path " , " " ) ,
intValue ( body , " channelNo " , 0 ) ,
stringValue ( body , " startTime " , " " ) ,
stringValue ( body , " endTime " , " " ) ,
longValue ( body , " warnBit1 " , 0 ) ,
longValue ( body , " warnBit2 " , 0 ) ,
intValue ( body , " mediaType " , 0 ) ,
intValue ( body , " streamType " , 0 ) ,
intValue ( body , " storageType " , 0 ) ,
intValue ( body , " condition " , 0 ) ) , " jt1078-file-upload " ) ;
}
@PostMapping ( " /jt1078/file_upload_control " )
public CommandResult jt1078FileUploadControl ( @RequestParam String clientId ,
@RequestBody Map < String , Object > body ) {
return awaitSync ( clientId , Jt1078Commands . fileUploadControl (
intValue ( body , " responseSerialNo " , 0 ) ,
intValue ( body , " command " , 0 ) ) , " jt1078-file-upload-control " ) ;
}
// ===== helpers =====
private Optional < DeviceSession > resolveSession ( String clientId ) {
return sessions . findBySessionId ( clientId )
. or ( ( ) - > sessions . findByPhone ( clientId ) )
. or ( ( ) - > sessions . findByVin ( clientId ) ) ;
}
private CommandResult awaitSync ( String clientId , Jt808Commands . DownlinkCommand cmd , String op ) {
CompletableFuture < Jt808Message > future =
dispatcher . request ( clientId , cmd , Jt808Message . class , DEFAULT_TIMEOUT ) ;
try {
Jt808Message resp = future . join ( ) ;
return CommandResult . ok ( Map . of (
" messageId " , " 0x " + Integer . toHexString ( resp . header ( ) . messageId ( ) ) ,
" serial " , resp . header ( ) . serialNo ( ) ,
" phone " , resp . header ( ) . phone ( ) ) ) ;
} catch ( CompletionException e ) {
Throwable cause = e . getCause ( ) ! = null ? e . getCause ( ) : e ;
return CommandResult . failure ( 500 , op + " failed: " + cause . getMessage ( ) ) ;
} catch ( Exception e ) {
return CommandResult . failure ( 500 , op + " failed: " + e . getMessage ( ) ) ;
}
}
private CommandResult fireAndForget ( String clientId , Jt808Commands . DownlinkCommand cmd , String op ) {
try {
dispatcher . notify ( clientId , cmd ) . join ( ) ;
return CommandResult . ok ( Map . of ( " op " , op ) ) ;
} catch ( Exception e ) {
return CommandResult . failure ( 500 , op + " failed: " + e . getMessage ( ) ) ;
}
}
private static long parseId ( String key ) {
if ( key = = null | | key . isBlank ( ) ) return 0 ;
String k = key . startsWith ( " 0x " ) ? key . substring ( 2 ) : key ;
return Long . parseLong ( k , 16 ) ;
}
private static List < Long > parseIds ( String ids ) {
if ( ids = = null | | ids . isBlank ( ) ) {
return List . of ( ) ;
}
return java . util . Arrays . stream ( ids . split ( " , " ) )
. map ( String : : trim )
. filter ( s - > ! s . isBlank ( ) )
. map ( TerminalCommandController : : parseLong )
. toList ( ) ;
}
private static long parseLong ( String value ) {
if ( value = = null | | value . isBlank ( ) ) return 0 ;
String v = value . startsWith ( " 0x " ) | | value . startsWith ( " 0X " ) ? value . substring ( 2 ) : value ;
return value . startsWith ( " 0x " ) | | value . startsWith ( " 0X " )
? Long . parseLong ( v , 16 )
: Long . parseLong ( v ) ;
}
private static String stringValue ( Map < String , Object > body , String key , String defaultValue ) {
Object value = body . get ( key ) ;
return value = = null ? defaultValue : value . toString ( ) ;
}
private static int intValue ( Map < String , Object > body , String key , int defaultValue ) {
Object value = body . get ( key ) ;
return value = = null ? defaultValue : ( int ) parseLong ( value . toString ( ) ) ;
}
private static long longValue ( Map < String , Object > body , String key , long defaultValue ) {
Object value = body . get ( key ) ;
return value = = null ? defaultValue : parseLong ( value . toString ( ) ) ;
}
}