From c074adb3a96bec2cf8aed345cc4d06eb9870e552 Mon Sep 17 00:00:00 2001 From: tongque <2863528786@qq.com> Date: Tue, 22 Apr 2025 12:36:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20gRPC=20=E5=92=8C?= =?UTF-8?q?=20HTTP=20=E7=BD=91=E5=85=B3=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Common/Output.go | 72 +++++++ RPC/buf.gen.yaml | 13 ++ RPC/buf.lock | 8 + RPC/buf.yaml | 3 + RPC/lib/{fscan.pb.go => rpc.pb.go} | 209 ++++++++++++------- RPC/lib/{fscan.pb.gw.go => rpc.pb.gw.go} | 62 +----- RPC/lib/rpc.proto | 25 ++- RPC/lib/{fscan_grpc.pb.go => rpc_grpc.pb.go} | 54 +---- RPC/server.go | 23 +- RPC/service/fscan.go | 94 ++++----- 11 files changed, 314 insertions(+), 250 deletions(-) create mode 100644 RPC/buf.gen.yaml create mode 100644 RPC/buf.lock create mode 100644 RPC/buf.yaml rename RPC/lib/{fscan.pb.go => rpc.pb.go} (61%) rename RPC/lib/{fscan.pb.gw.go => rpc.pb.gw.go} (75%) rename RPC/lib/{fscan_grpc.pb.go => rpc_grpc.pb.go} (71%) diff --git a/.gitignore b/.gitignore index 4c6aeee..937e9e1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ main fscan.exe fscan makefile +fscanapi.csv diff --git a/Common/Output.go b/Common/Output.go index b20dda5..f5cf206 100644 --- a/Common/Output.go +++ b/Common/Output.go @@ -67,6 +67,11 @@ func InitOutput() error { return fmt.Errorf(GetText("output_create_dir_failed", err)) } + if ApiAddr != "" { + OutputFormat = "csv" + Outputfile = filepath.Join(dir, "fscanapi.csv") + } + manager := &OutputManager{ outputPath: Outputfile, outputFormat: OutputFormat, @@ -135,6 +140,17 @@ func SaveResult(result *ScanResult) error { LogDebug(GetText("output_saving_result", result.Type, result.Target)) return ResultOutput.saveResult(result) } +func GetResults() ([]*ScanResult, error) { + if ResultOutput == nil { + return nil, fmt.Errorf(GetText("output_not_init")) + } + + if ResultOutput.outputFormat == "csv" { + return ResultOutput.getResult() + } + // 其他格式尚未实现读取支持 + return nil, fmt.Errorf(GetText("output_format_read_not_supported")) +} func (om *OutputManager) saveResult(result *ScanResult) error { om.mu.Lock() @@ -165,6 +181,62 @@ func (om *OutputManager) saveResult(result *ScanResult) error { } return err } +func (om *OutputManager) getResult() ([]*ScanResult, error) { + om.mu.Lock() + defer om.mu.Unlock() + + if !om.isInitialized { + LogDebug(GetText("output_not_init")) + return nil, fmt.Errorf(GetText("output_not_init")) + } + + file, err := os.Open(om.outputPath) + if err != nil { + LogDebug(GetText("output_open_file_failed", err)) + return nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + LogDebug(GetText("output_read_csv_failed", err)) + return nil, err + } + + var results []*ScanResult + for i, row := range records { + // 跳过 CSV 头部 + if i == 0 { + continue + } + if len(row) < 5 { + continue // 数据不完整 + } + + t, err := time.Parse("2006-01-02 15:04:05", row[0]) + if err != nil { + continue + } + + var details map[string]interface{} + if err := json.Unmarshal([]byte(row[4]), &details); err != nil { + details = make(map[string]interface{}) + } + + result := &ScanResult{ + Time: t, + Type: ResultType(row[1]), + Target: row[2], + Status: row[3], + Details: details, + } + results = append(results, result) + } + + LogDebug(GetText("output_read_csv_success", len(results))) + return results, nil +} func (om *OutputManager) writeTxt(result *ScanResult) error { // 格式化 Details 为键值对字符串 diff --git a/RPC/buf.gen.yaml b/RPC/buf.gen.yaml new file mode 100644 index 0000000..f40d639 --- /dev/null +++ b/RPC/buf.gen.yaml @@ -0,0 +1,13 @@ +version: v1 +plugins: + - plugin: go + out: lib + opt: paths=import + + - plugin: go-grpc + out: lib + opt: paths=import + + - plugin: grpc-gateway + out: lib + opt: paths=import diff --git a/RPC/buf.lock b/RPC/buf.lock new file mode 100644 index 0000000..2ca2765 --- /dev/null +++ b/RPC/buf.lock @@ -0,0 +1,8 @@ +# Generated by buf. DO NOT EDIT. +version: v1 +deps: + - remote: buf.build + owner: googleapis + repository: googleapis + commit: 61b203b9a9164be9a834f58c37be6f62 + digest: shake256:e619113001d6e284ee8a92b1561e5d4ea89a47b28bf0410815cb2fa23914df8be9f1a6a98dcf069f5bc2d829a2cfb1ac614863be45cd4f8a5ad8606c5f200224 diff --git a/RPC/buf.yaml b/RPC/buf.yaml new file mode 100644 index 0000000..87f59f8 --- /dev/null +++ b/RPC/buf.yaml @@ -0,0 +1,3 @@ +version: v1 +deps: + - buf.build/googleapis/googleapis diff --git a/RPC/lib/fscan.pb.go b/RPC/lib/rpc.pb.go similarity index 61% rename from RPC/lib/fscan.pb.go rename to RPC/lib/rpc.pb.go index 53917df..ddc7bc1 100644 --- a/RPC/lib/fscan.pb.go +++ b/RPC/lib/rpc.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.6 // protoc (unknown) -// source: proto/fscan.proto +// source: lib/rpc.proto package lib @@ -10,6 +10,7 @@ import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -33,7 +34,7 @@ type StartScanRequest struct { func (x *StartScanRequest) Reset() { *x = StartScanRequest{} - mi := &file_proto_fscan_proto_msgTypes[0] + mi := &file_lib_rpc_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -45,7 +46,7 @@ func (x *StartScanRequest) String() string { func (*StartScanRequest) ProtoMessage() {} func (x *StartScanRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_fscan_proto_msgTypes[0] + mi := &file_lib_rpc_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -58,7 +59,7 @@ func (x *StartScanRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartScanRequest.ProtoReflect.Descriptor instead. func (*StartScanRequest) Descriptor() ([]byte, []int) { - return file_proto_fscan_proto_rawDescGZIP(), []int{0} + return file_lib_rpc_proto_rawDescGZIP(), []int{0} } func (x *StartScanRequest) GetSecret() string { @@ -86,7 +87,7 @@ type StartScanResponse struct { func (x *StartScanResponse) Reset() { *x = StartScanResponse{} - mi := &file_proto_fscan_proto_msgTypes[1] + mi := &file_lib_rpc_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -98,7 +99,7 @@ func (x *StartScanResponse) String() string { func (*StartScanResponse) ProtoMessage() {} func (x *StartScanResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_fscan_proto_msgTypes[1] + mi := &file_lib_rpc_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -111,7 +112,7 @@ func (x *StartScanResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartScanResponse.ProtoReflect.Descriptor instead. func (*StartScanResponse) Descriptor() ([]byte, []int) { - return file_proto_fscan_proto_rawDescGZIP(), []int{1} + return file_lib_rpc_proto_rawDescGZIP(), []int{1} } func (x *StartScanResponse) GetTaskId() string { @@ -131,15 +132,15 @@ func (x *StartScanResponse) GetMessage() string { // 获取扫描结果的请求 type TaskResultsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` - Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Secret string `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` // 用于身份校验 + Filter *Filter `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` // 筛选条件(如关键字、状态等) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *TaskResultsRequest) Reset() { *x = TaskResultsRequest{} - mi := &file_proto_fscan_proto_msgTypes[2] + mi := &file_lib_rpc_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -151,7 +152,7 @@ func (x *TaskResultsRequest) String() string { func (*TaskResultsRequest) ProtoMessage() {} func (x *TaskResultsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_fscan_proto_msgTypes[2] + mi := &file_lib_rpc_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -164,21 +165,81 @@ func (x *TaskResultsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskResultsRequest.ProtoReflect.Descriptor instead. func (*TaskResultsRequest) Descriptor() ([]byte, []int) { - return file_proto_fscan_proto_rawDescGZIP(), []int{2} + return file_lib_rpc_proto_rawDescGZIP(), []int{2} } -func (x *TaskResultsRequest) GetTaskId() string { +func (x *TaskResultsRequest) GetSecret() string { + if x != nil { + return x.Secret + } + return "" +} + +func (x *TaskResultsRequest) GetFilter() *Filter { + if x != nil { + return x.Filter + } + return nil +} + +type Filter struct { + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` // 任务ID + StartTime string `protobuf:"bytes,2,opt,name=Start_time,json=StartTime,proto3" json:"Start_time,omitempty"` // 开始时间 + EndTime string `protobuf:"bytes,3,opt,name=End_time,json=EndTime,proto3" json:"End_time,omitempty"` // 结束时间 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Filter) Reset() { + *x = Filter{} + mi := &file_lib_rpc_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Filter) ProtoMessage() {} + +func (x *Filter) ProtoReflect() protoreflect.Message { + mi := &file_lib_rpc_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Filter.ProtoReflect.Descriptor instead. +func (*Filter) Descriptor() ([]byte, []int) { + return file_lib_rpc_proto_rawDescGZIP(), []int{3} +} + +func (x *Filter) GetTaskId() string { if x != nil { return x.TaskId } return "" } -func (x *TaskResultsRequest) GetOffset() uint32 { +func (x *Filter) GetStartTime() string { if x != nil { - return x.Offset + return x.StartTime } - return 0 + return "" +} + +func (x *Filter) GetEndTime() string { + if x != nil { + return x.EndTime + } + return "" } // 获取扫描结果的响应 @@ -193,7 +254,7 @@ type TaskResultsResponse struct { func (x *TaskResultsResponse) Reset() { *x = TaskResultsResponse{} - mi := &file_proto_fscan_proto_msgTypes[3] + mi := &file_lib_rpc_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -205,7 +266,7 @@ func (x *TaskResultsResponse) String() string { func (*TaskResultsResponse) ProtoMessage() {} func (x *TaskResultsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_fscan_proto_msgTypes[3] + mi := &file_lib_rpc_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -218,7 +279,7 @@ func (x *TaskResultsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskResultsResponse.ProtoReflect.Descriptor instead. func (*TaskResultsResponse) Descriptor() ([]byte, []int) { - return file_proto_fscan_proto_rawDescGZIP(), []int{3} + return file_lib_rpc_proto_rawDescGZIP(), []int{4} } func (x *TaskResultsResponse) GetTaskId() string { @@ -249,14 +310,14 @@ type ScanResult struct { Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` - DetailsJson string `protobuf:"bytes,5,opt,name=details_json,json=detailsJson,proto3" json:"details_json,omitempty"` + DetailsJson *structpb.Struct `protobuf:"bytes,5,opt,name=details_json,json=detailsJson,proto3" json:"details_json,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ScanResult) Reset() { *x = ScanResult{} - mi := &file_proto_fscan_proto_msgTypes[4] + mi := &file_lib_rpc_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -268,7 +329,7 @@ func (x *ScanResult) String() string { func (*ScanResult) ProtoMessage() {} func (x *ScanResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_fscan_proto_msgTypes[4] + mi := &file_lib_rpc_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -281,7 +342,7 @@ func (x *ScanResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ScanResult.ProtoReflect.Descriptor instead. func (*ScanResult) Descriptor() ([]byte, []int) { - return file_proto_fscan_proto_rawDescGZIP(), []int{4} + return file_lib_rpc_proto_rawDescGZIP(), []int{5} } func (x *ScanResult) GetTime() string { @@ -312,98 +373,104 @@ func (x *ScanResult) GetStatus() string { return "" } -func (x *ScanResult) GetDetailsJson() string { +func (x *ScanResult) GetDetailsJson() *structpb.Struct { if x != nil { return x.DetailsJson } - return "" + return nil } -var File_proto_fscan_proto protoreflect.FileDescriptor +var File_lib_rpc_proto protoreflect.FileDescriptor -const file_proto_fscan_proto_rawDesc = "" + +const file_lib_rpc_proto_rawDesc = "" + "\n" + - "\x11proto/fscan.proto\x12\x03lib\x1a\x1cgoogle/api/annotations.proto\"<\n" + + "\rlib/rpc.proto\x12\x03lib\x1a\x1cgoogle/api/annotations.proto\x1a\x1cgoogle/protobuf/struct.proto\"<\n" + "\x10StartScanRequest\x12\x16\n" + "\x06secret\x18\x01 \x01(\tR\x06secret\x12\x10\n" + "\x03arg\x18\x02 \x01(\tR\x03arg\"F\n" + "\x11StartScanResponse\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage\"E\n" + - "\x12TaskResultsRequest\x12\x17\n" + - "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x16\n" + - "\x06offset\x18\x02 \x01(\rR\x06offset\"u\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"Q\n" + + "\x12TaskResultsRequest\x12\x16\n" + + "\x06secret\x18\x01 \x01(\tR\x06secret\x12#\n" + + "\x06filter\x18\x02 \x01(\v2\v.lib.FilterR\x06filter\"[\n" + + "\x06Filter\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x1d\n" + + "\n" + + "Start_time\x18\x02 \x01(\tR\tStartTime\x12\x19\n" + + "\bEnd_time\x18\x03 \x01(\tR\aEndTime\"u\n" + "\x13TaskResultsResponse\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12)\n" + "\aresults\x18\x02 \x03(\v2\x0f.lib.ScanResultR\aresults\x12\x1a\n" + - "\bfinished\x18\x03 \x01(\bR\bfinished\"\x87\x01\n" + + "\bfinished\x18\x03 \x01(\bR\bfinished\"\xa0\x01\n" + "\n" + "ScanResult\x12\x12\n" + "\x04time\x18\x01 \x01(\tR\x04time\x12\x12\n" + "\x04type\x18\x02 \x01(\tR\x04type\x12\x16\n" + "\x06target\x18\x03 \x01(\tR\x06target\x12\x16\n" + - "\x06status\x18\x04 \x01(\tR\x06status\x12!\n" + - "\fdetails_json\x18\x05 \x01(\tR\vdetailsJson2\xa0\x02\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12:\n" + + "\fdetails_json\x18\x05 \x01(\v2\x17.google.protobuf.StructR\vdetailsJson2\xc4\x01\n" + "\fFscanService\x12T\n" + "\tStartScan\x12\x15.lib.StartScanRequest\x1a\x16.lib.StartScanResponse\"\x18\x82\xd3\xe4\x93\x02\x12:\x01*\"\r/v1/startscan\x12^\n" + - "\x0eGetScanResults\x12\x17.lib.TaskResultsRequest\x1a\x18.lib.TaskResultsResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\"\x0e/v1/getresults\x12Z\n" + - "\x11StreamScanResults\x12\x17.lib.TaskResultsRequest\x1a\x0f.lib.ScanResult\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/v1/streamresults0\x01B\x1eZ\x1cgithub.com/shadow1ng/RPC;libb\x06proto3" + "\x0eGetScanResults\x12\x17.lib.TaskResultsRequest\x1a\x18.lib.TaskResultsResponse\"\x19\x82\xd3\xe4\x93\x02\x13:\x01*\"\x0e/v1/getresultsB\bZ\x06./;libb\x06proto3" var ( - file_proto_fscan_proto_rawDescOnce sync.Once - file_proto_fscan_proto_rawDescData []byte + file_lib_rpc_proto_rawDescOnce sync.Once + file_lib_rpc_proto_rawDescData []byte ) -func file_proto_fscan_proto_rawDescGZIP() []byte { - file_proto_fscan_proto_rawDescOnce.Do(func() { - file_proto_fscan_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_fscan_proto_rawDesc), len(file_proto_fscan_proto_rawDesc))) +func file_lib_rpc_proto_rawDescGZIP() []byte { + file_lib_rpc_proto_rawDescOnce.Do(func() { + file_lib_rpc_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_lib_rpc_proto_rawDesc), len(file_lib_rpc_proto_rawDesc))) }) - return file_proto_fscan_proto_rawDescData + return file_lib_rpc_proto_rawDescData } -var file_proto_fscan_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_proto_fscan_proto_goTypes = []any{ +var file_lib_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_lib_rpc_proto_goTypes = []any{ (*StartScanRequest)(nil), // 0: lib.StartScanRequest (*StartScanResponse)(nil), // 1: lib.StartScanResponse (*TaskResultsRequest)(nil), // 2: lib.TaskResultsRequest - (*TaskResultsResponse)(nil), // 3: lib.TaskResultsResponse - (*ScanResult)(nil), // 4: lib.ScanResult + (*Filter)(nil), // 3: lib.Filter + (*TaskResultsResponse)(nil), // 4: lib.TaskResultsResponse + (*ScanResult)(nil), // 5: lib.ScanResult + (*structpb.Struct)(nil), // 6: google.protobuf.Struct } -var file_proto_fscan_proto_depIdxs = []int32{ - 4, // 0: lib.TaskResultsResponse.results:type_name -> lib.ScanResult - 0, // 1: lib.FscanService.StartScan:input_type -> lib.StartScanRequest - 2, // 2: lib.FscanService.GetScanResults:input_type -> lib.TaskResultsRequest - 2, // 3: lib.FscanService.StreamScanResults:input_type -> lib.TaskResultsRequest - 1, // 4: lib.FscanService.StartScan:output_type -> lib.StartScanResponse - 3, // 5: lib.FscanService.GetScanResults:output_type -> lib.TaskResultsResponse - 4, // 6: lib.FscanService.StreamScanResults:output_type -> lib.ScanResult - 4, // [4:7] is the sub-list for method output_type - 1, // [1:4] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name +var file_lib_rpc_proto_depIdxs = []int32{ + 3, // 0: lib.TaskResultsRequest.filter:type_name -> lib.Filter + 5, // 1: lib.TaskResultsResponse.results:type_name -> lib.ScanResult + 6, // 2: lib.ScanResult.details_json:type_name -> google.protobuf.Struct + 0, // 3: lib.FscanService.StartScan:input_type -> lib.StartScanRequest + 2, // 4: lib.FscanService.GetScanResults:input_type -> lib.TaskResultsRequest + 1, // 5: lib.FscanService.StartScan:output_type -> lib.StartScanResponse + 4, // 6: lib.FscanService.GetScanResults:output_type -> lib.TaskResultsResponse + 5, // [5:7] is the sub-list for method output_type + 3, // [3:5] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } -func init() { file_proto_fscan_proto_init() } -func file_proto_fscan_proto_init() { - if File_proto_fscan_proto != nil { +func init() { file_lib_rpc_proto_init() } +func file_lib_rpc_proto_init() { + if File_lib_rpc_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_fscan_proto_rawDesc), len(file_proto_fscan_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_lib_rpc_proto_rawDesc), len(file_lib_rpc_proto_rawDesc)), NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_proto_fscan_proto_goTypes, - DependencyIndexes: file_proto_fscan_proto_depIdxs, - MessageInfos: file_proto_fscan_proto_msgTypes, + GoTypes: file_lib_rpc_proto_goTypes, + DependencyIndexes: file_lib_rpc_proto_depIdxs, + MessageInfos: file_lib_rpc_proto_msgTypes, }.Build() - File_proto_fscan_proto = out.File - file_proto_fscan_proto_goTypes = nil - file_proto_fscan_proto_depIdxs = nil + File_lib_rpc_proto = out.File + file_lib_rpc_proto_goTypes = nil + file_lib_rpc_proto_depIdxs = nil } diff --git a/RPC/lib/fscan.pb.gw.go b/RPC/lib/rpc.pb.gw.go similarity index 75% rename from RPC/lib/fscan.pb.gw.go rename to RPC/lib/rpc.pb.gw.go index 5d64ea4..5389a96 100644 --- a/RPC/lib/fscan.pb.gw.go +++ b/RPC/lib/rpc.pb.gw.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. -// source: proto/fscan.proto +// source: lib/rpc.proto /* Package lib is a reverse proxy. @@ -83,32 +83,6 @@ func local_request_FscanService_GetScanResults_0(ctx context.Context, marshaler return msg, metadata, err } -var filter_FscanService_StreamScanResults_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} - -func request_FscanService_StreamScanResults_0(ctx context.Context, marshaler runtime.Marshaler, client FscanServiceClient, req *http.Request, pathParams map[string]string) (FscanService_StreamScanResultsClient, runtime.ServerMetadata, error) { - var ( - protoReq TaskResultsRequest - metadata runtime.ServerMetadata - ) - io.Copy(io.Discard, req.Body) - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_FscanService_StreamScanResults_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - stream, err := client.StreamScanResults(ctx, &protoReq) - if err != nil { - return nil, metadata, err - } - header, err := stream.Header() - if err != nil { - return nil, metadata, err - } - metadata.HeaderMD = header - return stream, metadata, nil -} - // RegisterFscanServiceHandlerServer registers the http handlers for service FscanService to "mux". // UnaryRPC :call FscanServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -156,13 +130,6 @@ func RegisterFscanServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu forward_FscanService_GetScanResults_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_FscanService_StreamScanResults_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") - _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - }) - return nil } @@ -236,34 +203,15 @@ func RegisterFscanServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu } forward_FscanService_GetScanResults_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_FscanService_StreamScanResults_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/lib.FscanService/StreamScanResults", runtime.WithHTTPPathPattern("/v1/streamresults")) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_FscanService_StreamScanResults_0(annotatedContext, inboundMarshaler, client, req, pathParams) - annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) - if err != nil { - runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) - return - } - forward_FscanService_StreamScanResults_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) - }) return nil } var ( - pattern_FscanService_StartScan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startscan"}, "")) - pattern_FscanService_GetScanResults_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getresults"}, "")) - pattern_FscanService_StreamScanResults_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "streamresults"}, "")) + pattern_FscanService_StartScan_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startscan"}, "")) + pattern_FscanService_GetScanResults_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getresults"}, "")) ) var ( - forward_FscanService_StartScan_0 = runtime.ForwardResponseMessage - forward_FscanService_GetScanResults_0 = runtime.ForwardResponseMessage - forward_FscanService_StreamScanResults_0 = runtime.ForwardResponseStream + forward_FscanService_StartScan_0 = runtime.ForwardResponseMessage + forward_FscanService_GetScanResults_0 = runtime.ForwardResponseMessage ) diff --git a/RPC/lib/rpc.proto b/RPC/lib/rpc.proto index 728a567..cd8a81a 100644 --- a/RPC/lib/rpc.proto +++ b/RPC/lib/rpc.proto @@ -5,6 +5,7 @@ package lib; option go_package = "./;lib"; import "google/api/annotations.proto"; +import "google/protobuf/struct.proto"; service FscanService { // 启动扫描任务 @@ -23,12 +24,12 @@ service FscanService { }; } - // 获取扫描结果(流式) - rpc StreamScanResults(TaskResultsRequest) returns (stream ScanResult) { - option (google.api.http) = { - get: "/v1/streamresults" - }; - } + // TODO: 流式获取扫描结果 + // rpc StreamScanResults(TaskResultsRequest) returns (stream ScanResult) { + // option (google.api.http) = { + // get: "/v1/streamresults" + // }; + // } } // 启动任务的请求 @@ -45,8 +46,14 @@ message StartScanResponse { // 获取扫描结果的请求 message TaskResultsRequest { - string task_id = 1; - uint32 offset = 2; + string secret = 1; // 用于身份校验 + Filter filter = 2; // 筛选条件(如关键字、状态等) +} + +message Filter { + string task_id = 1; // 任务ID + string Start_time = 2; // 开始时间 + string End_time = 3; // 结束时间 } // 获取扫描结果的响应 @@ -62,5 +69,5 @@ message ScanResult { string type = 2; string target = 3; string status = 4; - string details_json = 5; + google.protobuf.Struct details_json = 5; } diff --git a/RPC/lib/fscan_grpc.pb.go b/RPC/lib/rpc_grpc.pb.go similarity index 71% rename from RPC/lib/fscan_grpc.pb.go rename to RPC/lib/rpc_grpc.pb.go index c2ca790..79588b1 100644 --- a/RPC/lib/fscan_grpc.pb.go +++ b/RPC/lib/rpc_grpc.pb.go @@ -2,7 +2,7 @@ // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc (unknown) -// source: proto/fscan.proto +// source: lib/rpc.proto package lib @@ -19,9 +19,8 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - FscanService_StartScan_FullMethodName = "/lib.FscanService/StartScan" - FscanService_GetScanResults_FullMethodName = "/lib.FscanService/GetScanResults" - FscanService_StreamScanResults_FullMethodName = "/lib.FscanService/StreamScanResults" + FscanService_StartScan_FullMethodName = "/lib.FscanService/StartScan" + FscanService_GetScanResults_FullMethodName = "/lib.FscanService/GetScanResults" ) // FscanServiceClient is the client API for FscanService service. @@ -32,8 +31,6 @@ type FscanServiceClient interface { StartScan(ctx context.Context, in *StartScanRequest, opts ...grpc.CallOption) (*StartScanResponse, error) // 获取扫描结果(非流式) GetScanResults(ctx context.Context, in *TaskResultsRequest, opts ...grpc.CallOption) (*TaskResultsResponse, error) - // 获取扫描结果(流式) - StreamScanResults(ctx context.Context, in *TaskResultsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ScanResult], error) } type fscanServiceClient struct { @@ -64,25 +61,6 @@ func (c *fscanServiceClient) GetScanResults(ctx context.Context, in *TaskResults return out, nil } -func (c *fscanServiceClient) StreamScanResults(ctx context.Context, in *TaskResultsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ScanResult], error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &FscanService_ServiceDesc.Streams[0], FscanService_StreamScanResults_FullMethodName, cOpts...) - if err != nil { - return nil, err - } - x := &grpc.GenericClientStream[TaskResultsRequest, ScanResult]{ClientStream: stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type FscanService_StreamScanResultsClient = grpc.ServerStreamingClient[ScanResult] - // FscanServiceServer is the server API for FscanService service. // All implementations must embed UnimplementedFscanServiceServer // for forward compatibility. @@ -91,8 +69,6 @@ type FscanServiceServer interface { StartScan(context.Context, *StartScanRequest) (*StartScanResponse, error) // 获取扫描结果(非流式) GetScanResults(context.Context, *TaskResultsRequest) (*TaskResultsResponse, error) - // 获取扫描结果(流式) - StreamScanResults(*TaskResultsRequest, grpc.ServerStreamingServer[ScanResult]) error mustEmbedUnimplementedFscanServiceServer() } @@ -109,9 +85,6 @@ func (UnimplementedFscanServiceServer) StartScan(context.Context, *StartScanRequ func (UnimplementedFscanServiceServer) GetScanResults(context.Context, *TaskResultsRequest) (*TaskResultsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetScanResults not implemented") } -func (UnimplementedFscanServiceServer) StreamScanResults(*TaskResultsRequest, grpc.ServerStreamingServer[ScanResult]) error { - return status.Errorf(codes.Unimplemented, "method StreamScanResults not implemented") -} func (UnimplementedFscanServiceServer) mustEmbedUnimplementedFscanServiceServer() {} func (UnimplementedFscanServiceServer) testEmbeddedByValue() {} @@ -169,17 +142,6 @@ func _FscanService_GetScanResults_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _FscanService_StreamScanResults_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(TaskResultsRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(FscanServiceServer).StreamScanResults(m, &grpc.GenericServerStream[TaskResultsRequest, ScanResult]{ServerStream: stream}) -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type FscanService_StreamScanResultsServer = grpc.ServerStreamingServer[ScanResult] - // FscanService_ServiceDesc is the grpc.ServiceDesc for FscanService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -196,12 +158,6 @@ var FscanService_ServiceDesc = grpc.ServiceDesc{ Handler: _FscanService_GetScanResults_Handler, }, }, - Streams: []grpc.StreamDesc{ - { - StreamName: "StreamScanResults", - Handler: _FscanService_StreamScanResults_Handler, - ServerStreams: true, - }, - }, - Metadata: "proto/fscan.proto", + Streams: []grpc.StreamDesc{}, + Metadata: "lib/rpc.proto", } diff --git a/RPC/server.go b/RPC/server.go index 3555724..7d43d27 100644 --- a/RPC/server.go +++ b/RPC/server.go @@ -14,15 +14,12 @@ import ( ) // 启动 gRPC + HTTP Gateway 服务(仅当设置了 API 地址时) -// 如果未设置 API 地址,直接返回 nil -// 如果 HTTP 启动失败,则返回 error func StartApiServer() error { if Common.ApiAddr == "" { - Common.LogDebug("未设置 API 地址,跳过 API 服务启动") return nil } - grpcAddr := ":50051" + grpcAddr := "127.0.0.1:50051" httpAddr := validateHTTPAddr(Common.ApiAddr, ":8088") go runGRPCServer(grpcAddr) @@ -61,8 +58,16 @@ func runHTTPGateway(httpAddr, grpcAddr string) error { return err } - // 添加 CORS 支持 - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 使用中间件包装 mux + handler := applyMiddlewares(mux) + + Common.LogSuccess("✅ HTTP Gateway 已启动,地址: " + httpAddr) + return http.ListenAndServe(httpAddr, handler) +} + +// 注册 HTTP 中间件 +func applyMiddlewares(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") @@ -71,11 +76,9 @@ func runHTTPGateway(httpAddr, grpcAddr string) error { w.WriteHeader(http.StatusOK) return } - mux.ServeHTTP(w, r) - }) - Common.LogSuccess("✅ HTTP Gateway 已启动,地址: " + httpAddr) - return http.ListenAndServe(httpAddr, handler) + handler.ServeHTTP(w, r) + }) } // 校验监听地址格式,格式非法使用默认 diff --git a/RPC/service/fscan.go b/RPC/service/fscan.go index 0e1dcd5..471b8a0 100644 --- a/RPC/service/fscan.go +++ b/RPC/service/fscan.go @@ -10,25 +10,27 @@ import ( "github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/Core" pb "github.com/shadow1ng/fscan/RPC/lib" + structpb "google.golang.org/protobuf/types/known/structpb" ) type FscanService struct { pb.UnimplementedFscanServiceServer - scanMutex sync.Mutex - isScanning int32 // 原子变量,用于标记是否正在扫描 + scanMutex sync.Mutex + isScanning int32 + scanStartTime time.Time // 记录扫描开始时间 } func (s *FscanService) StartScan(ctx context.Context, req *pb.StartScanRequest) (*pb.StartScanResponse, error) { if !atomic.CompareAndSwapInt32(&s.isScanning, 0, 1) { return &pb.StartScanResponse{ - TaskId: "", + TaskId: "current", Message: "已有扫描任务正在运行,请稍后重试", }, nil } - taskID := "uuid" + s.scanStartTime = time.Now() // 记录任务开始时间 - go func(taskID string, req *pb.StartScanRequest) { + go func(req *pb.StartScanRequest) { defer atomic.StoreInt32(&s.isScanning, 0) s.scanMutex.Lock() @@ -43,69 +45,53 @@ func (s *FscanService) StartScan(ctx context.Context, req *pb.StartScanRequest) if err := Common.Parse(&info); err != nil { return } - //TODO: 结果保存需要在output模块中设计 + if err := Common.CloseOutput(); err != nil { + Common.LogError(fmt.Sprintf("关闭输出系统失败: %v", err)) + return + } if err := Common.InitOutput(); err != nil { Common.LogError(fmt.Sprintf("初始化输出系统失败: %v", err)) return } Core.Scan(info) - }(taskID, req) + + Common.LogDebug("扫描任务完成") + }(req) return &pb.StartScanResponse{ - TaskId: taskID, - Message: "扫描任务已启动", + TaskId: "current", + Message: "成功启动扫描任务", }, nil } -// GetScanResults 用于获取指定任务 ID 的扫描结果。 -// 参数: -// - ctx:请求上下文。 -// - req:TaskResultsRequest,包含任务 ID。 -// 返回值: -// - TaskResultsResponse:包含结果列表、任务状态等信息。 -// - error:执行中出现的错误信息。 func (s *FscanService) GetScanResults(ctx context.Context, req *pb.TaskResultsRequest) (*pb.TaskResultsResponse, error) { - // TODO: 实现根据任务 ID 查询任务结果,可以从缓存、数据库或临时文件中获取。 - // 此处为模拟数据 - - result := &pb.ScanResult{ - Time: time.Now().Format(time.RFC3339), - Type: "port", - Target: "192.168.1.1:80", - Status: "open", - DetailsJson: `{"banner":"nginx"}`, + results, err := Common.GetResults() + if err != nil { + return nil, fmt.Errorf("读取结果失败: %w", err) } + pbResults := make([]*pb.ScanResult, 0, len(results)) + for _, r := range results { + detailsStruct, err := structpb.NewStruct(r.Details) + if err != nil { + Common.LogError(fmt.Sprintf("转换为 Struct 失败: %v", err)) + continue + } + + pbResults = append(pbResults, &pb.ScanResult{ + Time: r.Time.Format(time.RFC3339), + Type: string(r.Type), + Target: r.Target, + Status: r.Status, + DetailsJson: detailsStruct, + }) + } + + finished := atomic.LoadInt32(&s.isScanning) == 0 return &pb.TaskResultsResponse{ - TaskId: req.TaskId, - Results: []*pb.ScanResult{result}, - Finished: true, // TODO: 判断任务是否真正完成 + TaskId: req.Filter.TaskId, + Results: pbResults, + Finished: finished, }, nil } - -// StreamScanResults 用于通过流式返回任务扫描结果,适合长时间扫描过程。 -// 参数: -// - req:TaskResultsRequest,包含任务 ID。 -// - stream:用于向客户端持续推送结果。 -// 返回值: -// - error:执行中出现的错误信息。 -func (s *FscanService) StreamScanResults(req *pb.TaskResultsRequest, stream pb.FscanService_StreamScanResultsServer) error { - // TODO: 根据任务 ID 逐步查询任务结果,并通过 stream.Send 发送给客户端。 - // 可以监听任务进度,逐步推送最新结果。 - - for i := 0; i < 5; i++ { - result := &pb.ScanResult{ - Time: time.Now().Format(time.RFC3339), - Type: "vuln", - Target: "192.168.1.1", - Status: "found", - DetailsJson: `{"vuln":"CVE-2021-12345"}`, - } - if err := stream.Send(result); err != nil { - return err - } - time.Sleep(1 * time.Second) // 模拟异步推送过程 - } - return nil -}