新闻详情

新闻详情

首页 / 资讯中心 / 详情

11 — 完整 CRUD 示例

发布时间:2026/11/15 15:00:09来源:尧图网络
11 — 完整 CRUD 示例
11.1 这一篇做什么前面几篇分别讲了每一层的写法这一篇把它们串起来从零写完一个项目管理模块的完整 CRUD。读完这篇你应该能照着写出其他任何模块。11.2 要实现的接口方法URL功能GET/api/v1/projects分页查询项目列表支持按名称搜索GET/api/v1/projects/:id查询单个项目详情POST/api/v1/projects新建项目PUT/api/v1/projects/:id更新项目DELETE/api/v1/projects/:id删除项目统一响应格式{ status: 0, data: ... }11.3 第一步定义数据模型src/models/project/project.tsexportinterfaceProject{id:number;name:string;description:string;status:active|inactive;created_at:number;updated_at:number;}11.4 第二步写 Model 层src/models/project/project-model.tsimporttype{Project}from./project.js;exportclassProjectModel{#projects:Project[];constructor(initialData:readonlyProject[][]){this.#projectsinitialData.map((p)structuredClone(p));}list():Project[]{returnstructuredClone(this.#projects);}findById(id:number):Project|undefined{constprojectthis.#projects.find((p)p.idid);returnproject?structuredClone(project):undefined;}findByName(name:string):Project|undefined{constprojectthis.#projects.find((p)p.namename);returnproject?structuredClone(project):undefined;}create(input:PickProject,name|description|status):Project{constnowMath.floor(Date.now()/1000);constproject:Project{...input,id:this.#nextId(),created_at:now,updated_at:now,};this.#projects.push(structuredClone(project));returnstructuredClone(project);}update(id:number,input:PartialPickProject,name|description|status,):Project|undefined{constprojectthis.#projects.find((p)p.idid);if(!project)returnundefined;if(input.name!undefined)project.nameinput.name;if(input.description!undefined)project.descriptioninput.description;if(input.status!undefined)project.statusinput.status;project.updated_atMath.floor(Date.now()/1000);returnstructuredClone(project);}delete(id:number):boolean{constindexthis.#projects.findIndex((p)p.idid);if(index0)returnfalse;this.#projects.splice(index,1);returntrue;}#nextId():number{if(this.#projects.length0)return1;returnMath.max(...this.#projects.map((p)p.id))1;}}11.5 第三步准备初始数据src/data/project/index.tsimporttype{Project}from/models/project/project.js;constCREATED_AT1_700_000_000;exportconstprojectData:readonlyProject[][{id:1,name:示例项目A,description:这是第一个示例项目,status:active,created_at:CREATED_AT,updated_at:CREATED_AT,},{id:2,name:示例项目B,description:这是第二个示例项目,status:inactive,created_at:CREATED_AT,updated_at:CREATED_AT,},];11.6 第四步写 Service 层src/services/project/project-service.tsimport{AppError}from/errors/app-error.js;importtype{ProjectModel}from/models/project/project-model.js;importtype{Project}from/models/project/project.js;exportclassProjectService{constructor(privatereadonlyprojects:ProjectModel){}// 查询列表支持按名称模糊搜索list(name):Project[]{constallthis.projects.list();if(!name)returnall;constlowername.toLowerCase();returnall.filter((p)p.name.toLowerCase().includes(lower));}// 查询详情getById(id:number):Project{constprojectthis.projects.findById(id);if(!project){thrownewAppError(项目不存在,{status:2});}returnproject;}// 新建create(body:Recordstring,unknown):Project{constnamethis.#requireString(body.name,项目名称不能为空);constdescriptiontypeofbody.descriptionstring?body.description:;conststatusbody.statusinactive?inactive:active;if(this.projects.findByName(name)){thrownewAppError(项目名称已存在,{status:2});}returnthis.projects.create({name,description,status});}// 更新update(id:number,body:Recordstring,unknown):Project{constcurrentthis.projects.findById(id);if(!current){thrownewAppError(项目不存在,{status:2});}constnamebody.name!undefined?this.#requireString(body.name,项目名称不能为空):undefined;if(namename!current.name){if(this.projects.findByName(name)){thrownewAppError(项目名称已存在,{status:2});}}constupdatedthis.projects.update(id,{name,description:typeofbody.descriptionstring?body.description:undefined,status:body.statusinactive||body.statusactive?body.status:undefined,});if(!updated){thrownewAppError(更新失败,{status:1});}returnupdated;}// 删除delete(id:number):void{constcurrentthis.projects.findById(id);if(!current){thrownewAppError(项目不存在,{status:2});}constsuccessthis.projects.delete(id);if(!success){thrownewAppError(删除失败,{status:1});}}#requireString(value:unknown,errorMsg:string):string{conststrtypeofvaluestring?value.trim():;if(!str){thrownewAppError(errorMsg,{status:2});}returnstr;}}11.7 第五步写 Controller 层src/controllers/project/project-controller.tsimporttype{Request,Response}fromexpress;importtype{ProjectService}from/services/project/project-service.js;import{sendOk,sendList}from/utils/response.js;import{parseOffset,parseSize,parseText}from/utils/pagination.js;exportclassProjectController{constructor(privatereadonlyservice:ProjectService){}list(req:Request,res:Response){constoffsetparseOffset(req.query.offset);constsizeparseSize(req.query.size);constnameparseText(req.query.name);constallthis.service.list(name);returnsendList(res,all.slice(offset,offsetsize),{count:all.length,offset,size,});};get(req:Request,res:Response){constprojectthis.service.getById(Number(req.params.id));returnsendOk(res,project);};create(req:Request,res:Response){constprojectthis.service.create(req.body);returnsendOk(res,{id:project.id});};update(req:Request,res:Response){this.service.update(Number(req.params.id),req.body);returnsendOk(res,null);};remove(req:Request,res:Response){this.service.delete(Number(req.params.id));returnsendOk(res,null);};}11.8 第六步写 Router 层src/routers/project/project-router.tsimport{Router}fromexpress;importtype{ProjectController}from/controllers/project/project-controller.js;exportfunctioncreateProjectRouter(controller:ProjectController){constrouterRouter();router.get(/,controller.list);// GET /projectsrouter.get(/:id,controller.get);// GET /projects/:idrouter.post(/,controller.create);// POST /projectsrouter.put(/:id,controller.update);// PUT /projects/:idrouter.delete(/:id,controller.remove);// DELETE /projects/:idreturnrouter;}11.9 第七步注册到总路由src/bootstrap.ts里加上import{projectData}from./data/project/index.js;import{ProjectModel}from./models/project/project-model.js;import{ProjectService}from./services/project/project-service.js;import{ProjectController}from./controllers/project/project-controller.js;exportfunctionbootstrap(){constprojectsnewProjectModel(projectData);constprojectnewProjectController(newProjectService(projects));return{projects,controllers:{project},};}src/routers/index.ts里加上import{createProjectRouter}from./project/project-router.js;exportfunctioncreateRouter(controllers:any){constrouterRouter();router.use(/projects,createProjectRouter(controllers.project));returnrouter;}11.10 测试一下启动服务npmrun dev用 curl 测试# 列表curlhttp://localhost:3000/api/v1/projects# 搜索curlhttp://localhost:3000/api/v1/projects?nameA# 详情curlhttp://localhost:3000/api/v1/projects/1# 新建curl-XPOST http://localhost:3000/api/v1/projects\-HContent-Type: application/json\-d{name:新项目,description:测试,status:active}# 更新curl-XPUT http://localhost:3000/api/v1/projects/3\-HContent-Type: application/json\-d{name:改名后的项目}# 删除curl-XDELETE http://localhost:3000/api/v1/projects/311.11 小结一个完整的 CRUD 模块需要 6 个文件文件作用models/project/project.ts类型定义models/project/project-model.ts数据操作data/project/index.ts初始数据services/project/project-service.ts业务逻辑controllers/project/project-controller.ts请求处理routers/project/project-router.ts路由映射每一层职责单一改其中一层不影响其他层。照着这个模板写其他模块就行。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

【外设】之大彩串口显示屏 2026/11/15 4:54:16

【外设】之大彩串口显示屏

大彩串口屏初步使用 1 .官网下载 STM32 屏幕 GUI 设计资料 http://www.gz-dc.com/category/typeid/4112 找到 STM32 Keil 工程,移植相关代码因项目而异进行移植,由于项目简单,本人只对用到的指令接口进行修改。 比如:注意事项&…

阅读更多 →
无法下载Windows系统iso文件 2026/11/15 2:04:12

无法下载Windows系统iso文件

当我遇到这个问题的时候,我打开了一个网站: 登录 然后我打算下载的时候: 突然那个官方的连接就可以下载了:

阅读更多 →
【清华代码熊】DeepSeek V4.1 Flash 后训练详解 2026/11/14 10:11:15

【清华代码熊】DeepSeek V4.1 Flash 后训练详解

📌 上期解析了 DeepSeek V4.1 Flash 模型架构改进,本期解析 DeepSeek V4.1 Flash 预训练/后训练技术: 🌟 预训练:45T 文本 多模态混合语料、直接训练 sparse attention(取消 DeepSeek V4 的 dense 冷启动&…

阅读更多 →
Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ... 2026/11/14 17:14:38

Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ...

文章主要内容和创新点 主要内容 本文聚焦于多模态大语言模型(MLLM)强化学习(RL)训练中的效率问题,提出了一个名为Shuffle-R1的框架。研究发现,当前RL训练存在两个关键缺陷: 优势值坍缩(Advantage Collapsing):批次中大多数优势值集中在零附近,导致有效梯度信号被淹…

阅读更多 →
PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction 2026/11/14 16:35:50

PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction

一、文章主要内容总结 本文聚焦于利用大型语言模型(LLMs)实现个人身份信息(PII)脱敏的研究,旨在解决传统脱敏方法(如基于规则的系统、领域特定命名实体识别(NER)模型)泛化能力差、跨格式/跨语境适应性弱的问题。 研究通过全面评估多种LLM架构(包括密集型LLM(D-LLM…

阅读更多 →
LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model 2026/11/15 4:06:36

LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model

文章主要内容和创新点 主要内容 本文聚焦于二进制图像-文本相关性评估任务(判断图像与文本“相关”或“不相关”),针对该任务中文本格式多样、相关性定义随场景变化等挑战,提出了基于多模态大语言模型(MLLM)的解决方案LLaVA-RE。 模型设计:LLaVA-RE基于LLaVA 1.5架构,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉