You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript gRPC服务器开发报错排查:类型不匹配与属性可选性问题

我来帮你拆解每个错误的原因,再给出具体的修复方案:


错误1:GetAllNews服务提示“与类型'NewsList'无共同属性”

原因:你的Proto文件定义GetAllNews的返回值是NewsList类型,这个类型是一个包含repeated News news字段的对象,但你直接返回了newsLists数组,和NewsList的结构完全不匹配,所以TypeScript会报错。

修复:把返回值包装成符合NewsList结构的对象:

"GetAllNews": (_, callback) => {
  callback(null, { news: newsLists })
},

错误2:AddNews服务无法向数组添加元素

原因:你的newsLists数组里的元素是所有字段都必填的News类型,但gRPC生成的TS类型中,call.request的字段是可选的(客户端可能没传全字段),所以合并后的news对象会带有可选字段,无法直接赋值给必填的News类型。

修复:先检查客户端传入的必填字段是否存在,再做类型断言确保符合News类型:

"AddNews": (call, callback) => {
  // 校验必填字段是否存在
  if (!call.request.title || !call.request.body || !call.request.postImage) {
    callback({
      code: grpc.status.INVALID_ARGUMENT,
      message: "title、body、postImage为必填字段"
    }, null)
    return
  }
  let news = { id: uuidv4(), ...call.request} as News
  newsLists.push(news)
  callback(null, news)
},

同时记得导入News类型:

import { NewsServiceHandlers, News } from './proto/newsPackage/NewsService';

错误3:EditNews服务出现“Type 'string | undefined' is not assignable to type 'string'”

原因:call.request的字段是可选类型(string | undefined),但newsItem的字段是必填的string类型,直接赋值会触发类型不匹配;另外你还没处理newsItem可能为undefined的情况(找不到对应ID的新闻时)。

修复:先检查新闻是否存在,再选择性更新存在的字段:

"EditNews": (call, callback) => {
  const newsId = call.request.id
  const newsItem = newsLists.find(({ id }) => newsId === id)
  
  if (!newsItem) {
    callback({
      code: grpc.status.NOT_FOUND,
      message: "未找到对应新闻"
    }, null)
    return
  }

  // 只更新客户端传入的非undefined字段
  if (call.request.title !== undefined) {
    newsItem.title = call.request.title
  }
  if (call.request.body !== undefined) {
    newsItem.body = call.request.body
  }
  if (call.request.postImage !== undefined) {
    newsItem.postImage = call.request.postImage
  }
  
  callback(null, newsItem)
},

额外的逻辑错误(GetNews)

你在GetNews里写的判断条件是id !== newsId,这会返回第一个ID不等于传入值的新闻,逻辑完全反了,需要修正为:

"GetNews": (call, callback) => {
  const newsId = call.request.id
  const newsItem = newsLists.find(({ id }) => id === newsId)
  
  if (!newsItem) {
    callback({
      code: grpc.status.NOT_FOUND,
      message: "未找到对应新闻"
    }, null)
    return
  }
  
  callback(null, newsItem)
},

完整修复后的server.ts代码

import path from 'path'
import * as grpc from '@grpc/grpc-js'
import * as protoLoader from '@grpc/proto-loader'
import { v4 as uuidv4 } from 'uuid'
import { ProtoGrpcType } from './proto/news';
import { NewsServiceHandlers, News } from './proto/newsPackage/NewsService';

const PORT = 9999;
const PROTO_FILE = './proto/random.proto'

const packageDef = protoLoader.loadSync(path.resolve(__dirname, PROTO_FILE))
const grpcObj = (grpc.loadPackageDefinition(packageDef) as unknown) as ProtoGrpcType
const newsPackage = grpcObj.newsPackage

let newsLists: News[] = [
 { id: "1", title: "Note 1", body: "Content 1", postImage: "Post image 1" },
 { id: "2", title: "Note 2", body: "Content 2", postImage: "Post image 2" },
]

function main() {
 const server = getServer()
 server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (err, port) => {
  if (err) {
   console.error(err)
   return
  }
  console.log('Your server is started on port', port)
  server.start()
 })
}

function getServer() {
 const server = new grpc.Server()
 server.addService(newsPackage.NewsService.service, {
  "GetAllNews": (_, callback) => {
   callback(null, { news: newsLists })
  },
  "GetNews": (call, callback) => {
   const newsId = call.request.id
   const newsItem = newsLists.find(({ id }) => id === newsId)
   if (!newsItem) {
    callback({
      code: grpc.status.NOT_FOUND,
      message: "未找到对应新闻"
    }, null)
    return
   }
   callback(null, newsItem)
  },
  "AddNews": (call, callback) => {
   if (!call.request.title || !call.request.body || !call.request.postImage) {
    callback({
      code: grpc.status.INVALID_ARGUMENT,
      message: "title、body、postImage为必填字段"
    }, null)
    return
   }
   let news = { id: uuidv4(), ...call.request} as News
   newsLists.push(news)
   callback(null, news)
  },
  "EditNews": (call, callback) => {
   const newsId = call.request.id
   const newsItem = newsLists.find(({ id }) => newsId === id)
   if (!newsItem) {
    callback({
      code: grpc.status.NOT_FOUND,
      message: "未找到对应新闻"
    }, null)
    return
   }
   if (call.request.title !== undefined) {
    newsItem.title = call.request.title
   }
   if (call.request.body !== undefined) {
    newsItem.body = call.request.body
   }
   if (call.request.postImage !== undefined) {
    newsItem.postImage = call.request.postImage
   }
   callback(null, newsItem)
  },
  "DeleteNews": (call, callback) => {
   const newsId = call.request.id;
   newsLists = newsLists.filter(({ id }) => id !== newsId);
   callback(null, {});
  }
 } as NewsServiceHandlers)
 return server
}

main()

内容的提问来源于stack exchange,提问作者ferryawijayanto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 16:32:35