实战 图书馆系统管理案例

  • config :敏感的配置一般都是在配置中心配置,比如consul或者阿波罗上面
  • controller :写一些handler的,拿到参数要去调用service层的逻辑。(只负责接受参数,怎么绑定参数,要去调用哪个service的,handler的一个入口
  • service:service层才是正真的业务处理层。调用dao层的增删改查操作
  • dao:只关心数据库的一个操作,其实你有数据库和es都在dao层
  • db:负责数据库,中间件层的初始化
  • middleware:存放gin的中间件,在注册路由的时候将中间件引入进来

go常用项目结构 


dao层只关心数据库的操作,db层只做Mysql层的初始化。model去定义表结构,定义中间表,多对多的结构体。 

中间件有个token的验证。

controller层,先去找路由,这个项目有多少的接口(router.go),每个接口到哪些对应的handler。

controller层接收完参数之后,再看调用到哪个service,service再去干了什么增删改查操作。

数据库增删改查就去dao层查看。

这一层一层非常的分明。上面可以理解为日常开发的目录结构最佳实践。

创建数据库


mysql> create database books charset utf8;

图书管理服务 


用户服务:登录,注册
书籍服务:对书籍的增删改查的操作
主要有两个维度,一个是书籍维度,一个是用户维度。用户维度要去写两个功能,一个功能是登入,一个功能是注册。
注册就是数据库插入一条数据。登入就是一个校验。配合token中间件去做一个校验。
在新增书籍的时候要去关联用户,这本书属于谁?书籍的增删改查都需要关联这个用户。这里就涉及到1对多的关系。
开发的时候是从最底层开始,往上开发。第一个先去定义数据库的model层。

  

model层 定义数据库结构


binding标签表示是必填项,token是可以为空的,因为一开始注册的时候token的为空。只有登入的时候才有token。

user.go

package modeltype User struct {ID       int64  `gorm:"primaryKey" json:"id"`UserName string `gorm:"not null" json:"username" binding:"required"`PassWord string `gorm:"not null" json:"password" binding:"required"`Token    string `json:"token"`
}func (*User) TableName() string {return "user"
}

book.go

package modeltype Book struct {ID    int64  `gorm:"primaryKey" json:"id"`Name  string `gorm:"not null" json:"name" binding:"required"`Desc  string `json:"desc"`Users []User `gorm:"many2many:book_users"`
}func (*Book) TableName() string {return "book"
}

多对多关系可以在user这层定义也行,在book这一层定义也可以。这个主要看你的一个实际使用的场景,这里在book模型里面去定义就行了。

还需要去定义一个中间表的模型user_m2m_book.go

package modeltype BookUser struct {UserID int64 `gorm:"primaryKey"`BookID int64 `gorm:"primaryKey"`
}

这里不需要自定义表名,它只有一个主键,也没有其他属性了。它也是永了外键,也是使用了那两个模型的主键。


DB层


模型定义好之后去做数据库的初始化,这里需要预留,因为可能不仅仅只有MySQL的初始化。

这样赋值到一个全局变量,之后使用mysql.DB就可以在任何地方去使用了。 

package mysqlimport ("book/model""fmt""gorm.io/driver/mysql""gorm.io/gorm"
)// DB 要将DB实例放到全局变量,这样就可以使用mysql.DB在任何地方去使用了.
var DB *gorm.DBfunc InitMysql() {dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/books?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})//这个地方Log和panic都是可以的if err != nil {fmt.Println(err)}DB = dberr = DB.AutoMigrate(model.Book{}, model.User{}, model.BookUser{})if err != nil {fmt.Println(err)}
}

DAO层


初始化好之后在dao层开始写,dao层的操作就是数据库的增删改查

package daoimport ("book/db/mysql""book/model""errors""fmt""github.com/wonderivan/logger""gorm.io/gorm"
)//定义user结构体,以及User变量,能够直接跨包调用user下面的方法
//只需要一次初始化即可,不用每次调用的都是先初始化var User usertype user struct {
}// Add 新增 用于注册
func (*user) Add(user *model.User) error {if tx := mysql.DB.Create(user); tx.Error != nil {//打印错误提示logger.Error(fmt.Sprintf("添加User失败,错误信息:%s", tx.Error))return errors.New(fmt.Sprintf("添加User失败,错误信息:%s", tx.Error))}return nil
}// Has 查询 基于name 用于新增
func (*user) Has(name string) (*model.User, bool, error) {//初始化要申请内存,不然会报错data := &model.User{}tx := mysql.DB.Where("username = ?", name).Find(data)//如果记录没有查询到,报错是从tx.error里面拿到的,相当于tx.Error = gorm.ErrRecordNotFoundif errors.Is(tx.Error, gorm.ErrRecordNotFound) {return nil, false, nil}//等会去调用的时候,不会先去判断有没有,要先去判断error,有error就是真正的错误,没有判断是不是falseif tx.Error != nil {logger.Error(fmt.Sprintf("查询用户失败,错误信息:%s", tx.Error))return nil, false, errors.New(fmt.Sprintf("查询用户失败,错误信息:%s", tx.Error))}return data, true, nil
}// GetByToken 基于token查询,用于中间件token校验
func (*user) GetByToken(token string) (*model.User, bool, error) {//初始化要申请内存,不然会报错data := &model.User{}tx := mysql.DB.Where("token = ?", token).Find(data)//如果记录没有查询到,报错是从tx.error里面拿到的,相当于tx.Error = gorm.ErrRecordNotFoundif errors.Is(tx.Error, gorm.ErrRecordNotFound) {return nil, false, nil}if tx.Error != nil {logger.Error(fmt.Sprintf("查询用户失败%s", tx.Error))return nil, false, errors.New(fmt.Sprintf("查询用户失败%v/n", tx.Error))}return data, true, nil
}// UPDateToken 更新token,这里其实就是找到user的条件去更新就行了
// 第一个参数可以是id也可以是用户名,如果用户名唯一,只要保证找到指定用户即可
// 除了查询的操作以外,增删改只需要返回一个error即可,判断操作有没有成功
func (*user) UPDateToken(user *model.User, token string) error {tx := mysql.DB.Model(user).Where("username= ? and password = ?", user.UserName, user.PassWord).Update("token", token)if tx.Error != nil {logger.Error(fmt.Sprintf("更新user token失败,错误信息:%s", tx.Error))return errors.New(fmt.Sprintf("更新user token失败,错误信息:%s", tx.Error))}return nil
}

service层


和dao层一样,都定义了相同的结构体,不同的包使用相同的结构体变量,但是点出来的方法都是不同的。

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

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

相关文章

Viobot输出数据说明

一.原始数据 1.ROS话题 1)相机原始图像数据 Type: sensor_msgs::Image Topic: 左目:/image_left 右目:/image_right 2)imu数据 Type: sensor_msgs::Imu Topic: /imu 3)TOF数据 点云数据: Type: sensor_msgs::P…

算法与数据结构(十)--图的入门

一.图的定义和分类 定义:图是由一组顶点和一组能够将两个顶点连接的边组成的。 特殊的图: 1.自环:即一条连接一个顶点和其自身的边; 2.平行边:连接同一对顶点的两条边; 图的分类: 按照连接两个顶点的边的…

带你速览主数据管理(MDM)的前世今生

主数据管理的历史可以追溯到很久以前,可以说主数据管理是生产生活的一部分。随着社会生产力和生产工具的不断发展,主数据和主数据管理在其中的作用不断提升,成为当今政府、企业和社会团队等组织管理中必不可少基础管理工作,同时也…

FrameBuffer 应用编程

目录 什么是FrameBufferLCD 的基础知识使用ioctl()获取屏幕参数信息使用mmap()将显示缓冲区映射到用户空间 LCD 应用编程练习之LCD 基本操作LCD 应用编程练习之显示BMP 图片BMP 图像介绍在LCD 上显示BMP 图像在开发板上测试 在LCD 上显示jpeg 图像在LCD 上显示png 图片LCD 横屏…

C语言_分支和循环语句(2)

文章目录 前言一、for 循环1.1语法1.2 for 语句的循环控制变量1.3 一些 for 循环的变种 二、do ... while()循环2.1 do 语句的语法2.2 do ... while 循环中的 break 和 continue2.3 练习1 **- 计算n的阶乘**2. - **在一个有序数组中查找具体的某个数字 n** 二分查找算法&#x…

68、使用aws官方的demo和配置aws服务,进行视频流上传播放

基本思想:参考官方视频,进行了配置aws,测试了视频推流,rtsp和mp4格式的视频貌似有问题,待调研和解决 第一步:1) 进入aws的网站,然后进入ioT Core 2)先配置 Thing types & Thing,选择香港的节点,然后AWS ioT--->Manage---> Thing type 然后输入名字,创建Th…

screen命令,可以断开服务器连接,依旧能运行你的程序了

可以参考博客1:https://blog.csdn.net/nima_zhang_b/article/details/82797928 可以参考博客2:https://blog.csdn.net/herocheney/article/details/130984403 Linux中的screen是一个命令行工具,可以让用户在同一个终端会话中创建多个虚拟终端。它非常有…

null值 字段运算

null值字段运算前先把null转成0 test表如下,num2为null select num1-num2 from test; 结果为null减去null值结果为null select sum(num1),SUM(num2) from test ;sum求和结果为null 判断字段是null不能用 null ,要用is null 错误写法: select IF(…

Spring AOP基于注解方式实现和细节

目录 一、Spring AOP底层技术 二、初步实现AOP编程 三、获取切点详细信息 四、 切点表达式语法 五、重用(提取)切点表达式 一、Spring AOP底层技术 SpringAop的核心在于动态代理,那么在SpringAop的底层的技术是依靠了什么技术呢&#x…

CSS按钮-跑马灯边框

思路很简单&#xff0c;实现方法有很多很多。但是大体思路与实现方法都类似&#xff1a;渐变色 动画&#xff0c;主要区别在动画的具体实现 0、HTML 结构 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><titl…

服务网格实施周期缩短 50%,丽迅物流基于阿里云 ACK 和 ASM 的云原生应用管理实践

作者&#xff1a;王夕宁、 刘强、 华相 公司介绍 丽迅物流是百丽旗下专注于时尚产业、为企业提供专业物流及供应链解决方案的服务商。其产品服务主要包括城市落地配、仓配一体、干线运输及定制化解决方案。通过自研智能化物流管理平台&#xff0c;全面助力企业合作集约化发展…

4年外包出来,5次面试全挂....

我的情况 大概介绍一下个人情况&#xff0c;男&#xff0c;毕业于普通二本院校非计算机专业&#xff0c;18年跨专业入行测试&#xff0c;第一份工作在湖南某软件公司&#xff0c;做了接近4年的外包测试工程师&#xff0c;今年年初&#xff0c;感觉自己不能够再这样下去了&…

leetcode 739. 每日温度

2023.8.28 本题用暴力双层for循环解会超时&#xff0c;所以使用单调栈来解决&#xff0c;本质上是用空间换时间。维护一个单调递减栈&#xff0c;存储的是数组的下标。 代码如下&#xff1a; class Solution { public:vector<int> dailyTemperatures(vector<int>&…

CPU和GPU的区别

介绍什么是GPU, 那就要从CPU和GPU的比较不同中能更好更快的学习到什么是GPU CPU和GPU的总体区别 CPU&#xff1a; 叫做中央处理器&#xff08;central processing unit&#xff09; 可以形象的理解为有25%的ALU(运算单元)、有25%的Control(控制单元)、50%的Cache(缓存单元)…

笔记:transformer系列

1、和其他网络的比较 CNN归纳偏置&#xff08;inductive bias&#xff0c;目标函数的必要假设) 1、平移不变性&#xff1a;平移旋转缩放等变化&#xff0c;CNN依旧能够识别 2、空间局部性&#xff1a;局部像素联系密切&#xff0c;因此每个神经元无需有全局感知&#xff0c;在…

webassembly003 ggml ADAM (暂记)

Adam优化器的工作方式是通过不断更新一阶矩估计和二阶矩估计来自适应地调整学习率&#xff0c;并利用动量法来加速训练过程。这种方式可以在不同的参数更新方向和尺度上进行自适应调整&#xff0c;从而更有效地优化模型。 https://arxiv.org/pdf/1412.6980.pdf 参数 这些参数…

Confluence使用教程(用户篇)

1、如何创建空间 可以把空间理解成一个gitlab仓库&#xff0c;空间之间相互独立&#xff0c;一般建议按照部门&#xff08;小组的人太少&#xff0c;没必要创建空间&#xff09;或者按照项目分别创建空间 2、confluence可以创建两种类型的文档&#xff1a;页面和博文 从内容上来…

开始MySQL之路——MySQL 事务(详解分析)

MySQL 事务概述 MySQL 事务主要用于处理操作量大&#xff0c;复杂度高的数据。比如说&#xff0c;在人员管理系统中&#xff0c;你删除一个人员&#xff0c;你即需要删除人员的基本资料&#xff0c;也要删除和该人员相关的信息&#xff0c;如信箱&#xff0c;文章等等&#xf…

oracle 启停操作

1. 监听端口启停 # 根据实际情况 切换至oracle用户 su - oracle# 状态查看 lsnrctl stat# 启动1521端口监听 lsnrctl start# 关闭1521监听 lsnrctl stop 2. 数据库服务启停 # 立即关闭服务 shutdown immediate# 启动服务 startup

C语言——类型转换

数据有不同的类型&#xff0c;不同类型数据之间进行混合运算时涉及到类型的转换问题。 转换的方法有两种&#xff1a; 自动转换(隐式转换)&#xff1a;遵循一定的规则&#xff0c;由编译系统自动完成强制类型转换&#xff1a;把表达式的运算结果强制转换成所需的数据类型 语法格…