【Goalng 开源项目】还在手写重复的 CRUD 吗?这个开源项目帮你解放双手

news2024/11/25 17:23:37

gormpher

    • Gormpher 介绍
    • 快速开始
    • WebObject 接口约定
      • 查询单条数据
      • 删除单条数据
      • 创建单条数据
      • 编辑单条数据
      • 条件查询多条数据
    • 进阶
      • WebObject 配置项
      • 动态接口函数
      • Gorm 泛型函数
      • Admin
    • 源码
      • handleEditObject
      • handleQueryObject

Gormpher 介绍

gormpher 是一个轻量级的 Golang 库

  • 基于 Gin 和 Gorm
  • WebObject 机制:根据模型生成对应的 Restful API,一键生成平时开发中重复的 CRUD 代码
  • 通用 Gorm 泛型函数
  • 基于泛型的动态接口函数
  • 将 WebObject 注册到 Admin,自带测试用的 Web 界面
  • 完善的单元测试,覆盖率接近 80%(完善中)

gormpher github:https://github.com/restsend/gormpher

使用 gormpher 的开源项目 rabbit-admin:https://github.com/szluyu99/rabbit-admin
建议看完本文,理解 gormpher 后可以查看该开源项目,体会其在实际开发中到底能节约多少工作量

快速开始

先直观的体会一下这个项目最基础的功能是用来做什么的。

go get github.com/restsend/gormpher

示例源于 gormpher 仓库下 example/main.go:(下面是其简化版)

代码版本可能会更新,文章中不一定是最新的,可以参考上面的 example/main.go 文件获取最新用法

使用流程:

  1. 定义好一个结构体 struct,指定其 primarykey
  2. 创建 gormpher 依赖的 gorm 指针 和 gin 路由对象(后续考虑适配其他 web 框架)
  3. 创建 gormpher 中的 WebObject 对象
  4. 注册 WebObject 对象到指定路由组
  5. (可选)将 WebObject 对象注册到 Admin 中,展示一个测试用的 Web 界面
package main

import (
	"flag"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/restsend/gormpher"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// 1. 定义结构体,指定 primarykey
type User struct {
	ID        uint       `json:"id" gorm:"primarykey"`
	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	Name      string     `json:"name"`
	Age       int        `json:"age"`
	Enabled   bool       `json:"enabled"`
	LastLogin *time.Time `json:"lastLogin"`
}

func main() {
	var dsn string
	var addr string

	flag.StringVar(&dsn, "n", "", "DB DSN")
	flag.StringVar(&addr, "a", ":8890", "Api Server Addr")
	flag.Parse()

	// 2. gorm db, gin router
	db, _ := gorm.Open(sqlite.Open(dsn), nil)
	db.AutoMigrate(User{})
	r := gin.Default()
	
	// 3. 创建 gormpher WebObject 对象(User 为模型对象)
	objs := []gormpher.WebObject{
		{
			Name:         "user",
			Model:        &User{},
			SearchFields: []string{"Name", "Enabled"}, // 可模糊搜索的字段
			EditFields:   []string{"Name", "Age", "Enabled", "LastLogin"}, // 可编辑的字段
			FilterFields: []string{"Name", "CreatedAt", "Age", "Enabled"}, // 可条件查询的字段
			OrderFields:  []string{"CreatedAt", "Age", "Enabled"}, // 可排序的字段
			GetDB:        func(ctx *gin.Context, isCreate bool) *gorm.DB { return db }, // 返回 *gorm.DB 的方法 
		},
	}

	// 4. 注册 gormpher WebObject 对象到指定路由组,生成以下 API
	// PUT 		http://localhost:8890/api/user
	// GET 		http://localhost:8890/api/user/:key
	// PATCH	http://localhost:8890/api/user/:key
	// POST 	http://localhost:8890/api/user
	// DELETE   http://localhost:8890/api/user/:key
	gormpher.RegisterObjects(r.Group("api"), objs)
	
	// 5. (可选)将 WebObject 对象注册到 Admin 中,展示一个测试用的 Web 界面
	// 访问 URL: http://localhost:8890/admin
	gormpher.RegisterObjectsWithAdmin(r.Group("admin"), objs)
	
	r.Run(addr)
}

运行该程序:

go run main.go

经过上面简短的代码,我们就生成了以下 API:

[GIN-debug] GET    /api/user/:key            --> github.com/restsend/gormpher.(*WebObject).RegisterObject.func1 (3 handlers)
[GIN-debug] PUT    /api/user                 --> github.com/restsend/gormpher.(*WebObject).RegisterObject.func2 (3 handlers)
[GIN-debug] PATCH  /api/user/:key            --> github.com/restsend/gormpher.(*WebObject).RegisterObject.func3 (3 handlers)
[GIN-debug] DELETE /api/user/:key            --> github.com/restsend/gormpher.(*WebObject).RegisterObject.func4 (3 handlers)
[GIN-debug] POST   /api/user                 --> github.com/restsend/gormpher.(*WebObject).RegisterObject.func5 (3 handlers)

目前你可能不太理解这些接口的用法,具体参数本文后面会介绍:核心约定。


访问:http://localhost:8890/admin 查看测试用的 Web 界面

先尝试体验一下,通过上面这么简单的几行代码可以实现的接口效果。

在这里插入图片描述

WebObject 接口约定

平时开发中的容易重复的 CRUD 代码可以简单分为以下情况:

  • 新增数据(单条)- Create
  • 删除数据(单条 或 多条)- Delete
  • 编辑数据(单条)- Edit
  • 查询单条数据 - Get
  • 条件查询多条数据(分页)- Query

概述:Gormpher 的核心思想是基于模型对象来生成其 API,例如对于一个 user 模块

  • PUT /user 即创建一个 user 对象
    • 请求体传递要创建的 user 对象
  • DELETE /user/:key 即删除主键为 key 的 user 数据
  • PATCH /user/:key 即编辑主键为 key 的 user 数据
    • 请求体传递要编辑的 user 对象
  • GET /user/:key 即查询主键为 key 的单个 user 数据
  • POST /user 即条件查询多条 user 的信息(分页)
    • 请求体传递查询参数

涉及到请求体全部使用 Content-Type: application/json 传输数据

即对于 user 模块,最终我们生成的 API 如下:

GET    /user/:key
PUT    /user
PATCH  /user/:key
DELETE /user/:key
POST   /user

然后再解析一下以上的行为,并给出对应的接口请求与响应示例。

查询单条数据

GET /user/:key

  • 请求参数
    • URL 路径参数:key 为数据的主键
# Path
GET /user/1
  • 响应数据:User 对象
{
    "id": 1,
    "createdAt": "2023-06-13T23:43:27.590377962+08:00",
    "updatedAt": "2023-06-13T23:43:27.590377962+08:00",
    "name": "u1",
    "age": 10,
    "enabled": false,
    "lastLogin": "2023-06-01T23:43:00Z"
}

删除单条数据

DELETE /user/:key

  • 请求参数
    • URL 路径参数:key 为数据的主键
# Path
DELETE /user/1
  • 响应数据Boolean,表示是否删除成功
true

创建单条数据

PUT /user

  • 请求参数
    • 请求体参数:User 对象
# Path
PUT /user

# Request Body
{
    "name": "u2",
    "age": 5,
    "lastLogin": "2023-06-01T23:49",
    "enabled": true
}
  • 响应数据:User 对象
{
    "id": 2,
    "createdAt": "2023-06-13T23:50:01.615012885+08:00",
    "updatedAt": "2023-06-13T23:50:01.615012885+08:00",
    "name": "u2",
    "age": 5,
    "enabled": true,
    "lastLogin": "2023-06-01T23:49:00Z"
}

编辑单条数据

PATCH /user/:key

  • 请求参数
    • URL 路径参数:key 为数据的主键
    • 请求体参数:需要编辑的 User 对象(未传的属性不会被修改,传了的即使是空值也会被修改)
# Path
PATCH /user/1

# Request Body
{
	"enabled" :  true,
	"name": "aaaa"
}
  • 响应数据Boolean,表示是否编辑成功
true

注意:

  • 并不是所有的字段都可以被前端传来的参数所编辑的
  • 因此,我们将可以被编辑的字段暴露成一个配置项 EditFields
  • 如果前端传递了非 EditFeilds 中指定的字段,则无法编辑成功

示例中指定了 user 可编辑字段为:NameAgeEnabledLastLogin

{
	// ...
	Model:        &User{},
	EditFields:   []string{"Name", "Age", "Enabled", "LastLogin"}, // 可编辑的字段
	// ...
}

条件查询多条数据

POST /user

  • 请求参数QueryForm 对象
    • 请求体参数:User 对象
  • 响应数据QueryResult 对象

这是一个非常重要且复杂的行为,大部分重复的代码都出自它,为抽象出一个通用规则,我们将以下字段暴露成可配置项:

  • SearchFields:可以被模糊搜索的字段
  • FilterFields:可以被条件查询的字段
  • OrderFields:可以被排序的字段
{
	// ...
	Model:        &User{},
	SearchFields: []string{"Name", "Enabled"}, // 可模糊搜索的字段
	FilterFields: []string{"Name", "CreatedAt", "Age", "Enabled"}, // 可条件查询的字段
	OrderFields:  []string{"CreatedAt", "Age", "Enabled"}, // 可排序的字段
	// ...
}

查询表单对象:QueryForm

NameTypeDescDefault
posnumber分页参数,数据查询位置0
limitnumber分页参数 ,数据查询范围50
keywordnumber模糊搜索关键字,字段需要配置在 SearchFields“”
filters[]Filter条件查询对象数组,字段需要配置在 FilterFieldsnull
orders[]Order排序对象数组,字段需要配置在 OrderFieldsnull

Filter:

NameOpDesc
namestring字段
opstring=, <>, in, not_in, >, >=, <, <=
valuestring

示例:检索 age > 10 且 enabled = true 的数据,请求参数如下

{
	filters: [
		{ "name": "age", "op": ">", "value": 10 },
		{ "name": "enabled", "op": "=", "value": true }
	]
}

Order:

NameOpDesc
namestring
opstringasc, desc

示例:检索按 age 降序排序的数据,请求参数如下

{
	orders: [
		{ "name": "age", op: "desc" }
	]
}

请求示例:检索条件如下

  • 查询开始位置 0,查询数量 10
  • 模糊查询关键字为 “u”
  • age > 10 且 enabled = true
  • 按 age 降序排序
{
	"pos": 0,
	"limit": 10,
	"keyword": "u",
	"filters": [
		{ "name": "age", "op": ">", "value": 10 },
		{ "name": "enabled", "op": "=", "value": true }
	],
	"orders": [
		{ "name": "age", op: "desc" }
	]
}

查询结果:QueryResult

NameTypeDesc
posnumber本次查询中分页参数
limitnumber本次查询分页参数
keywordstring本次查询模糊搜索关键字
totalnumber本次查询数据总数
items[]object本次查询数据数组

响应示例:

{
    "total": 2,
    "pos": 0,
    "limit": 10,
    "items": [
        {
            "id": 1,
            "createdAt": "2023-06-13T23:43:27.590377962+08:00",
            "updatedAt": "2023-06-14T00:23:36.46322106+08:00",
            "name": "u1",
            "age": 10,
            "enabled": true,
            "lastLogin": "2023-06-01T23:43:00Z"
        },
        {
            "id": 2,
            "createdAt": "2023-06-13T23:50:01.615012885+08:00",
            "updatedAt": "2023-06-13T23:50:01.615012885+08:00",
            "name": "u2",
            "age": 5,
            "enabled": true,
            "lastLogin": "2023-06-01T23:49:00Z"
        }
    ]
}

下面的内容后续完善。

进阶

WebObject 配置项

动态接口函数

Gorm 泛型函数

Admin

源码

handleEditObject

handleQueryObject

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

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

相关文章

编译原理及应用期末复习

杂 3型文法 右线性文法 短语、直接短语、句柄 、判断是否是二义性文法 1、证明是二义性文法&#xff1a;证明存在一个句子有两颗不同的语法树 ① 画语法分析树 ② 、NFA、DFA K&#xff1a;所有状态&#xff0c;包含初始状态 Σ&#xff1a;终结字符集 M&#xff1a;状…

chatgpt赋能python:Python中替换字符串成int类型的方法

Python中替换字符串成int类型的方法 简介 在Python编程过程中&#xff0c;经常需要对字符串进行处理。有时候我们需要将字符串中的某些字符替换成int型数据&#xff0c;以便于进行一定的数值计算或其他操作。本文将介绍如何在Python中找出需要替换的字符&#xff0c;并将其转…

Python实现将txt文件转换成对应的excel或csv文件

前言 本文是该专栏的第29篇,后面会持续分享python的各种干货知识,值得关注。 工作上可能会遇到这样的情况,使用python将某个txt文本,按照行索引和列索引转换成对应的excel文件或者是csv文件。 那对于这样的需求,用python如何实现呢?跟着笔者直接往下看解决方法。(附完…

UART协议总结

UART&#xff08; Universal Asynchronous Receiver-Transmitter&#xff0c;通用异步收发器&#xff09;&#xff0c;是异步串行通信协议&#xff0c;用来传输串行数据。 1、UART原理说明 UART是全双工工作模式&#xff0c;其数据传输方法如下&#xff1a; 发送数据时&…

Hive 和 Oracle 中 Decode 函数的用法差异

前言 在数仓构建过程中,需要从业务那边进行数据的迁移!数仓大多数公司都是使用Hive,而业务那边使用的是Oracle数据库居多。最近就有个小伙伴在迁移的时候碰到了问题: 从报错来看,在使用 Decode() 函数的时候,传参有问题! 既然问题来了,我们就来解决一下呗,只有不断地…

基础知识学习---牛客网C++面试宝典(五)操作系统--第一节

1、本栏用来记录社招找工作过程中的内容&#xff0c;包括基础知识学习以及面试问题的记录等&#xff0c;以便于后续个人回顾学习&#xff1b; 暂时只有2023年3月份&#xff0c;第一次社招找工作的过程&#xff1b; 2、个人经历&#xff1a; 研究生期间课题是SLAM在无人机上的应…

LeetCode_Day6 | 四数相加||、赎金信、三数之和、四数之和!

LeetCode_哈希表 454.四数相加1.题目描述2.思路3.代码实现 383.赎金信1.题目描述2.暴力法3.哈希法思路代码实现 15.三数之和1.题目描述 454.四数相加 1.题目描述 详情leetcode链接 2.思路 解题步骤&#xff1a; 首先定义 map&#xff0c;key放a和b两数之和&#xff0c;valu…

神器CLIP:连接文本和图像,打造可迁移的视觉模型

2021年见证了vision transformer的大爆发&#xff0c;随着谷歌提出ViT之后&#xff0c;一大批的vision transformer的工作席卷计算机视觉任务。除了vision transformer&#xff0c;另外一个对计算机视觉影响比较大的工作就是Open AI在2021年1月份发布的DALL-E和CLIP&#xff0c…

chatgpt赋能python:一、Python在数据可视化中的应用

一、Python在数据可视化中的应用 Python是一种功能强大的编程语言&#xff0c;早已成为数据科学家和分析师的首选语言。数据可视化对于从数据中汲取信息和传达想法来说至关重要。Python也是数据可视化的理想工具之一。Python提供了许多强大的库&#xff0c;其中包括了一些流行…

( 2023版)互联网 Java 工程师面试题及答案汇总

最近很多粉丝朋友私信我说&#xff1a;熬过了去年的寒冬却没熬过现在的内卷&#xff1b;打开 Boss 直拒一排已读不回&#xff0c;回的基本都是外包&#xff0c;薪资还给的不高&#xff0c;对技术水平要求也远超从前&#xff1b;感觉 Java 一个初中级岗位有上千人同时竞争&#…

【野指针】

野指针 1. 指针是什么&#xff1f;2. 指针和指针类型2.1 指针-整数2.2 指针的解引用 3. 野指针3.1 野指针成因3.2 如何规避野指针 1. 指针是什么&#xff1f; 指针是什么&#xff1f; 指针理解的2个要点&#xff1a; 指针是内存中一个最小单元的编号&#xff0c;也就是地址平…

MySQL性能优化:慢查询优化

一、执行计划 执行计划的语法 在SQL查询的前面加上EXPLAIN关键字就行。比如: EXPLAIN select* from order_exp;执行效果如下。 &#xff08;一&#xff09;参数详解&#xff1a; 1、id 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id。我们知道我们写的查询语句一…

【框架源码】Spring源码解析之BeanDefinition加载流程解析

观看本文之前&#xff0c;我们先思考一个问题&#xff0c;Spring是如何描述Bean对象的&#xff1f; Spring是根据BeanDefinition来创建Bean对象&#xff0c;BeanDefinition就是Spring中表示Bean定义。BeanDefinition用来存储Bean的相关信息&#xff0c;主要包括&#xff1a;Be…

【Linux问题】删除用户时错把rm当成userdel删除后,该如何解决彻底删除?

问题引入&#xff1a;之前创建的用户默认在home目录中&#xff0c;过了段时间以为是一个目录就直接使用rm删除了&#xff0c;结果在创建一个和之前用户同名的用户时发现报错&#xff1a;useradd: user ‘cjs’ already exists&#xff08;该用户已存在&#xff09;。 1、问题 …

chatgpt赋能python:Python怎么找出最大数的位置?

Python怎么找出最大数的位置&#xff1f; Python是一种高级编程语言&#xff0c;它的简单易学和适用范围广泛使其成为了很多开发者的首选语言。在Python中&#xff0c;有许多方便的内置函数可以帮助我们轻松地处理各种任务。其中之一是查找最大值&#xff0c;但是我们如何找出…

【Linux后端服务器开发】shell脚本

目录 一、变量 1. 普通变量 2. 环境变量 3. 位置变量 4. 特殊变量 二、输入输出 1. read命令接收输入 2. echo命令输出字符串 三、表达式 1. 算术表达式 2. 逻辑表达式 四、分支控制 1. if 分支 2. case 分支 五、循环控制 1. for 循环 2. while 循环 3. unt…

redis键值对映射关系存储-Dict

基本概述 Redis是一个键值型&#xff08;Key-Value Pair&#xff09;的数据库&#xff0c;可以根据键实现快速的增删改查。而键与值的映射关系正是通过Dict来实现的。 Dict由三部分组成&#xff0c;分别是&#xff1a;哈希表&#xff08;DictHashTable&#xff09;、哈希节点&a…

基于51单片机设计的电动车控制器

一、项目介绍 随着社会经济的快速发展,人们对节能环保的要求越来越高,电动车因其无污染、噪音小、使用成本低等优点逐渐成为了市场关注的焦点。同时,随着科技的不断进步和应用,电动车的技术水平也在不断提高。 为了更好地满足市场需求和科技进步的要求,本项目基于51单片…

车载以太网 - 数据链路层 - VLAN

数据链路层通信 以太网二层数据链路层的寻址方式、帧结构、及 VLAN (Virtual LocalArea Network)&#xff0c;其分为LLC(Logical Link Control)逻辑链路控制子层&#xff0c;和 MAC(Media Access Control)媒体访问控制子层&#xff0c;其中&#xff0c;MAC 子层负责以太网的总…

chatgpt赋能python:Python中的异常处理

Python中的异常处理 在Python编程中&#xff0c;异常是指程序出现了不正常的情况&#xff0c;比如语法错误、运行时错误等等。这些异常会导致程序崩溃&#xff0c;所以我们需要在程序中使用异常处理来避免这种情况的发生。 什么是异常处理&#xff1f; 异常处理是一种技术&a…