mvc 两个控制器session 丢失_用纯 JavaScript 撸一个 MVC 程序

12c5d33ad3bb5f7ba25d017bf1428f7c.png

前言

我想用 model-view-controller 架构模式在纯 JavaScript 中写一个简单的程序,于是我这样做了。希望它可以帮你理解 MVC,因为当你刚开始接触它时,它是一个难以理解的概念。

我做了这个todo应用程序,这是一个简单小巧的浏览器应用,允许你对待办事项进行CRUD(创建,读取,更新和删除)操作。它只包含 index.htmlstyle.cssscript.js 三个文件,非常简单,无需任何依赖和框架。

先决条件

  • 基本的 JavaScript 和 HTML 知识
  • 熟悉最新的 JavaScript 语法

目标

用纯 JavaScript 在浏览器中创建一个 todo 应用程序,并熟悉MVC(和 OOP——面向对象编程)的概念。

  • 查看程序的演示
  • 查看程序的源代码
注意:由于此程序使用了最新的 JavaScript 功能(ES2017),因此在某些浏览器(如 Safari)上无法用 Babel 编译为向后兼容的 JavaScript 语法。

什么是 MVC?

MVC 是一种非常受欢迎组织代码的模式。

  • Model(模型) - 管理程序的数据
  • View(视图) - 模型的直观表示
  • Controller(控制器) - 链接用户和系统

模型是数据。在这个 todo 程序中,这将是实际的待办事项,以及将添加、编辑或删除它们的方法。

视图是数据的显示方式。在这个程序中,是 DOM 和 CSS 中呈现的 HTML。

控制器用来连接模型和视图。它需要用户输入,例如单击或键入,并处理用户交互的回调。

模型永远不会触及视图。视图永远不会触及模型。控制器用来连接它们。

我想提一下,为一个简单的 todo 程序做 MVC 实际上是一大堆样板。如果这是你想要创建的程序并且创建了整个系统,那真的会让事情变得过于复杂。关键是要尝试在较小的层面上理解它。

初始设置

这将是一个完全用 JavaScript 写的程序,这意味着一切都将通过 JavaScript 处理,HTML 将只包含根元素。

index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>Todo App</title><link rel="stylesheet" href="style.css" /></head><body><div id="root"></div><script src="script.js"></script></body>
</html>

我写了一小部分 CSS 只是为了让它看起来可以接受,你可以找到这个文件并保存到 style.css 。我不打算再写CSS了,因为它不是本文的重点。

好的,现在我们有了HTML和CSS,下面该开始编写程序了。

入门

我会使这个教程简单易懂,使你轻松了解哪个类属于 MVC 的哪个部分。我将创建一个 Model 类,View 类和 Controller 类。该程序将是控制器的实例。

如果你不熟悉类的工作方式,请阅读了解JavaScript中的类。
class Model {constructor() {}
}class View {constructor() {}
}class Controller {constructor(model, view) {this.model = modelthis.view = view}
}const app = new Controller(new Model(), new View())

模型

让我们先关注模型,因为它是三个部分中最简单的一个。它不涉及任何事件或 DOM 操作。它只是存储和修改数据。

//模型
class Model {constructor() {// The state of the model, an array of todo objects, prepopulated with some datathis.todos = [{ id: 1, text: 'Run a marathon', complete: false },{ id: 2, text: 'Plant a garden', complete: false },]}// Append a todo to the todos arrayaddTodo(todo) {this.todos = [...this.todos, todo]}// Map through all todos, and replace the text of the todo with the specified ideditTodo(id, updatedText) {this.todos = this.todos.map(todo =>todo.id === id ? { id: todo.id, text: updatedText, complete: todo.complete } : todo)}// Filter a todo out of the array by iddeleteTodo(id) {this.todos = this.todos.filter(todo => todo.id !== id)}// Flip the complete boolean on the specified todotoggleTodo(id) {this.todos = this.todos.map(todo =>todo.id === id ? { id: todo.id, text: todo.text, complete: !todo.complete } : todo)}
}

我们定义了 addTodoeditTododeleteTodotoggleTodo。这些都应该是一目了然的:add 添加到数组,edit 找到 todo 的 id 进行编辑和替换,delete 过滤数组中的todo,并切换切换 complete 布尔属性。

由于我们在浏览器中执行此操作,并且可以从窗口(全局)访问,因此你可以轻松地测试这些内容,输入以下内容:

app.model.addTodo({ id: 3, text: 'Take a nap', complete: false })

将向列表中添加一个待办事项,你可以查看 app.model.todos 的内容。

80b335256da02583f41006bacf6ed1fc.png

这对于现在的模型来说已经足够了。最后我们会将待办事项存储在 local storage 中,以使其成为半永久性的,但现在只要刷新页面,todo 就会刷新。

我们可以看到,该模型仅处理并修改实际数据。它不理解或不知道输入 —— 正在修改它,或输出 —— 最终会显示什么。

这时如果你通过控制台手动输入所有操作,并在控制台中查看输出,就可以获得功能完善的 CRUD 程序所需的一切。

视图

我们将通过操纵 DOM —— 文档对象模型来创建视图。由于没有 React 的 JSX 或模板语言的帮助,在普通的 JavaScript 中执行此操作,因此它将是冗长和丑陋的,但这是直接操纵 DOM 的本质。

控制器和模型都不应该知道关于 DOM、HTML元素、CSS 或其中任何内容的信息。任何与之相关的内容都应该放在视图中。

如果你不熟悉 DOM 或 DOM 与 HTML 源代码之间有什么不同,请阅读DOM简介。

要做的第一件事就是创建辅助方法来检索并创建元素。

//视图
class View {constructor() {}// Create an element with an optional CSS classcreateElement(tag, className) {const element = document.createElement(tag)if (className) element.classList.add(className)return element}// Retrieve an element from the DOMgetElement(selector) {const element = document.querySelector(selector)return element}
}

到目前为止还挺好。接着在构造函数中,我将为视图设置需要的所有东西:

  • 应用程序的根元素 - #root
  • 标题 h1
  • 一个表单,输入框和提交按钮,用于添加待办事项 - form, input, button
  • 待办事项清单 - ul

我将在构造函数中创建所有变量,以便可以轻松地引用它们。

//视图
class View {constructor() {// The root elementthis.app = this.getElement('#root')// The title of the appthis.title = this.createElement('h1')this.title.textContent = 'Todos'// The form, with a [type="text"] input, and a submit buttonthis.form = this.createElement('form')this.input = this.createElement('input')this.input.type = 'text'this.input.placeholder = 'Add todo'this.input.name = 'todo'this.submitButton = this.createElement('button')this.submitButton.textContent = 'Submit'// The visual representation of the todo listthis.todoList = this.createElement('ul', 'todo-list')// Append the input and submit button to the formthis.form.append(this.input, this.submitButton)// Append the title, form, and todo list to the appthis.app.append(this.title, this.form, this.todoList)}// ...
}

现在,将设置不会被更改的视图部分。

8a4c1cee7eb886f26f046b305c65ff04.png

另外两个小东西:输入(new todo)值的 getter 和 resetter。

// 视图
get todoText() {return this.input.value
}resetInput() {this.input.value = ''
}

现在所有设置都已完成。最复杂的部分是显示待办事项列表,这是每次对待办事项进行修改时将被更改的部分。

//视图
displayTodos(todos) {// ...
}

displayTodos 方法将创建待办事项列表所包含的 ulli 并显示它们。每次修改、添加或删除 todo 时,都会使用模型中的 todos 再次调用 displayTodos 方法,重置列表并重新显示它们。这将使视图与模型的状态保持同步。

我们要做的第一件事就是每次调用时删除所有 todo 节点。然后检查是否存在待办事项。如果不这样做,我们将会得到一个空的列表消息。

// 视图
// Delete all nodes
while (this.todoList.firstChild) {this.todoList.removeChild(this.todoList.firstChild)
}// Show default message
if (todos.length === 0) {const p = this.createElement('p')p.textContent = 'Nothing to do! Add a task?'this.todoList.append(p)
} else {// ...
}

现在循环遍历待办事项并为每个现有待办事项显示复选框、span 和删除按钮。

// 视图
else {// Create todo item nodes for each todo in statetodos.forEach(todo => {const li = this.createElement('li')li.id = todo.id// Each todo item will have a checkbox you can toggleconst checkbox = this.createElement('input')checkbox.type = 'checkbox'checkbox.checked = todo.complete// The todo item text will be in a contenteditable spanconst span = this.createElement('span')span.contentEditable = truespan.classList.add('editable')// If the todo is complete, it will have a strikethroughif (todo.complete) {const strike = this.createElement('s')strike.textContent = todo.textspan.append(strike)} else {// Otherwise just display the textspan.textContent = todo.text}// The todos will also have a delete buttonconst deleteButton = this.createElement('button', 'delete')deleteButton.textContent = 'Delete'li.append(checkbox, span, deleteButton)// Append nodes to the todo listthis.todoList.append(li)})
}

现在设置视图及模型。我们只是没有办法连接它们,因为现在还没有事件监视用户进行输入,也没有处理这种事件的输出的 handle。

控制台仍然作为临时控制器存在,你可以通过它添加和删除待办事项。

b26080292ee969d8c23f799b4ef251b7.png

控制器

最后,控制器是模型(数据)和视图(用户看到的内容)之间的链接。这是我们到目前为止控制器中的内容。

//控制器
class Controller {constructor(model, view) {this.model = modelthis.view = view}
}

在视图和模型之间的第一个链接是创建一个每次 todo 更改时调用 displayTodos 的方法。我们也可以在 constructor 中调用它一次,来显示初始的 todos(如果有的话)。

//控制器
class Controller {constructor(model, view) {this.model = modelthis.view = view// Display initial todosthis.onTodoListChanged(this.model.todos)}onTodoListChanged = todos => {this.view.displayTodos(todos)}
}

控制器将在触发后处理事件。当你提交新的待办事项、单击删除按钮或单击待办事项的复选框时,将触发一个事件。视图必须侦听这些事件,因为它们是视图的用户输入,它会将响应事件所要做的工作分配给控制器。

我们将为事件创建 handler。首先,提交一个 handleAddTodo 事件,当我们创建的待办事项输入表单被提交时,可以通过按 Enter 键或单击“提交”按钮来触发。这是一个 submit 事件。

回到视图中,我们将 this.input.value 的 getter 作为 get todoText。要确保输入不能为空,然后我们将创建带有 idtext 并且 complete 值为 false 的 todo。将 todo 添加到模型中,然后重置输入框。

// 控制器
// Handle submit event for adding a todo
handleAddTodo = event => {event.preventDefault()if (this.view.todoText) {const todo = {id: this.model.todos.length > 0 ? this.model.todos[this.model.todos.length - 1].id + 1 : 1,text: this.view.todoText,complete: false,}this.model.addTodo(todo)this.view.resetInput()}
}

删除 todo 的操作类似。它将响应删除按钮上的 click 事件。删除按钮的父元素是 todo li 本身,它附有相应的 id。我们需要将该数据发送给正确的模型方法。

// 控制器
// Handle click event for deleting a todo
handleDeleteTodo = event => {if (event.target.className === 'delete') {const id = parseInt(event.target.parentElement.id)this.model.deleteTodo(id)}
}

在 JavaScript 中,当你单击复选框来切换它时,会发出 change 事件。按照处理单击删除按钮的方式处理此方法,并调用模型方法。

// 控制器
// Handle change event for toggling a todo
handleToggle = event => {if (event.target.type === 'checkbox') {const id = parseInt(event.target.parentElement.id)this.model.toggleTodo(id)}
}
这些控制器方法有点乱 - 理想情况下它们不应该处理任何逻辑,而是应该简单地调用模型。

设置事件监听器

现在我们有了这三个 handler ,但控制器仍然不知道应该什么时候调用它们。必须把事件侦听器放在视图中的 DOM 元素上。我们将回复表单上的submit 事件,以及 todo 列表上的 clickchange事件。

View 中添加一个 bindEvents 方法,该方法将调用这些事件。

// 视图
bindEvents(controller) {this.form.addEventListener('submit', controller.handleAddTodo)this.todoList.addEventListener('click', controller.handleDeleteTodo)this.todoList.addEventListener('change', controller.handleToggle)
}

接着把侦听事件的方法绑定到视图。在 Controllerconstructor 中,调用 bindEvents 并传递控制器的this 上下文。

在所有句柄事件上都用了箭头函数。这允许我们可以用控制器的 this 上下文从视图中调用它们。如果不用箭头函数,我们将不得不手动去绑定它们,如 controller.handleAddTodo.bind(this)
// 控制器
this.view.bindEvents(this)

现在,当指定的元素发生submitclickchange 事件时,将会调用相应的 handler。

响应模型中的回调

我们还遗漏了一些东西:事件正在侦听,handler 被调用,但是没有任何反应。这是因为模型不知道视图应该更新,并且不知道如何更新视图。我们在视图上有 displayTodos 方法来解决这个问题,但如前所述,模型和视图不应该彼此了解。

就像侦听事件一样,模型应该回到控制器,让它知道发生了什么。

我们已经在控制器上创建了 onTodoListChanged 方法来处理这个问题,接下来只需让模型知道它。我们将它绑定到模型,就像对视图上的 handler 所做的一样。

在模型中,为 onTodoListChanged 添加 bindEvents

// 模型
bindEvents(controller) {this.onTodoListChanged = controller.onTodoListChanged
}

在控制器中,发送 this 上下文。

// 控制器
constructor() {// ...this.model.bindEvents(this)this.view.bindEvents(this)
}

现在,在模型中的每个方法之后,你将调用 onTodoListChanged 回调。

在更复杂的程序中,可能对不同的事件有不同的回调,但在这个简单的待办事项程序中,我们可以在所有方法之间共享一个回调。
//模型
addTodo(todo) {this.todos = [...this.todos, todo]this.onTodoListChanged(this.todos)
}

添加 local storage

这时程序的大部分都已完成,所有概念都已经演示过了。我们可以通过将数据保存在浏览器的 local storage 中来对其进行持久化。

如果你不了解 local storage 的工作原理,请阅读如何使用JavaScript local storage。

现在我们可以将待办事项的初始值设置为本地存储或空数组。

// 模型
class Model {constructor() {this.todos = JSON.parse(localStorage.getItem('todos')) || []}
}

然后创建一个 update 函数来更新 localStorage 的值。

//模型
update() {localStorage.setItem('todos', JSON.stringify(this.todos))
}

每次更改 this.todos 后,我们都可以调用它。

//模型
addTodo(todo) {this.todos = [...this.todos, todo]this.update()this.onTodoListChanged(this.todos)
}

添加实时编辑功能

这个难题的最后一部分是编辑现有待办事项的能力。编辑总是比添加或删除更棘手。我想简化它,不需要编辑按钮或用 input 或任何东西替换 span。我们也不想每输入一个字母都调用 editTodo,因为它会重新渲染整个待办事项列表UI。

我决定在控制器上创建一个方法,用新的编辑值更新临时状态变量,另一个方法调用模型中的 editTodo 方法。

//控制器
constructor() {// ...this.temporaryEditValue
}// Update temporary state
handleEditTodo = event => {if (event.target.className === 'editable') {this.temporaryEditValue = event.target.innerText}
}// Send the completed value to the model
handleEditTodoComplete = event => {if (this.temporaryEditValue) {const id = parseInt(event.target.parentElement.id)this.model.editTodo(id, this.temporaryEditValue)this.temporaryEditValue = ''}
}
我承认这个解决方案有点乱,因为 temporaryEditValue 变量在技术上应该在视图中而不是在控制器中,因为它是与视图相关的状态。

现在我们可以将这些添加到视图的事件侦听器中。当你在 contenteditable 元素输入时,input 事件会被触发,离开contenteditable元素时,focusout 会触发。

//视图
bindEvents(controller) {this.form.addEventListener('submit', controller.handleAddTodo)this.todoList.addEventListener('click', controller.handleDeleteTodo)this.todoList.addEventListener('input', controller.handleEditTodo)this.todoList.addEventListener('focusout', controller.handleEditTodoComplete)this.todoList.addEventListener('change', controller.handleToggle)
}

现在,当你单击任何待办事项时,将进入“编辑”模式,这将会更新临时状态变量,当选中或单击待办事项时,将会保存在模型中并重置临时状态。

contenteditable 解决方案很快得到实施。在程序中使用 contenteditable 时需要考虑各种问题,我在这里写过许多内容。

总结

现在你拥有了一个用纯 JavaScript 写的 todo 程序,它演示了模型 - 视图 - 控制器体系结构的概念。以下是演示和源代码的链接。

  • 查看程序的演示
  • 查看程序的源代码

我希望本教程能帮你理解 MVC。使用这种松散耦合的模式可以为程序添加大量的样板和抽象,同时它也是一种开发人员熟悉的模式,是一个通常用于许多框架的重要概念。

原文出处:思否
原文作者:疯狂的技术宅
原文链接:https://segmentfault.com/a/1190000020007033

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500976.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis线程阻塞原因排插_每次面试都要被问:为什么采用单线程的Redis也会如此之快?...

众所周知&#xff0c;Redis在内存库数据库领域非常地火热&#xff0c;它极高的性能和丰富的数据结构为我们的开发提供了极大的便利。但我们也听说了&#xff0c;Redis是单线程的&#xff0c;为什么采用单线程的Redis也会如此之快呢&#xff1f;这篇文章我们来分析一下其中的缘由…

ssm后台数据是为什么是空值_网易后台开发实习生面试总结

回想到网上流传多年的问题&#xff1a;学历重要&#xff1f;还是能力更重要&#xff1f;上周投了简历&#xff0c;网易HR共来了3个电话(头天晚上两个电话我在做实验没接到&#xff0c;最后第二天中午又给我打了电话)&#xff0c;当她来第三个电话的时候&#xff0c;上面的问题在…

iptable 详解_最全的iptables防火墙详解.pdf

最全的iptables防火墙详解iptables /iptables /iippttaabblleess官方网站&#xff1a;hhttttpp::nneettffiilltteerr..oorrgg//• 数据包经过防火墙的路径• 禁止端口• 强制访问某站点• 发布内部网络服务器• 智能DNS• 端口映射• 通过NAT上网• IP 规则的保存与恢复• ipta…

python中函数type可以测试对象类型_python类型检测最终指南--Typing模块的使用

正文共&#xff1a;30429 字预计阅读时间&#xff1a;76分钟原文链接:https://realpython.com/python-type-checking/作者:Geir Arne Hjelle译者&#xff1a;陈祥安在本指南中&#xff0c;你将了解Python类型检查。传统上&#xff0c;Python解释器以灵活但隐式的方式处理类型。…

java 图片分段上传_java 分段读取文件 并通过HTTP上传

1、首先将文件分段&#xff0c;用RandomAccessFile2、分段后将分出的内容上传到http1.[代码][Java]代码URL url new URL(actionUrl);HttpURLConnection con (HttpURLConnection) url.openConnection();/** 允许Input、Output&#xff0c;不使用Cache */con.setDoInput(true);…

python算法的基本原理_kNN算法基本原理与Python代码实践

kNN是一种常见的监督学习方法。工作机制简单&#xff1a;给定测试样本&#xff0c;基于某种距离度量找出训练集中与其最靠近的k各训练样本&#xff0c;然后基于这k个“邻居”的信息来进行预测&#xff0c;通常&#xff0c;在分类任务中可使用“投票法”&#xff0c;即选择这k个…

python中zip的使用_浅谈Python中的zip()与*zip()函数详解

前言1.实验环境: Python 3.6&#xff1b;2.示例代码地址&#xff1a;下载示例&#xff1b;3.本文中元素是指列表、元组、字典等集合类数据类型中的下一级项目(可能是单个元素或嵌套列表)。zip(*iterables)函数详解zip()函数的定义从参数中的多个迭代器取元素组合成一个新的迭代…

奥的斯电梯tt服务器使用表_奥的斯电梯服务器TT使用说明1

目录一、TT介绍............................................................................................................... 3二、OTIS TT插头各针脚说明................................................................................................. 4三、T…

python安装提示缺少dll_解决Python安装时报缺少DLL问题【两种解决方法】

准备开始学习Python&#xff0c;但是刚准备环境搭建时就遇到了下面的错误&#xff1a;仔细的看了看&#xff0c;说是缺少DLL。对于这个问题的解决办法&#xff1a;方法一&#xff1a;1. 在安装包上点右键以管理员身份运行2. C:\Users\用户名\AppData\Local\Temp 文件夹上右键-&…

影像科dsa为什么必须买维修保险_了解什么是DSA,看这篇就够了

相对于超声、CT、磁共振等&#xff0c;DSA可能对大部分人而言都是比较陌生的&#xff0c;目前国内DSA市场基本都是进口品牌主导&#xff0c;国内能生产这种医疗设备的屈指可数&#xff0c;也就是说&#xff0c;目前我们在医院看到的DSA设备基本都来自"GPST"等进口厂商…

python tkinter下载器_下载小说还要去找网站?Python使用tkinter打造一个小说下载器...

前言今天教大家用户Python GUI编程——tkinter 打造一个小说下载器&#xff0c;想看什么小说&#xff0c;就下载什么小说先看下效果图Tkinter 是使用 python 进行窗口视窗设计的模块。Tkinter模块("Tk 接口")是Python的标准Tk GUI工具包的接口。作为 python 特定的GU…

python while循环if_20170403Python控制流if、while、for语句学习

控制流if、while、for语句学习if elif else语句if语句是一种最常见的控制流语句类型。它用于判断一个条件&#xff0c;如果为True&#xff0c;则执行紧跟其后的代码块。一条if语句可以跟上一个或多个elif块以及一个“滴水不漏”的else块(如果所有条件都为False)。if ... :Print…

审计日志_Oracle审计日志过大?如何清理及关闭审计机制?

概述oracle 11g推出了审计功能&#xff0c;但这个功能会针对很多操作都产生审计文件.aud&#xff0c;日积月累下来这些文件也很多&#xff0c;默认情况下&#xff0c;系统为了节省资源&#xff0c;减少I/0操作&#xff0c;其审计功能是关闭的。这段时间发现审计占了比较多空间&…

windows7 python 指定源组播 10049_Python3组播通信编程实现教程(发送者+接收者)

一、说明1.1 标准组播解释通信分为单播、多播(即组播)、广播三种方式单播指发送者发送之后&#xff0c;IP数据包被路由器发往目的IP指定的唯一一台设备的通信形式&#xff0c;比如你现在与web服务器通信就是单播形式广播指发送者发送之后&#xff0c;IP数据包被路由器发给与其连…

servlet如何使用session把用户的手机号修改_SpringBoot源码学习系列之嵌入式Servlet容器...

1、前言简单介绍SpringBoot的自动配置就是SpringBoot的精髓所在&#xff1b;对于SpringBoot项目是不需要配置Tomcat、jetty等等Servlet容器&#xff0c;直接启动application类既可&#xff0c;SpringBoot为什么能做到这么简捷&#xff1f;原因就是使用了内嵌的Servlet容器&…

mybatisplus新增返回主键_第17期:索引设计(主键设计)

表的主键指的针对一张表中的一列或者多列&#xff0c;其结果必须能标识表中每行记录的唯一性。InnoDB 表是索引组织表&#xff0c;主键既是数据也是索引。主键的设计原则1. 对空间占用要小上一篇我们介绍过 InnoDB 主键的存储方式&#xff0c;主键占用空间越小&#xff0c;每个…

mysql 集群与主从_Mysql集群和主从

1、Mysql cluster: share-nothing,分布式节点架构的存储方案&#xff0c;以便于提供容错性和高性能。需要用到mysql cluster安装包&#xff0c;在集群中的每一个机器上安装。有三个关键概念&#xff1a;Sql节点(多个)&#xff0c;数据节点(多个)&#xff0c;管理节点(一个)&…

python四则运算_四则运算 python

java转换json需要导入的jar包&#xff0c;org&sol;apache&sol;commons&sol;lang&sol;exception&sol;NestableRuntimeException缺少相应jar包都会有异常,根据异常找jar包导入...... 这里我说下lang包,因为这个包我找了好半天: 我用的是: commons-lang…

mysql 字段排重_MySQL 根据单个、多个字段排重

情景是这样的 首先我们业务需要 在一张流水表中需要进行流水的记录 这个记录是从别的平台拉过来的数据 但是他们数据无唯一约束 即流水这样就很有可能出现单条数据重复的问题所以这种情况 用join的话 效率可能不是很大 所以就写了以下sql注意 排重后还需保留一个唯一有效的记录…

python开发框架 代码生成_我的第一个python web开发框架(31)——定制ORM(七)...

几个复杂的ORM方式都已介绍完了&#xff0c;剩下一些常用的删除、获取记录数量、统计合计数、获取最大值、获取最小值等方法我就不一一详细介绍了&#xff0c;直接给出代码大家自行查看。1 #!/usr/bin/env python2 #codingutf-834 from common importdb_helper567 classLogicBa…