[{"data":1,"prerenderedAt":2095},["ShallowReactive",2],{"page-/post/hono/hono-feat-config-common-utils":3,"surrounding-page":2086},{"id":4,"title":5,"author":6,"body":7,"date":2073,"description":17,"extension":2074,"group":6,"lastmod":2075,"meta":2076,"navigation":454,"path":2077,"rawbody":2078,"seo":2079,"showTitle":5,"stem":2080,"tags":2081,"versions":2083,"__hash__":2085},"content/post/Hono/hono-feat-config-common-utils.md","【Hono】优化：提取配置项及公共函数",null,{"type":8,"value":9,"toc":2065},"minimark",[10,14,18,21,24,29,32,231,238,248,259,262,297,300,304,307,321,331,572,575,602,605,609,612,633,636,989,1003,1007,1010,1013,1023,1026,1301,1317,1483,1486,1489,1774,1778,1785,1792,1797,1834,1839,1857,1867,1938,1941,1950,1983,1989,2011,2021,2024,2027,2030,2033,2044,2047,2054,2061],[11,12],"h1",{"id":13},"",[15,16,17],"p",{},"项目完成的七七八八了，代码也慢慢多了起来，有些基本的优化工作必须要做了。",[15,19,20],{},"不然等再加一些业务逻辑，就会变得非常臃肿，然后就又免不了被抛弃的命运。",[15,22,23],{},"写自己的玩具就是这样的，总是在不停的造玩具。",[25,26,28],"h2",{"id":27},"优化点1errorhandler","优化点1：ErrorHandler",[15,30,31],{},"目前的目录是这样的",[33,34,38],"pre",{"className":35,"code":36,"language":37,"meta":13,"style":13},"language-shell shiki shiki-themes github-light",".\n├── Dockerfile\n├── README.md\n├── bun.lockb\n├── bunfig.toml\n├── db\n│   └── zzaoclub.db\n├── docker-compose.dev.yml\n├── docker-compose.prod.yml\n├── docker-compose.yml\n├── logs\n├── out\n│   └── index.js\n├── package.json\n├── src\n│   ├── common\n│   ├── database\n│   ├── index.ts\n│   ├── salt\n│   └── user\n└── tsconfig.json\n","shell",[39,40,41,50,61,69,77,85,93,105,113,121,129,137,145,155,163,171,182,192,202,212,222],"code",{"__ignoreMap":13},[42,43,46],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"sYu0t",".\n",[42,51,53,57],{"class":44,"line":52},2,[42,54,56],{"class":55},"s7eDp","├──",[42,58,60],{"class":59},"sYBdl"," Dockerfile\n",[42,62,64,66],{"class":44,"line":63},3,[42,65,56],{"class":55},[42,67,68],{"class":59}," README.md\n",[42,70,72,74],{"class":44,"line":71},4,[42,73,56],{"class":55},[42,75,76],{"class":59}," bun.lockb\n",[42,78,80,82],{"class":44,"line":79},5,[42,81,56],{"class":55},[42,83,84],{"class":59}," bunfig.toml\n",[42,86,88,90],{"class":44,"line":87},6,[42,89,56],{"class":55},[42,91,92],{"class":59}," db\n",[42,94,96,99,102],{"class":44,"line":95},7,[42,97,98],{"class":55},"│",[42,100,101],{"class":59},"   └──",[42,103,104],{"class":59}," zzaoclub.db\n",[42,106,108,110],{"class":44,"line":107},8,[42,109,56],{"class":55},[42,111,112],{"class":59}," docker-compose.dev.yml\n",[42,114,116,118],{"class":44,"line":115},9,[42,117,56],{"class":55},[42,119,120],{"class":59}," docker-compose.prod.yml\n",[42,122,124,126],{"class":44,"line":123},10,[42,125,56],{"class":55},[42,127,128],{"class":59}," docker-compose.yml\n",[42,130,132,134],{"class":44,"line":131},11,[42,133,56],{"class":55},[42,135,136],{"class":59}," logs\n",[42,138,140,142],{"class":44,"line":139},12,[42,141,56],{"class":55},[42,143,144],{"class":59}," out\n",[42,146,148,150,152],{"class":44,"line":147},13,[42,149,98],{"class":55},[42,151,101],{"class":59},[42,153,154],{"class":59}," index.js\n",[42,156,158,160],{"class":44,"line":157},14,[42,159,56],{"class":55},[42,161,162],{"class":59}," package.json\n",[42,164,166,168],{"class":44,"line":165},15,[42,167,56],{"class":55},[42,169,170],{"class":59}," src\n",[42,172,174,176,179],{"class":44,"line":173},16,[42,175,98],{"class":55},[42,177,178],{"class":59},"   ├──",[42,180,181],{"class":59}," common\n",[42,183,185,187,189],{"class":44,"line":184},17,[42,186,98],{"class":55},[42,188,178],{"class":59},[42,190,191],{"class":59}," database\n",[42,193,195,197,199],{"class":44,"line":194},18,[42,196,98],{"class":55},[42,198,178],{"class":59},[42,200,201],{"class":59}," index.ts\n",[42,203,205,207,209],{"class":44,"line":204},19,[42,206,98],{"class":55},[42,208,178],{"class":59},[42,210,211],{"class":59}," salt\n",[42,213,215,217,219],{"class":44,"line":214},20,[42,216,98],{"class":55},[42,218,101],{"class":59},[42,220,221],{"class":59}," user\n",[42,223,225,228],{"class":44,"line":224},21,[42,226,227],{"class":55},"└──",[42,229,230],{"class":59}," tsconfig.json\n",[15,232,233,234,237],{},"目前只关注",[39,235,236],{},"src","下的结构即可。",[15,239,240,243,244,247],{},[39,241,242],{},"index.ts","作为入口文件，加载了一些全局中间价，以及去挂载子路由，拦截全局的",[39,245,246],{},"error","。",[15,249,250,251,254,255,258],{},"这里能抽出去一个",[39,252,253],{},"errorHandler","，我把它放在",[39,256,257],{},"common","下。",[15,260,261],{},"它的功能也很简单：",[263,264,265,277,280,291],"ol",{},[266,267,268,269,273,274],"li",{},"拦截到错误后，把",[270,271,272],"strong",{},"Http状态码","设置为",[39,275,276],{},"200",[266,278,279],{},"在控制台/日志文件中打印/记录请求时间+方式+url+原始错误信息",[266,281,282,283,286,287,290],{},"在上下文中使用",[39,284,285],{},"c.get('errCode')","取出在某处抛出的自定义错误，然后把",[270,288,289],{},"自定义的错误码和错误信息或默认的错误信息","返回给前端",[266,292,293,294],{},"统一返回标准如:",[39,295,296],{},"{ code: 40001, data: xxx, msg: '1123131' }",[15,298,299],{},"拆好后重新引入测试，然后就已经出现了下一个优化点",[25,301,303],{"id":302},"优化点2errorcode","优化点2：ErrorCode",[15,305,306],{},"刚才拆处errorHandler后，发现会取一些默认的错误信息，以及有一套自定义的错误嘛。",[15,308,309,310,313,314,313,317,320],{},"所以要提前定义好一套，不然随手就写个",[39,311,312],{},"40001","、",[39,315,316],{},"40002",[39,318,319],{},"40003","，时间长了鬼知道这是什么意思。",[15,322,323,324,326,327,330],{},"所以再从",[39,325,257],{},"下新建一个",[39,328,329],{},"errorCode.ts"," ，我只是举个例子，错误码和信息要自己看着来。",[33,332,336],{"className":333,"code":334,"language":335,"meta":13,"style":13},"language-typescript shiki shiki-themes github-light","export const ErrorCode = {\n  PERMISSION_DENIED: 40001, // 权限问题\n  VALIDATION_ERROR: 40002, // 参数问题\n  UNAUTHORIZED: 40003, // 未登录\n  LOGIN_EXPIRED: 40004, // 登录已过期\n  NOT_FOUND: 40005, // 不存在的接口\n  INTERNAL_SERVER_ERROR: 50000, // 服务器内部错误\n  UNKOWN_ERROR: 50001 // 未知错误\n}\n\nexport const ErrorCodeMsg = {\n  [ErrorCode.PERMISSION_DENIED]: '权限问题',\n  [ErrorCode.VALIDATION_ERROR]: '参数问题',\n  [ErrorCode.UNAUTHORIZED]: '未登录',\n  [ErrorCode.LOGIN_EXPIRED]: '登录已过期',\n  [ErrorCode.NOT_FOUND]: '不存在的接口',\n  [ErrorCode.INTERNAL_SERVER_ERROR]: '服务器内部错误',\n  [ErrorCode.UNKOWN_ERROR]: '未知错误'\n}\n","typescript",[39,337,338,357,371,383,395,408,421,434,445,450,456,469,486,500,514,528,542,556,568],{"__ignoreMap":13},[42,339,340,344,347,350,353],{"class":44,"line":45},[42,341,343],{"class":342},"sD7c4","export",[42,345,346],{"class":342}," const",[42,348,349],{"class":48}," ErrorCode",[42,351,352],{"class":342}," =",[42,354,356],{"class":355},"sgsFI"," {\n",[42,358,359,362,364,367],{"class":44,"line":52},[42,360,361],{"class":355},"  PERMISSION_DENIED: ",[42,363,312],{"class":48},[42,365,366],{"class":355},", ",[42,368,370],{"class":369},"sAwPA","// 权限问题\n",[42,372,373,376,378,380],{"class":44,"line":63},[42,374,375],{"class":355},"  VALIDATION_ERROR: ",[42,377,316],{"class":48},[42,379,366],{"class":355},[42,381,382],{"class":369},"// 参数问题\n",[42,384,385,388,390,392],{"class":44,"line":71},[42,386,387],{"class":355},"  UNAUTHORIZED: ",[42,389,319],{"class":48},[42,391,366],{"class":355},[42,393,394],{"class":369},"// 未登录\n",[42,396,397,400,403,405],{"class":44,"line":79},[42,398,399],{"class":355},"  LOGIN_EXPIRED: ",[42,401,402],{"class":48},"40004",[42,404,366],{"class":355},[42,406,407],{"class":369},"// 登录已过期\n",[42,409,410,413,416,418],{"class":44,"line":87},[42,411,412],{"class":355},"  NOT_FOUND: ",[42,414,415],{"class":48},"40005",[42,417,366],{"class":355},[42,419,420],{"class":369},"// 不存在的接口\n",[42,422,423,426,429,431],{"class":44,"line":95},[42,424,425],{"class":355},"  INTERNAL_SERVER_ERROR: ",[42,427,428],{"class":48},"50000",[42,430,366],{"class":355},[42,432,433],{"class":369},"// 服务器内部错误\n",[42,435,436,439,442],{"class":44,"line":107},[42,437,438],{"class":355},"  UNKOWN_ERROR: ",[42,440,441],{"class":48},"50001",[42,443,444],{"class":369}," // 未知错误\n",[42,446,447],{"class":44,"line":115},[42,448,449],{"class":355},"}\n",[42,451,452],{"class":44,"line":123},[42,453,455],{"emptyLinePlaceholder":454},true,"\n",[42,457,458,460,462,465,467],{"class":44,"line":131},[42,459,343],{"class":342},[42,461,346],{"class":342},[42,463,464],{"class":48}," ErrorCodeMsg",[42,466,352],{"class":342},[42,468,356],{"class":355},[42,470,471,474,477,480,483],{"class":44,"line":139},[42,472,473],{"class":355},"  [ErrorCode.",[42,475,476],{"class":48},"PERMISSION_DENIED",[42,478,479],{"class":355},"]: ",[42,481,482],{"class":59},"'权限问题'",[42,484,485],{"class":355},",\n",[42,487,488,490,493,495,498],{"class":44,"line":147},[42,489,473],{"class":355},[42,491,492],{"class":48},"VALIDATION_ERROR",[42,494,479],{"class":355},[42,496,497],{"class":59},"'参数问题'",[42,499,485],{"class":355},[42,501,502,504,507,509,512],{"class":44,"line":157},[42,503,473],{"class":355},[42,505,506],{"class":48},"UNAUTHORIZED",[42,508,479],{"class":355},[42,510,511],{"class":59},"'未登录'",[42,513,485],{"class":355},[42,515,516,518,521,523,526],{"class":44,"line":165},[42,517,473],{"class":355},[42,519,520],{"class":48},"LOGIN_EXPIRED",[42,522,479],{"class":355},[42,524,525],{"class":59},"'登录已过期'",[42,527,485],{"class":355},[42,529,530,532,535,537,540],{"class":44,"line":173},[42,531,473],{"class":355},[42,533,534],{"class":48},"NOT_FOUND",[42,536,479],{"class":355},[42,538,539],{"class":59},"'不存在的接口'",[42,541,485],{"class":355},[42,543,544,546,549,551,554],{"class":44,"line":184},[42,545,473],{"class":355},[42,547,548],{"class":48},"INTERNAL_SERVER_ERROR",[42,550,479],{"class":355},[42,552,553],{"class":59},"'服务器内部错误'",[42,555,485],{"class":355},[42,557,558,560,563,565],{"class":44,"line":194},[42,559,473],{"class":355},[42,561,562],{"class":48},"UNKOWN_ERROR",[42,564,479],{"class":355},[42,566,567],{"class":59},"'未知错误'\n",[42,569,570],{"class":44,"line":204},[42,571,449],{"class":355},[15,573,574],{},"这样在某个路由抛出错误时，应该是这样的",[33,576,578],{"className":333,"code":577,"language":335,"meta":13,"style":13}," c.set('errCode', ErrorCode.UNAUTHORIZED)\n",[39,579,580],{"__ignoreMap":13},[42,581,582,585,588,591,594,597,599],{"class":44,"line":45},[42,583,584],{"class":355}," c.",[42,586,587],{"class":55},"set",[42,589,590],{"class":355},"(",[42,592,593],{"class":59},"'errCode'",[42,595,596],{"class":355},", ErrorCode.",[42,598,506],{"class":48},[42,600,601],{"class":355},")\n",[15,603,604],{},"然后接下来再去看看已有逻辑里，哪里需要抛出错误，把这些自定义的错误码给用上。",[25,606,608],{"id":607},"优化点3jwt相关","优化点3：JWT相关",[15,610,611],{},"刚才在替换错误码时，发现JWT和Zod的相关逻辑里需要抛出一些异常。而有了两个模块后，也会发现他们有一些重复代码。",[15,613,614,615,618,619,622,623,626,627,629,630,247],{},"比如",[39,616,617],{},"user","中的",[270,620,621],{},"jwt中间件","和",[39,624,625],{},"salt","模块中是一样的，没必要写两份，可以把jwt中间件的逻辑抽出，放在",[39,628,242],{},"中，目前来看，jwt只是校验一下用户有没有登录，以及跳过一些",[39,631,632],{},"路由白名单",[15,634,635],{},"所以可以先把jwt逻辑抽出来，以下是一个参考",[33,637,639],{"className":333,"code":638,"language":335,"meta":13,"style":13},"const JWT_SECRET = Bun.env.JWT_SECRET || ''\nconst jwtMiddware = jwt({\n  secret: JWT_SECRET,\n})\n\nsalt.use('/*', async (c, next) => {\n  if (NoAuthPaths.includes(c.req.path)) {\n    await next();\n    return;\n  }\n  await jwtMiddware(c, async () => {\n    const user = c.get('jwtPayload')\n    \n    if (!user) {\n      c.set('errMsg', '用户未登录')\n      c.set('errCode', ErrorCode.UNAUTHORIZED)\n      throw new HTTPException(401)\n    }\n\n    if (user.id !== 1) {\n      c.set('errMsg', '用户无权限')\n      c.set('errCode', ErrorCode.PERMISSION_DENIED)\n      throw new HTTPException(401)\n    }\n    await next()\n  })\n\n})\n",[39,640,641,663,678,687,692,696,734,748,759,767,772,791,813,818,831,850,866,884,889,893,909,926,943,958,963,973,979,984],{"__ignoreMap":13},[42,642,643,646,649,651,654,657,660],{"class":44,"line":45},[42,644,645],{"class":342},"const",[42,647,648],{"class":48}," JWT_SECRET",[42,650,352],{"class":342},[42,652,653],{"class":355}," Bun.env.",[42,655,656],{"class":48},"JWT_SECRET",[42,658,659],{"class":342}," ||",[42,661,662],{"class":59}," ''\n",[42,664,665,667,670,672,675],{"class":44,"line":52},[42,666,645],{"class":342},[42,668,669],{"class":48}," jwtMiddware",[42,671,352],{"class":342},[42,673,674],{"class":55}," jwt",[42,676,677],{"class":355},"({\n",[42,679,680,683,685],{"class":44,"line":63},[42,681,682],{"class":355},"  secret: ",[42,684,656],{"class":48},[42,686,485],{"class":355},[42,688,689],{"class":44,"line":71},[42,690,691],{"class":355},"})\n",[42,693,694],{"class":44,"line":79},[42,695,455],{"emptyLinePlaceholder":454},[42,697,698,701,704,706,709,711,714,717,721,723,726,729,732],{"class":44,"line":87},[42,699,700],{"class":355},"salt.",[42,702,703],{"class":55},"use",[42,705,590],{"class":355},[42,707,708],{"class":59},"'/*'",[42,710,366],{"class":355},[42,712,713],{"class":342},"async",[42,715,716],{"class":355}," (",[42,718,720],{"class":719},"sqxcx","c",[42,722,366],{"class":355},[42,724,725],{"class":719},"next",[42,727,728],{"class":355},") ",[42,730,731],{"class":342},"=>",[42,733,356],{"class":355},[42,735,736,739,742,745],{"class":44,"line":95},[42,737,738],{"class":342},"  if",[42,740,741],{"class":355}," (NoAuthPaths.",[42,743,744],{"class":55},"includes",[42,746,747],{"class":355},"(c.req.path)) {\n",[42,749,750,753,756],{"class":44,"line":107},[42,751,752],{"class":342},"    await",[42,754,755],{"class":55}," next",[42,757,758],{"class":355},"();\n",[42,760,761,764],{"class":44,"line":115},[42,762,763],{"class":342},"    return",[42,765,766],{"class":355},";\n",[42,768,769],{"class":44,"line":123},[42,770,771],{"class":355},"  }\n",[42,773,774,777,779,782,784,787,789],{"class":44,"line":131},[42,775,776],{"class":342},"  await",[42,778,669],{"class":55},[42,780,781],{"class":355},"(c, ",[42,783,713],{"class":342},[42,785,786],{"class":355}," () ",[42,788,731],{"class":342},[42,790,356],{"class":355},[42,792,793,796,799,801,803,806,808,811],{"class":44,"line":139},[42,794,795],{"class":342},"    const",[42,797,798],{"class":48}," user",[42,800,352],{"class":342},[42,802,584],{"class":355},[42,804,805],{"class":55},"get",[42,807,590],{"class":355},[42,809,810],{"class":59},"'jwtPayload'",[42,812,601],{"class":355},[42,814,815],{"class":44,"line":147},[42,816,817],{"class":355},"    \n",[42,819,820,823,825,828],{"class":44,"line":157},[42,821,822],{"class":342},"    if",[42,824,716],{"class":355},[42,826,827],{"class":342},"!",[42,829,830],{"class":355},"user) {\n",[42,832,833,836,838,840,843,845,848],{"class":44,"line":165},[42,834,835],{"class":355},"      c.",[42,837,587],{"class":55},[42,839,590],{"class":355},[42,841,842],{"class":59},"'errMsg'",[42,844,366],{"class":355},[42,846,847],{"class":59},"'用户未登录'",[42,849,601],{"class":355},[42,851,852,854,856,858,860,862,864],{"class":44,"line":173},[42,853,835],{"class":355},[42,855,587],{"class":55},[42,857,590],{"class":355},[42,859,593],{"class":59},[42,861,596],{"class":355},[42,863,506],{"class":48},[42,865,601],{"class":355},[42,867,868,871,874,877,879,882],{"class":44,"line":184},[42,869,870],{"class":342},"      throw",[42,872,873],{"class":342}," new",[42,875,876],{"class":55}," HTTPException",[42,878,590],{"class":355},[42,880,881],{"class":48},"401",[42,883,601],{"class":355},[42,885,886],{"class":44,"line":194},[42,887,888],{"class":355},"    }\n",[42,890,891],{"class":44,"line":204},[42,892,455],{"emptyLinePlaceholder":454},[42,894,895,897,900,903,906],{"class":44,"line":214},[42,896,822],{"class":342},[42,898,899],{"class":355}," (user.id ",[42,901,902],{"class":342},"!==",[42,904,905],{"class":48}," 1",[42,907,908],{"class":355},") {\n",[42,910,911,913,915,917,919,921,924],{"class":44,"line":224},[42,912,835],{"class":355},[42,914,587],{"class":55},[42,916,590],{"class":355},[42,918,842],{"class":59},[42,920,366],{"class":355},[42,922,923],{"class":59},"'用户无权限'",[42,925,601],{"class":355},[42,927,929,931,933,935,937,939,941],{"class":44,"line":928},22,[42,930,835],{"class":355},[42,932,587],{"class":55},[42,934,590],{"class":355},[42,936,593],{"class":59},[42,938,596],{"class":355},[42,940,476],{"class":48},[42,942,601],{"class":355},[42,944,946,948,950,952,954,956],{"class":44,"line":945},23,[42,947,870],{"class":342},[42,949,873],{"class":342},[42,951,876],{"class":55},[42,953,590],{"class":355},[42,955,881],{"class":48},[42,957,601],{"class":355},[42,959,961],{"class":44,"line":960},24,[42,962,888],{"class":355},[42,964,966,968,970],{"class":44,"line":965},25,[42,967,752],{"class":342},[42,969,755],{"class":55},[42,971,972],{"class":355},"()\n",[42,974,976],{"class":44,"line":975},26,[42,977,978],{"class":355},"  })\n",[42,980,982],{"class":44,"line":981},27,[42,983,455],{"emptyLinePlaceholder":454},[42,985,987],{"class":44,"line":986},28,[42,988,691],{"class":355},[15,990,991,992,313,995,998,999,1002],{},"把后面函数单独抽离出去即可，同时我在每个模块下如",[39,993,994],{},"src/user",[39,996,997],{},"src/salt","下再放一个",[39,1000,1001],{},"common.ts","，把路由白名单（NoAuthPaths）放进去，每个模块在使用jwt中间件时，再把这个白名单传进去。",[25,1004,1006],{"id":1005},"优化点4zod相关","优化点4：Zod相关",[15,1008,1009],{},"看到路由的参数校验那一串，就知道不得不优化一下，因为不可能每个接口直接写那么一大串schame。",[15,1011,1012],{},"抽离分两部分，一部分是validator函数，一部分是schame的定义。",[15,1014,1015,1016,1018,1019,1022],{},"先来validator，在",[39,1017,257],{},"下再新建个",[39,1020,1021],{},"validator.ts","，封装一个zvalidator函数，传参就按zValidator需要什么就行，主要是为了抛出错误。",[15,1024,1025],{},"以下也只是个示例：",[33,1027,1029],{"className":333,"code":1028,"language":335,"meta":13,"style":13},"import { zValidator } from \"@hono/zod-validator\"\nimport { Context, Next } from \"hono\"\nimport { ErrorCode, ErrorCodeMsg } from \"./errorCode\";\nimport { HTTPException } from \"hono/http-exception\";\n// 自定义校验, 在校验失败时抛出异常, 由errorHanlder统一处理\nexport const zvalidator = (source: any, schema: any) => {\n  return zValidator(source, schema, (result, c: Context) => {\n    if (!result.success) {\n      const errMsg = result.error.errors.map((e: any) => `field:${e.path[0]} - ${e.message}`).join(', ')\n      c.set('errMsg', errMsg)\n      c.set('errCode', ErrorCode.VALIDATION_ERROR)\n      throw new HTTPException(400, { message: errMsg })\n    }\n  })\n};\n",[39,1030,1031,1045,1057,1071,1085,1090,1127,1156,1167,1243,1256,1272,1288,1292,1296],{"__ignoreMap":13},[42,1032,1033,1036,1039,1042],{"class":44,"line":45},[42,1034,1035],{"class":342},"import",[42,1037,1038],{"class":355}," { zValidator } ",[42,1040,1041],{"class":342},"from",[42,1043,1044],{"class":59}," \"@hono/zod-validator\"\n",[42,1046,1047,1049,1052,1054],{"class":44,"line":52},[42,1048,1035],{"class":342},[42,1050,1051],{"class":355}," { Context, Next } ",[42,1053,1041],{"class":342},[42,1055,1056],{"class":59}," \"hono\"\n",[42,1058,1059,1061,1064,1066,1069],{"class":44,"line":63},[42,1060,1035],{"class":342},[42,1062,1063],{"class":355}," { ErrorCode, ErrorCodeMsg } ",[42,1065,1041],{"class":342},[42,1067,1068],{"class":59}," \"./errorCode\"",[42,1070,766],{"class":355},[42,1072,1073,1075,1078,1080,1083],{"class":44,"line":71},[42,1074,1035],{"class":342},[42,1076,1077],{"class":355}," { HTTPException } ",[42,1079,1041],{"class":342},[42,1081,1082],{"class":59}," \"hono/http-exception\"",[42,1084,766],{"class":355},[42,1086,1087],{"class":44,"line":79},[42,1088,1089],{"class":369},"// 自定义校验, 在校验失败时抛出异常, 由errorHanlder统一处理\n",[42,1091,1092,1094,1096,1099,1101,1103,1106,1109,1112,1114,1117,1119,1121,1123,1125],{"class":44,"line":87},[42,1093,343],{"class":342},[42,1095,346],{"class":342},[42,1097,1098],{"class":55}," zvalidator",[42,1100,352],{"class":342},[42,1102,716],{"class":355},[42,1104,1105],{"class":719},"source",[42,1107,1108],{"class":342},":",[42,1110,1111],{"class":48}," any",[42,1113,366],{"class":355},[42,1115,1116],{"class":719},"schema",[42,1118,1108],{"class":342},[42,1120,1111],{"class":48},[42,1122,728],{"class":355},[42,1124,731],{"class":342},[42,1126,356],{"class":355},[42,1128,1129,1132,1135,1138,1141,1143,1145,1147,1150,1152,1154],{"class":44,"line":95},[42,1130,1131],{"class":342},"  return",[42,1133,1134],{"class":55}," zValidator",[42,1136,1137],{"class":355},"(source, schema, (",[42,1139,1140],{"class":719},"result",[42,1142,366],{"class":355},[42,1144,720],{"class":719},[42,1146,1108],{"class":342},[42,1148,1149],{"class":55}," Context",[42,1151,728],{"class":355},[42,1153,731],{"class":342},[42,1155,356],{"class":355},[42,1157,1158,1160,1162,1164],{"class":44,"line":107},[42,1159,822],{"class":342},[42,1161,716],{"class":355},[42,1163,827],{"class":342},[42,1165,1166],{"class":355},"result.success) {\n",[42,1168,1169,1172,1175,1177,1180,1183,1186,1189,1191,1193,1195,1197,1200,1202,1205,1208,1211,1214,1217,1220,1222,1224,1227,1230,1233,1236,1238,1241],{"class":44,"line":115},[42,1170,1171],{"class":342},"      const",[42,1173,1174],{"class":48}," errMsg",[42,1176,352],{"class":342},[42,1178,1179],{"class":355}," result.error.errors.",[42,1181,1182],{"class":55},"map",[42,1184,1185],{"class":355},"((",[42,1187,1188],{"class":719},"e",[42,1190,1108],{"class":342},[42,1192,1111],{"class":48},[42,1194,728],{"class":355},[42,1196,731],{"class":342},[42,1198,1199],{"class":59}," `field:${",[42,1201,1188],{"class":355},[42,1203,1204],{"class":59},".",[42,1206,1207],{"class":355},"path",[42,1209,1210],{"class":59},"[",[42,1212,1213],{"class":48},"0",[42,1215,1216],{"class":59},"]",[42,1218,1219],{"class":59},"} - ${",[42,1221,1188],{"class":355},[42,1223,1204],{"class":59},[42,1225,1226],{"class":355},"message",[42,1228,1229],{"class":59},"}`",[42,1231,1232],{"class":355},").",[42,1234,1235],{"class":55},"join",[42,1237,590],{"class":355},[42,1239,1240],{"class":59},"', '",[42,1242,601],{"class":355},[42,1244,1245,1247,1249,1251,1253],{"class":44,"line":123},[42,1246,835],{"class":355},[42,1248,587],{"class":55},[42,1250,590],{"class":355},[42,1252,842],{"class":59},[42,1254,1255],{"class":355},", errMsg)\n",[42,1257,1258,1260,1262,1264,1266,1268,1270],{"class":44,"line":131},[42,1259,835],{"class":355},[42,1261,587],{"class":55},[42,1263,590],{"class":355},[42,1265,593],{"class":59},[42,1267,596],{"class":355},[42,1269,492],{"class":48},[42,1271,601],{"class":355},[42,1273,1274,1276,1278,1280,1282,1285],{"class":44,"line":139},[42,1275,870],{"class":342},[42,1277,873],{"class":342},[42,1279,876],{"class":55},[42,1281,590],{"class":355},[42,1283,1284],{"class":48},"400",[42,1286,1287],{"class":355},", { message: errMsg })\n",[42,1289,1290],{"class":44,"line":147},[42,1291,888],{"class":355},[42,1293,1294],{"class":44,"line":157},[42,1295,978],{"class":355},[42,1297,1298],{"class":44,"line":165},[42,1299,1300],{"class":355},"};\n",[15,1302,1303,1304,326,1307,1310,1311,1313,1314],{},"然后再把schame抽出去，在",[270,1305,1306],{},"模块目录",[39,1308,1309],{},"schame.ts","，因为这块不是公共的，是每个模块每个接口都有可能不一样。就类似路由白名单也样，我也没把它放在",[39,1312,257],{},"下，而是都放在了",[270,1315,1316],{},"模块目录下。",[33,1318,1320],{"className":333,"code":1319,"language":335,"meta":13,"style":13},"// 对象\nexport const userSchema = z.object({\n  name: z.string(),\n  desc: z.string().optional().default(''),\n  desc2: z.string().default(''),\n})\n\n// 列表\nexport const usersSchema = z.array(userSchema)\n\n// 修改\nexport const sauceUpdateSchema = userSchema.extend({\n  id: z.string().min(1)\n})\n",[39,1321,1322,1327,1346,1357,1383,1400,1404,1408,1413,1432,1436,1441,1460,1479],{"__ignoreMap":13},[42,1323,1324],{"class":44,"line":45},[42,1325,1326],{"class":369},"// 对象\n",[42,1328,1329,1331,1333,1336,1338,1341,1344],{"class":44,"line":52},[42,1330,343],{"class":342},[42,1332,346],{"class":342},[42,1334,1335],{"class":48}," userSchema",[42,1337,352],{"class":342},[42,1339,1340],{"class":355}," z.",[42,1342,1343],{"class":55},"object",[42,1345,677],{"class":355},[42,1347,1348,1351,1354],{"class":44,"line":63},[42,1349,1350],{"class":355},"  name: z.",[42,1352,1353],{"class":55},"string",[42,1355,1356],{"class":355},"(),\n",[42,1358,1359,1362,1364,1367,1370,1372,1375,1377,1380],{"class":44,"line":71},[42,1360,1361],{"class":355},"  desc: z.",[42,1363,1353],{"class":55},[42,1365,1366],{"class":355},"().",[42,1368,1369],{"class":55},"optional",[42,1371,1366],{"class":355},[42,1373,1374],{"class":55},"default",[42,1376,590],{"class":355},[42,1378,1379],{"class":59},"''",[42,1381,1382],{"class":355},"),\n",[42,1384,1385,1388,1390,1392,1394,1396,1398],{"class":44,"line":79},[42,1386,1387],{"class":355},"  desc2: z.",[42,1389,1353],{"class":55},[42,1391,1366],{"class":355},[42,1393,1374],{"class":55},[42,1395,590],{"class":355},[42,1397,1379],{"class":59},[42,1399,1382],{"class":355},[42,1401,1402],{"class":44,"line":87},[42,1403,691],{"class":355},[42,1405,1406],{"class":44,"line":95},[42,1407,455],{"emptyLinePlaceholder":454},[42,1409,1410],{"class":44,"line":107},[42,1411,1412],{"class":369},"// 列表\n",[42,1414,1415,1417,1419,1422,1424,1426,1429],{"class":44,"line":115},[42,1416,343],{"class":342},[42,1418,346],{"class":342},[42,1420,1421],{"class":48}," usersSchema",[42,1423,352],{"class":342},[42,1425,1340],{"class":355},[42,1427,1428],{"class":55},"array",[42,1430,1431],{"class":355},"(userSchema)\n",[42,1433,1434],{"class":44,"line":123},[42,1435,455],{"emptyLinePlaceholder":454},[42,1437,1438],{"class":44,"line":131},[42,1439,1440],{"class":369},"// 修改\n",[42,1442,1443,1445,1447,1450,1452,1455,1458],{"class":44,"line":139},[42,1444,343],{"class":342},[42,1446,346],{"class":342},[42,1448,1449],{"class":48}," sauceUpdateSchema",[42,1451,352],{"class":342},[42,1453,1454],{"class":355}," userSchema.",[42,1456,1457],{"class":55},"extend",[42,1459,677],{"class":355},[42,1461,1462,1465,1467,1469,1472,1474,1477],{"class":44,"line":147},[42,1463,1464],{"class":355},"  id: z.",[42,1466,1353],{"class":55},[42,1468,1366],{"class":355},[42,1470,1471],{"class":55},"min",[42,1473,590],{"class":355},[42,1475,1476],{"class":48},"1",[42,1478,601],{"class":355},[42,1480,1481],{"class":44,"line":157},[42,1482,691],{"class":355},[15,1484,1485],{},"我目前用的语法就这几个，一个普通对象，一个数据对象，一个继承方法用来抽离一些公共的schame。",[15,1487,1488],{},"抽出一部分逻辑后，目录现在是这样的，清晰了很多，目前除了要完善具体逻辑，应该没有目录上的改动了",[33,1490,1492],{"className":35,"code":1491,"language":37,"meta":13,"style":13},".\n├── Dockerfile\n├── README.md\n├── bun.lockb\n├── bunfig.toml\n├── db\n│   └── zzaoclub.db\n├── docker-compose.dev.yml\n├── docker-compose.prod.yml\n├── docker-compose.yml\n├── logs\n├── out\n│   └── index.js\n├── package.json\n├── src\n│   ├── common\n│   │   ├── errorCode.ts\n│   │   ├── logger.ts\n│   │   ├── responseFormatter.ts\n│   │   └── validator.ts\n│   ├── database\n│   │   └── sqlite.ts\n│   ├── index.ts\n│   ├── salt\n│   │   ├── config.ts\n│   │   ├── crud.ts\n│   │   ├── index.ts\n│   │   ├── readme.md\n│   │   └── schema.ts\n│   └── user\n│       ├── crud.ts\n│       ├── index.ts\n│       └── schema.ts\n└── tsconfig.json\n",[39,1493,1494,1498,1504,1510,1516,1522,1528,1536,1542,1548,1554,1560,1566,1574,1580,1586,1594,1606,1617,1628,1639,1647,1658,1666,1674,1685,1696,1706,1717,1729,1738,1748,1757,1767],{"__ignoreMap":13},[42,1495,1496],{"class":44,"line":45},[42,1497,49],{"class":48},[42,1499,1500,1502],{"class":44,"line":52},[42,1501,56],{"class":55},[42,1503,60],{"class":59},[42,1505,1506,1508],{"class":44,"line":63},[42,1507,56],{"class":55},[42,1509,68],{"class":59},[42,1511,1512,1514],{"class":44,"line":71},[42,1513,56],{"class":55},[42,1515,76],{"class":59},[42,1517,1518,1520],{"class":44,"line":79},[42,1519,56],{"class":55},[42,1521,84],{"class":59},[42,1523,1524,1526],{"class":44,"line":87},[42,1525,56],{"class":55},[42,1527,92],{"class":59},[42,1529,1530,1532,1534],{"class":44,"line":95},[42,1531,98],{"class":55},[42,1533,101],{"class":59},[42,1535,104],{"class":59},[42,1537,1538,1540],{"class":44,"line":107},[42,1539,56],{"class":55},[42,1541,112],{"class":59},[42,1543,1544,1546],{"class":44,"line":115},[42,1545,56],{"class":55},[42,1547,120],{"class":59},[42,1549,1550,1552],{"class":44,"line":123},[42,1551,56],{"class":55},[42,1553,128],{"class":59},[42,1555,1556,1558],{"class":44,"line":131},[42,1557,56],{"class":55},[42,1559,136],{"class":59},[42,1561,1562,1564],{"class":44,"line":139},[42,1563,56],{"class":55},[42,1565,144],{"class":59},[42,1567,1568,1570,1572],{"class":44,"line":147},[42,1569,98],{"class":55},[42,1571,101],{"class":59},[42,1573,154],{"class":59},[42,1575,1576,1578],{"class":44,"line":157},[42,1577,56],{"class":55},[42,1579,162],{"class":59},[42,1581,1582,1584],{"class":44,"line":165},[42,1583,56],{"class":55},[42,1585,170],{"class":59},[42,1587,1588,1590,1592],{"class":44,"line":173},[42,1589,98],{"class":55},[42,1591,178],{"class":59},[42,1593,181],{"class":59},[42,1595,1596,1598,1601,1603],{"class":44,"line":184},[42,1597,98],{"class":55},[42,1599,1600],{"class":59},"   │",[42,1602,178],{"class":59},[42,1604,1605],{"class":59}," errorCode.ts\n",[42,1607,1608,1610,1612,1614],{"class":44,"line":194},[42,1609,98],{"class":55},[42,1611,1600],{"class":59},[42,1613,178],{"class":59},[42,1615,1616],{"class":59}," logger.ts\n",[42,1618,1619,1621,1623,1625],{"class":44,"line":204},[42,1620,98],{"class":55},[42,1622,1600],{"class":59},[42,1624,178],{"class":59},[42,1626,1627],{"class":59}," responseFormatter.ts\n",[42,1629,1630,1632,1634,1636],{"class":44,"line":214},[42,1631,98],{"class":55},[42,1633,1600],{"class":59},[42,1635,101],{"class":59},[42,1637,1638],{"class":59}," validator.ts\n",[42,1640,1641,1643,1645],{"class":44,"line":224},[42,1642,98],{"class":55},[42,1644,178],{"class":59},[42,1646,191],{"class":59},[42,1648,1649,1651,1653,1655],{"class":44,"line":928},[42,1650,98],{"class":55},[42,1652,1600],{"class":59},[42,1654,101],{"class":59},[42,1656,1657],{"class":59}," sqlite.ts\n",[42,1659,1660,1662,1664],{"class":44,"line":945},[42,1661,98],{"class":55},[42,1663,178],{"class":59},[42,1665,201],{"class":59},[42,1667,1668,1670,1672],{"class":44,"line":960},[42,1669,98],{"class":55},[42,1671,178],{"class":59},[42,1673,211],{"class":59},[42,1675,1676,1678,1680,1682],{"class":44,"line":965},[42,1677,98],{"class":55},[42,1679,1600],{"class":59},[42,1681,178],{"class":59},[42,1683,1684],{"class":59}," config.ts\n",[42,1686,1687,1689,1691,1693],{"class":44,"line":975},[42,1688,98],{"class":55},[42,1690,1600],{"class":59},[42,1692,178],{"class":59},[42,1694,1695],{"class":59}," crud.ts\n",[42,1697,1698,1700,1702,1704],{"class":44,"line":981},[42,1699,98],{"class":55},[42,1701,1600],{"class":59},[42,1703,178],{"class":59},[42,1705,201],{"class":59},[42,1707,1708,1710,1712,1714],{"class":44,"line":986},[42,1709,98],{"class":55},[42,1711,1600],{"class":59},[42,1713,178],{"class":59},[42,1715,1716],{"class":59}," readme.md\n",[42,1718,1720,1722,1724,1726],{"class":44,"line":1719},29,[42,1721,98],{"class":55},[42,1723,1600],{"class":59},[42,1725,101],{"class":59},[42,1727,1728],{"class":59}," schema.ts\n",[42,1730,1732,1734,1736],{"class":44,"line":1731},30,[42,1733,98],{"class":55},[42,1735,101],{"class":59},[42,1737,221],{"class":59},[42,1739,1741,1743,1746],{"class":44,"line":1740},31,[42,1742,98],{"class":55},[42,1744,1745],{"class":59},"       ├──",[42,1747,1695],{"class":59},[42,1749,1751,1753,1755],{"class":44,"line":1750},32,[42,1752,98],{"class":55},[42,1754,1745],{"class":59},[42,1756,201],{"class":59},[42,1758,1760,1762,1765],{"class":44,"line":1759},33,[42,1761,98],{"class":55},[42,1763,1764],{"class":59},"       └──",[42,1766,1728],{"class":59},[42,1768,1770,1772],{"class":44,"line":1769},34,[42,1771,227],{"class":55},[42,1773,230],{"class":59},[25,1775,1777],{"id":1776},"优化点5-env","优化点5： Env",[15,1779,1780,1781,1784],{},"在开发项目时，可能会随手写一些变量，这些变量在开发和正式环境下是肯定不一样的，所以我要把它放在",[39,1782,1783],{},".env","中，以便后续部署后也能正常使用。",[15,1786,1787,1788,1791],{},"最典型的就是",[39,1789,1790],{},"winston","，本地日志文件的路径和正式服务器上分别配置好",[15,1793,1794],{},[39,1795,1796],{},".env.production",[33,1798,1800],{"className":333,"code":1799,"language":335,"meta":13,"style":13},"LOG_DIR=/usr/src/app/prod-logs\n",[39,1801,1802],{"__ignoreMap":13},[42,1803,1804,1807,1810,1813,1816,1818,1820,1823,1825,1828,1831],{"class":44,"line":45},[42,1805,1806],{"class":48},"LOG_DIR",[42,1808,1809],{"class":342},"=/",[42,1811,1812],{"class":355},"usr",[42,1814,1815],{"class":342},"/",[42,1817,236],{"class":355},[42,1819,1815],{"class":342},[42,1821,1822],{"class":355},"app",[42,1824,1815],{"class":342},[42,1826,1827],{"class":355},"prod",[42,1829,1830],{"class":342},"-",[42,1832,1833],{"class":355},"logs\n",[15,1835,1836],{},[39,1837,1838],{},".env.development",[33,1840,1842],{"className":333,"code":1841,"language":335,"meta":13,"style":13},"LOG_DIR=logs2/\n",[39,1843,1844],{"__ignoreMap":13},[42,1845,1846,1848,1851,1854],{"class":44,"line":45},[42,1847,1806],{"class":48},[42,1849,1850],{"class":342},"=",[42,1852,1853],{"class":355},"logs2",[42,1855,1856],{"class":342},"/\n",[15,1858,1859,1860,1863,1864],{},"然后在",[39,1861,1862],{},"common/logger.ts","中替换成对应的",[39,1865,1866],{},"env变量",[33,1868,1870],{"className":333,"code":1869,"language":335,"meta":13,"style":13},"const LOG_DIR = Bun.env.LOG_DIR || 'logs/';\n...\ntransports: [\n    new DailyRotateFile({\n            filename: path.join(LOG_DIR, 'info-%DATE%.log'),\n  ...\n",[39,1871,1872,1892,1897,1905,1915,1933],{"__ignoreMap":13},[42,1873,1874,1876,1879,1881,1883,1885,1887,1890],{"class":44,"line":45},[42,1875,645],{"class":342},[42,1877,1878],{"class":48}," LOG_DIR",[42,1880,352],{"class":342},[42,1882,653],{"class":355},[42,1884,1806],{"class":48},[42,1886,659],{"class":342},[42,1888,1889],{"class":59}," 'logs/'",[42,1891,766],{"class":355},[42,1893,1894],{"class":44,"line":52},[42,1895,1896],{"class":342},"...\n",[42,1898,1899,1902],{"class":44,"line":63},[42,1900,1901],{"class":55},"transports",[42,1903,1904],{"class":355},": [\n",[42,1906,1907,1910,1913],{"class":44,"line":71},[42,1908,1909],{"class":342},"    new",[42,1911,1912],{"class":55}," DailyRotateFile",[42,1914,677],{"class":355},[42,1916,1917,1920,1922,1924,1926,1928,1931],{"class":44,"line":79},[42,1918,1919],{"class":355},"            filename: path.",[42,1921,1235],{"class":55},[42,1923,590],{"class":355},[42,1925,1806],{"class":48},[42,1927,366],{"class":355},[42,1929,1930],{"class":59},"'info-%DATE%.log'",[42,1932,1382],{"class":355},[42,1934,1935],{"class":44,"line":87},[42,1936,1937],{"class":342},"  ...\n",[15,1939,1940],{},"后面等开始部署时，再来验证配置是否生效。",[15,1942,1943,1944,618,1946,1949],{},"然后",[39,1945,242],{},[39,1947,1948],{},"port","，也可以配置一下，开发和正式没必要一样，尤其是选择开源的话。",[33,1951,1953],{"className":333,"code":1952,"language":335,"meta":13,"style":13},"export default {\n  port: Bun.env.PORT,\n  fetch: app.fetch,\n}\n",[39,1954,1955,1964,1974,1979],{"__ignoreMap":13},[42,1956,1957,1959,1962],{"class":44,"line":45},[42,1958,343],{"class":342},[42,1960,1961],{"class":342}," default",[42,1963,356],{"class":355},[42,1965,1966,1969,1972],{"class":44,"line":52},[42,1967,1968],{"class":355},"  port: Bun.env.",[42,1970,1971],{"class":48},"PORT",[42,1973,485],{"class":355},[42,1975,1976],{"class":44,"line":63},[42,1977,1978],{"class":355},"  fetch: app.fetch,\n",[42,1980,1981],{"class":44,"line":71},[42,1982,449],{"class":355},[15,1984,1985,1986],{},"然后就是JWT的",[39,1987,1988],{},"secret",[33,1990,1992],{"className":333,"code":1991,"language":335,"meta":13,"style":13},"const JWT_SECRET = Bun.env.JWT_SECRET || '1234567'\n",[39,1993,1994],{"__ignoreMap":13},[42,1995,1996,1998,2000,2002,2004,2006,2008],{"class":44,"line":45},[42,1997,645],{"class":342},[42,1999,648],{"class":48},[42,2001,352],{"class":342},[42,2003,653],{"class":355},[42,2005,656],{"class":48},[42,2007,659],{"class":342},[42,2009,2010],{"class":59}," '1234567'\n",[15,2012,2013,2014,2017,2018,2020],{},"修改并替换好后，还要记得在",[39,2015,2016],{},".gitignore","中把",[39,2019,1796],{},"加上，就不要提交到仓库里去了。",[15,2022,2023],{},"还有日志文件，也没必要提交上去",[25,2025,2026],{"id":2026},"总结",[15,2028,2029],{},"虽然代码没多少，但是基本的封装和目录划分还是要提前思考一下。",[15,2031,2032],{},"总体的思路就是：",[15,2034,2035,2036,2039,2040,2043],{},"一、整个项目公用的提取到",[39,2037,2038],{},"src/common","，每个模块公用的放在",[39,2041,2042],{},"src/模块/","下就近管理，不出现重复代码",[15,2045,2046],{},"二、代码中不要出现不清不楚的常量，每个常量要定义好语意化的枚举、Map等变量引入的方式使用",[15,2048,2049,2050,2053],{},"三、区分环境的配置进一步提取到",[39,2051,2052],{},"env","文件或其他配置中心服务",[15,2055,2056,2057,2060],{},"ok，就这样。 以后的问题碰到再解决即可（比如现在TS用了很多",[39,2058,2059],{},"any","）。",[2062,2063,2064],"style",{},"html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":13,"searchDepth":52,"depth":52,"links":2066},[2067,2068,2069,2070,2071,2072],{"id":27,"depth":52,"text":28},{"id":302,"depth":52,"text":303},{"id":607,"depth":52,"text":608},{"id":1005,"depth":52,"text":1006},{"id":1776,"depth":52,"text":1777},{"id":2026,"depth":52,"text":2026},"2025-02-10T00:00:00.000Z","md","2025-02-12T00:00:00.000Z",{},"/post/hono/hono-feat-config-common-utils","---\ntitle: 【Hono】优化：提取配置项及公共函数\ndate: 2025-02-10\nlastmod: 2025-02-12\ntags: [\"Hono\"]\nversions: [\"hono@4.5.11\"]\nshowTitle: 【Hono】优化：提取配置项及公共函数\n---\n#\n\n项目完成的七七八八了，代码也慢慢多了起来，有些基本的优化工作必须要做了。\n\n不然等再加一些业务逻辑，就会变得非常臃肿，然后就又免不了被抛弃的命运。\n\n写自己的玩具就是这样的，总是在不停的造玩具。\n\n## 优化点1：ErrorHandler\n\n目前的目录是这样的\n\n```shell\n.\n├── Dockerfile\n├── README.md\n├── bun.lockb\n├── bunfig.toml\n├── db\n│   └── zzaoclub.db\n├── docker-compose.dev.yml\n├── docker-compose.prod.yml\n├── docker-compose.yml\n├── logs\n├── out\n│   └── index.js\n├── package.json\n├── src\n│   ├── common\n│   ├── database\n│   ├── index.ts\n│   ├── salt\n│   └── user\n└── tsconfig.json\n```\n\n目前只关注`src`下的结构即可。\n\n`index.ts`作为入口文件，加载了一些全局中间价，以及去挂载子路由，拦截全局的`error`。\n\n这里能抽出去一个`errorHandler`，我把它放在`common`下。\n\n它的功能也很简单：\n\n1. 拦截到错误后，把**Http状态码**设置为`200`\n\n2. 在控制台/日志文件中打印/记录请求时间+方式+url+原始错误信息\n\n3. 在上下文中使用`c.get('errCode')`取出在某处抛出的自定义错误，然后把**自定义的错误码和错误信息或默认的错误信息**返回给前端\n\n4. 统一返回标准如:`{ code: 40001, data: xxx, msg: '1123131' }`\n\n拆好后重新引入测试，然后就已经出现了下一个优化点\n\n## 优化点2：ErrorCode\n\n刚才拆处errorHandler后，发现会取一些默认的错误信息，以及有一套自定义的错误嘛。\n\n所以要提前定义好一套，不然随手就写个`40001`、`40002`、`40003`，时间长了鬼知道这是什么意思。\n\n所以再从`common`下新建一个`errorCode.ts` ，我只是举个例子，错误码和信息要自己看着来。\n\n```typescript\nexport const ErrorCode = {\n  PERMISSION_DENIED: 40001, // 权限问题\n  VALIDATION_ERROR: 40002, // 参数问题\n  UNAUTHORIZED: 40003, // 未登录\n  LOGIN_EXPIRED: 40004, // 登录已过期\n  NOT_FOUND: 40005, // 不存在的接口\n  INTERNAL_SERVER_ERROR: 50000, // 服务器内部错误\n  UNKOWN_ERROR: 50001 // 未知错误\n}\n\nexport const ErrorCodeMsg = {\n  [ErrorCode.PERMISSION_DENIED]: '权限问题',\n  [ErrorCode.VALIDATION_ERROR]: '参数问题',\n  [ErrorCode.UNAUTHORIZED]: '未登录',\n  [ErrorCode.LOGIN_EXPIRED]: '登录已过期',\n  [ErrorCode.NOT_FOUND]: '不存在的接口',\n  [ErrorCode.INTERNAL_SERVER_ERROR]: '服务器内部错误',\n  [ErrorCode.UNKOWN_ERROR]: '未知错误'\n}\n```\n\n这样在某个路由抛出错误时，应该是这样的\n\n```typescript\n c.set('errCode', ErrorCode.UNAUTHORIZED)\n```\n\n然后接下来再去看看已有逻辑里，哪里需要抛出错误，把这些自定义的错误码给用上。\n\n## 优化点3：JWT相关\n\n刚才在替换错误码时，发现JWT和Zod的相关逻辑里需要抛出一些异常。而有了两个模块后，也会发现他们有一些重复代码。\n\n比如`user`中的**jwt中间件**和`salt`模块中是一样的，没必要写两份，可以把jwt中间件的逻辑抽出，放在`index.ts`中，目前来看，jwt只是校验一下用户有没有登录，以及跳过一些`路由白名单`。\n\n所以可以先把jwt逻辑抽出来，以下是一个参考\n\n```typescript\nconst JWT_SECRET = Bun.env.JWT_SECRET || ''\nconst jwtMiddware = jwt({\n  secret: JWT_SECRET,\n})\n\nsalt.use('/*', async (c, next) => {\n  if (NoAuthPaths.includes(c.req.path)) {\n    await next();\n    return;\n  }\n  await jwtMiddware(c, async () => {\n    const user = c.get('jwtPayload')\n    \n    if (!user) {\n      c.set('errMsg', '用户未登录')\n      c.set('errCode', ErrorCode.UNAUTHORIZED)\n      throw new HTTPException(401)\n    }\n\n    if (user.id !== 1) {\n      c.set('errMsg', '用户无权限')\n      c.set('errCode', ErrorCode.PERMISSION_DENIED)\n      throw new HTTPException(401)\n    }\n    await next()\n  })\n\n})\n```\n\n把后面函数单独抽离出去即可，同时我在每个模块下如`src/user`、`src/salt`下再放一个`common.ts`，把路由白名单（NoAuthPaths）放进去，每个模块在使用jwt中间件时，再把这个白名单传进去。\n\n## 优化点4：Zod相关\n\n看到路由的参数校验那一串，就知道不得不优化一下，因为不可能每个接口直接写那么一大串schame。\n\n抽离分两部分，一部分是validator函数，一部分是schame的定义。\n\n先来validator，在`common`下再新建个`validator.ts`，封装一个zvalidator函数，传参就按zValidator需要什么就行，主要是为了抛出错误。\n\n以下也只是个示例：\n\n```typescript\nimport { zValidator } from \"@hono/zod-validator\"\nimport { Context, Next } from \"hono\"\nimport { ErrorCode, ErrorCodeMsg } from \"./errorCode\";\nimport { HTTPException } from \"hono/http-exception\";\n// 自定义校验, 在校验失败时抛出异常, 由errorHanlder统一处理\nexport const zvalidator = (source: any, schema: any) => {\n  return zValidator(source, schema, (result, c: Context) => {\n    if (!result.success) {\n      const errMsg = result.error.errors.map((e: any) => `field:${e.path[0]} - ${e.message}`).join(', ')\n      c.set('errMsg', errMsg)\n      c.set('errCode', ErrorCode.VALIDATION_ERROR)\n      throw new HTTPException(400, { message: errMsg })\n    }\n  })\n};\n```\n\n然后再把schame抽出去，在**模块目录**下新建一个`schame.ts`，因为这块不是公共的，是每个模块每个接口都有可能不一样。就类似路由白名单也样，我也没把它放在`common`下，而是都放在了**模块目录下。**\n\n```typescript\n// 对象\nexport const userSchema = z.object({\n  name: z.string(),\n  desc: z.string().optional().default(''),\n  desc2: z.string().default(''),\n})\n\n// 列表\nexport const usersSchema = z.array(userSchema)\n\n// 修改\nexport const sauceUpdateSchema = userSchema.extend({\n  id: z.string().min(1)\n})\n```\n\n我目前用的语法就这几个，一个普通对象，一个数据对象，一个继承方法用来抽离一些公共的schame。\n\n抽出一部分逻辑后，目录现在是这样的，清晰了很多，目前除了要完善具体逻辑，应该没有目录上的改动了\n\n```shell\n.\n├── Dockerfile\n├── README.md\n├── bun.lockb\n├── bunfig.toml\n├── db\n│   └── zzaoclub.db\n├── docker-compose.dev.yml\n├── docker-compose.prod.yml\n├── docker-compose.yml\n├── logs\n├── out\n│   └── index.js\n├── package.json\n├── src\n│   ├── common\n│   │   ├── errorCode.ts\n│   │   ├── logger.ts\n│   │   ├── responseFormatter.ts\n│   │   └── validator.ts\n│   ├── database\n│   │   └── sqlite.ts\n│   ├── index.ts\n│   ├── salt\n│   │   ├── config.ts\n│   │   ├── crud.ts\n│   │   ├── index.ts\n│   │   ├── readme.md\n│   │   └── schema.ts\n│   └── user\n│       ├── crud.ts\n│       ├── index.ts\n│       └── schema.ts\n└── tsconfig.json\n```\n\n## 优化点5： Env\n\n在开发项目时，可能会随手写一些变量，这些变量在开发和正式环境下是肯定不一样的，所以我要把它放在`.env`中，以便后续部署后也能正常使用。\n\n最典型的就是`winston`，本地日志文件的路径和正式服务器上分别配置好\n\n`.env.production`\n\n```typescript\nLOG_DIR=/usr/src/app/prod-logs\n```\n\n`.env.development`\n\n```typescript\nLOG_DIR=logs2/\n```\n\n然后在`common/logger.ts`中替换成对应的`env变量`\n\n```typescript\nconst LOG_DIR = Bun.env.LOG_DIR || 'logs/';\n...\ntransports: [\n    new DailyRotateFile({\n            filename: path.join(LOG_DIR, 'info-%DATE%.log'),\n  ...\n```\n\n后面等开始部署时，再来验证配置是否生效。\n\n然后`index.ts`中的`port`，也可以配置一下，开发和正式没必要一样，尤其是选择开源的话。\n\n```typescript\nexport default {\n  port: Bun.env.PORT,\n  fetch: app.fetch,\n}\n```\n\n然后就是JWT的`secret`\n\n```typescript\nconst JWT_SECRET = Bun.env.JWT_SECRET || '1234567'\n```\n\n修改并替换好后，还要记得在`.gitignore`中把`.env.production`加上，就不要提交到仓库里去了。\n\n还有日志文件，也没必要提交上去\n\n## 总结\n\n虽然代码没多少，但是基本的封装和目录划分还是要提前思考一下。\n\n总体的思路就是：\n\n一、整个项目公用的提取到`src/common`，每个模块公用的放在`src/模块/`下就近管理，不出现重复代码\n\n二、代码中不要出现不清不楚的常量，每个常量要定义好语意化的枚举、Map等变量引入的方式使用\n\n三、区分环境的配置进一步提取到`env`文件或其他配置中心服务\n\nok，就这样。 以后的问题碰到再解决即可（比如现在TS用了很多`any`）。\n",{"title":5,"description":17},"post/Hono/hono-feat-config-common-utils",[2082],"Hono",[2084],"hono@4.5.11","qSKq-alg6Xu-AXBRmCodORBwF-CgUm2MdfSEQZ4Ocu4",[2087,2091],{"title":2088,"path":2089,"stem":2090},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":2092,"path":2093,"stem":2094},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005086185]