gin+MySQL简单实现数据库查询

利用 gin 项目搭建一个简易的后端系统。

一个简易的 HTTP 响应接口

首先在 go 工作区的终端输入这条指令:

go get -u github.com/gin-gonic/gin

将 gin 项目的相关依赖保存到本地。

在终端生成 go mod 包管理文件:

go mod init

再创建一个 main.go 文件:

package main

import "github.com/gonic-gin/gin"

func main() {
    r := gin.Default()
    r.GET("/test", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "test",
        })
    })
    r.Run(":9999") // 运行在9999端口
}

因为我是在 wsl 运行这个项目,所以还需要先获取虚拟机的 ip,然后用 curl 测试:

curl http://<ip>:9999/ping

返回结果:

{"message":"test"}

连接 MySQL

关于 MySQL 操作这部分,一开始想着简单,只学了查询数据库部分,大多数踩得坑都是在查询部分,后面觉得要举一反三,就在原来基础上又写了添加的部分,在添加数据这一块写的不是很详细。

添加

先把基本的框架写出来,这里我连接的数据库结构体如下:

type SqlUser struct {
	User_id       string `json:"user_id" from:"user_id"`	
	User_name     string `json:"user_name" from:"user_name"`
}

添加单条数据:

Db, _ := sql.Open("mysql", "<username>:<password>@(localhost:3306)/<yourDatabase>")
router.POST("/add", func(c *gin.Context) {
    User_id := c.Request.FormValue("User_id")
	User_name := c.Request.FormValue("User_name")
    result, err := db.SqlDB.Exec("INSERT INTO t_user (user_id,user_name) VALUE(?,?)", u.User_id, u.User_name)
    if err != nil {
        log.Fatalln(err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        log.Fatalln(err)
    }
    msg := fmt.Sprintf("insert successfully %d", id)
    c.JSON(http.StatusOK, gin.H{
        "msg": msg,
        "user_name": User_name,
    })
})
r.Run(":9999")

执行非 query 操作,使用 Exec 方法,使用 curl 进行测试:

curl -X POST http://<ip>:9999/add -d "User_id=2&User_name=aaa"

返回结果如下:

{"msg":"insert successfully 1","user_name":"test"}
查询

单条查询如下

r.GET("/test/:id", func(c *gin.Context) {
    id: c.Param("id")
    var u SqlUser
    err := Db.QueryRow("select user_id,user_name from t_user where user_id =?", id).Scan(&u.user_id, &u.user_name)
    if err != nil {
        log.Fatalln(err)
        c.JSON(http.StatusOK, gin.H{
            "user": nil,
        })
        return
    }
    c.JSON(http.StatusOK, gin.H{
        "user": u.user_name,
    })
})

然后用 curl 命令去测试:

curl http://<ip>:9999/test/1

会得到之前在 MySQL 中预先保存的条目,样例结构如下:

{"user":"test"}

然而,最开始我不是这样写的,当时的我,没整理好这个框架,直接在单文件里封装函数,下面是我的错误示范:

var Db *sql.DB
func QueryData(c *gin.Context) {
    id := c.Param("id")
    var u SqlUser
    err := Db.QueryRow("select user_id,user_name from t_user where user_id =?", id).Scan(&u.user_id, &u.user_name)
    if err != nil {
        log.Fatalln(err)
        c.JSON(200, gin.H{
            "user": nil,
        })
        return
    }
    c.JSON(200, gin.H{
        "user": u.user_name,
    })
}

func main() {
    Db, _ = sql.Open("/*mysql link*/")
    // 略去了这个问题相关的代码
    r.GET("/test/:id", QueryData)
    r.Run(":9999")
}

然后就出现这样的错误:

runtime error: invalid memory address or nil pointer dereference

必应上的结果是:指针声明后未初始化就赋值。

这个 bug 困扰了我一整天,然后就开始了痛苦的 debug,main 函数的 router 应该是没问题的,接口测试正常,那问题应该是出在 QueryData 上,那只能一个个打印变量测试吧,测试到 Db 时,发现这个变量为 nil,然后紧接着抛出刚才提到的错误。好吧,这个错误我在之前也偶尔犯过,虽然在 main 中对其赋了值,但作用域不一样,所以还是无法传值,那只好去考虑下框架了。

修改

修改单条数据:

router.PUT("/person/:id", func(c *gin.Context) {
    cid := c.Param("id")
    id, err := strconv.Atoi(cid)
    person := Person{Id: id}
    err = c.Bind(&person)
    if err != nil {
        log.Fatalln(err)
    }
    stmt, err := Db.Prepare("UPDATE person SET user_name=? where user_id=?")
    
    if err != nil {
        log.Fatalln(err)
    }
    defer stmt.Close()
    rs, err := stmt.Exec(person.user_name, person.user_id)
    if err != nil {
        log.Fatalln(err)
    }
    ra, err := rs.RowsAffected()
    if err != nil {
        log.Fatalln(err)
    }
    msg := fmt.Sprintf("UPDATE person %d successful %d", person.user_id, ra)
    c.JSON(http.StatusOK, gin.H{
        "msg": msg,
    })
})

urlencode 方式更新:

curl -X PUT http://<ip>:9999/person/1 -d "user_id=1&user_name=temp"

json 方式更新:

curl -X PUT http://<ip>:9999/person/1 -H "Content-Type: application/json" -d '{"user_name":"temp"}'
删除
router.DELETE("person/:id", func(c *gin.Context) {
    cid := c.Param("id")
    id, err := strconv.Atoi(cid)
    if err != nil {
        log.Fatalln(err)
    }
    rs, err := db.Exec("DELETE FROM person from where user_id=?", id)
    if err != nil {
        log.Fatalln(err)
    }
    ra, err := rs.RowsAffected()
    if err != nil {
        log.Fatalln(err)
    }
    msg := fmt.Sprintf("DELETE person %d successful %d", id, ra)
    c.JSON(http.StatusOK, gin.H{
        "msg": msg,
    })
})

直接使用删除接口

curl -X "DELETE" http://<ip>:9999/person/1 

这里的 DELETE 参数必须在双引号内,不然不会被识别为 delete 请求。

搭建框架

参考了大佬的做法 [1],我也来创建我的目录:

ginExample tree
.
├── api
│   └── query.go
├── database
│   └── mysql.go
├── main.go
├── models
│   └── queryUser.go
├── router.go
├── go.mod
└── go.sum

这里的话以添加、查询功能为例,增删改同理,具体可以查看文末的参考链接。

api 存放 handler 函数,model 存放数据模型。

数据库处理
// mysql.go
package database

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

var SqlDB *sql.DB

func init() {
    var err error
	SqlDB, err = sql.Open("mysql", "root:jaydenmysql@(127.0.0.1:3306)/microtest")
	if err != nil {
		log.Fatalln(err.Error())
	}
	err = SqlDB.Ping()
	if err != nil {
		log.Fatalln(err.Error())
	}
}

因为在别的包会用到 SqlDB 这个变量,因此必须大写 (golang 只有大写开头变量才是 public 类变量)

数据 model 封装

抽离出 SqlUser 结构体以及对应的方法:

package models

import (
	db "ginExample/database"
	"log"
)

type SqlUser struct {
	User_id       string `json:"user_id" from:"user_id"`	
	User_name     string `json:"user_name" from:"user_name"`
}

func (u *SqlUser) GetUser(id string) SqlUser {
	err := db.SqlDB.QueryRow("select user_id,user_name from t_user where user_id =?", id).Scan(&u.User_id, &u.User_name)
	if err != nil {
		log.Println(err)
		u.User_name = "nil"
		return *u
	}
	return *u
}

func (u *SqlUser) AddUser() (id int64, err error) {
    result, err := db.SqlDB.Exec("INSERT INTO t_user (user_id,user_name) VALUE(?,?)", u.User_id, u.User_name)
    if err != nil {
		log.Fatalln(err)
		return
	}
	id, err = result.LastInsertId()
	if err != nil {
		log.Fatalln(err)
		return
	}
	fmt.Println(id)
	return id, err
}

func (u *SqlUser) UpdateUser() int64 {
    stmt, err := db.SqlDB.Prepare("UPDATE t_user SET user_name=? where user_id=?")
	if err != nil {
		log.Fatalln(err)
	}
	rs, err := stmt.Exec(u.User_name, u.User_id)
	if err != nil {
		log.Fatalln(err)
	}
	ra, err := rs.RowsAffected()
	if err != nil {
		log.Fatalln(err)
	}
	return ra
}

func (u *SqlUser) DeleteUser(id string) int64 {
	rs, err := db.SqlDB.Exec("DELETE FROM t_user where user_id=?", id)
	if err != nil {
		log.Fatalln(err)
	}
	ra, err := rs.RowsAffected()
	if err != nil {
		log.Fatalln(err)
	}
	return ra
}
handler

然后把具体的 handler 封装到 api 中,handler 操作数据库,因此会引用 model 包。

package api

import (
	"fmt"
	"net/http"

	. "ginExample/models"

	"github.com/gin-gonic/gin"
)

func IndexApi(c *gin.Context) {
	c.String(http.StatusOK, "It works")
}

func GetUserApi(c *gin.Context) {
	var u SqlUser
	id := c.Param("id")
	u = u.GetUser(id)
	c.JSON(200, gin.H{
		"user": u.User_name,
	})
}

func AddUserApi(c *gin.Context) {
	User_id := c.Request.FormValue("User_id")
	User_name := c.Request.FormValue("User_name")
    // 这里的大小写一定要对应上结构体内的变量名

	u := models.SqlUser{User_id: User_id, User_name: User_name}

	rows, err := u.AddUser()
	if err != nil {
		log.Fatalln(err)
	}
	msg := fmt.Sprintf("insert successfully %d\n", rows)
	c.JSON(200, gin.H{
		"msg":       msg,
		"user_name": User_name,
	})
}

func UpdateUserApi(c *gin.Context) {
	User_id := c.Request.FormValue("User_id")
	User_name := c.Request.FormValue("User_name")

	u := models.SqlUser{User_id: User_id, User_name: User_name}
	row := u.UpdateUser()
	msg := fmt.Sprintf("update successful %d", row)
	c.JSON(200, gin.H{
		"msg": msg,
	})
}

func DeleteUserApi(c *gin.Context) {
	var u models.SqlUser
	id := c.Param("id")
	row := u.DeleteUser(id)
	msg := fmt.Sprintf("DELETE user successful %d", row)
	c.JSON(200, gin.H{
		"msg": msg,
	})
}
router

最后就是把路由抽离出来:

//router.go
package main

import (
    "github.com/gin-gonic/gin"
    . "ginExample/api"
)

func initRouter() *gin.Engine {
    router := gin.Default()
    router.GET("/", IndexApi)
    router.GET("/query/:id", GetUserApi)
    router.POST("/add", api.AddUserApi)
	router.PUT("/update/:id", api.UpdateUserApi)
	router.DELETE("/delete/:id", api.DeleteUserApi)
    return router
}
app 入口

最后就是 main 的 app 入口,将路由导入,同时在 main 即将结束时,关闭全局数据库连接池。

package main

import db "ginExample/database"

func main() {
    defer db.SqlDB.Close()
    router := initRouter()
    router.Run(":9999")
}

这里运行项目的话,不能像之前简单地使用 go run main.go ,因为 main 包含 main.gorouter.go,因此要运行 go run *.go,如果最终编译二进制项目,则运行 go build -o app

测试结果和上面是一样的,至此,基本的访问、操作数据库功能实现。

参考链接


  1. Gin实战:Gin+Mysql简单的Restful风格的API - 简书 (jianshu.com) ↩︎

本文转载于网络 如有侵权请联系删除

相关文章

  • Android浏览器的插件渲染模式简介

    大家好,又见面了,我是你们的朋友全栈君。Android2.1的浏览器插件有两种渲染模式,在android_npapi.h里的定义分别是:kBitmap_ANPDrawingModel=0;kSurface_ANPDrawingModel=1;(后面简称bitmap模式和surface模式)。在实例初始化的时候(Plugin函数列表的newp被调用时),Plug-in需要告知浏览器采用何种渲染方式。下面就对这两种渲染方式作简要介绍:1.bitmap模式kBitmap_ANPDrawingMode是传统的渲染方式,这种方式下,浏览器的底层会通过调用Plug-in提供的NPP_HandleEvent函数触发绘制事件,并把要渲染的bitmap的地址作为参数的一部分传给plug-in,plug-in只需要使用在初始化时获取到的相关的ANPInterface进行绘制即可。这种模式下,plug-in不需要考虑缩放问题,画面的缩放会由webkit自行处理。//===================================2.surface模式kSurface_ANPDrawingMode带有A

  • 【图像篇】OpenCV图像处理(六)---图像混合VS按位运算

    牛顿第一运动定律:简称牛顿第一定律。又称惯性定律、惰性定律。常见的完整表述:任何物体都要保持匀速直线运动或静止状态,直到外力迫使它改变运动状态为止。前言又是一期再见时,受疫情影响,小编已在家中上课两周了,一个多月没出过门了,实在是种说不出的感受,相信大家也一样,虽然待在家里,但不要除了手机还是手机,在study的路上,我们一直在前行。在上周的教程中我们了解到图像的色彩空间不光只有RGB,还有BGR,HSV等等,大家都学会了吗?今天我们进入图像的另一个学习方向--图像混合VS按位运算。图像混合一、简介图像混合,顾名思义就是将图像混合在一起,简单的来说,就是将两幅图像进行叠加在一起,实现两幅图像在一张图像中的现象,这样的实例相信大家肯定见过吧,下面进入正题哦!1.1原始图像img1:img2:1.1代码实践#-*-coding:utf-8-*- importcv2 image1=cv2.imread('cat.jpg')#根据路径读取一张图片 image2=cv2.imread('opencv.jpg')#根据路径读取一张图片 #对图片设置大小,图

  • 产品双月刊 | 腾讯云音视频云直播CSS(2021年5月-7月)

    近期,腾讯云直播有哪些 重大发布?他又带给我们了哪些 惊喜?请跟随我们的脚步一起来回顾!功能1:音频转码正式计费适用对象:直播全量用户主要优势:云直播音频转码功能正式计费,提供优质音频转码能力,支持对直播流进行音频转码率、音频转封装、音视频分离等服务,帮助客户降低适配成本、人力成本和机器成本。功能2:直播流量资源包新增抵扣规则适用对象:直播全量用户主要优势:支持1:1.8抵扣国际/港澳台标准直播下行流量产生的日结流量费用。支持1:2抵扣中国内地(大陆)快直播下行流量产生的日结流量费用。功能3:快直播海外计费上线适用对象:国际/港澳台用户主要优势:快直播新增支持国际/港澳台下行流量带宽业务,按照使用区域区分定价。功能4:拉流转推功能正式对外 适用对象:直播全量用户主要优势:拉流转推功能全量对外发布。提供内容拉取并推送的功能,无需进行直播推流,即可快速拉取已有的视频/直播,推送到目标地址上。功能5:直播录制功能联动点播配置适用对象:直播全量用户主要优势:直播录制联动点播降冷策略,并绑定任务流,实现录制文件的在点播实现自动化后处理。功能6:Web推流工具二期发布适用对象:直播全量用户主要优势

  • mysql 1075错误怎么办

    当我们使用mysql数据库的时候,非常容易遇上mysql1075的报错。在mysql中1075报错的原因是一个字段设置了自动递增,另外一个字段被设置为主键,发生冲突。在数据库当中,勾选自动递增的,系统会默认为主键,所以必须设置自增的一列为主键才可以。 看到这里,很多同学可能有所疑惑,树懒君来为你科普以下什么是主键和主键的自动递增字段每个表都应有一个主键字段。主键用于对表中的行(注:列表中的每一行)进行唯一标识。每个主键值在每个表中必须是唯一的。此外,主键字段不能为空。建表时通常这样设置:>>CREATETABLEmytable >>( >>idINTEGERUNSIGNEDNOTNULLAUTO_INCREMENT, >>titleVARCHAR(20),KEY(id) >>);复制就这样,就可以建立主键了注意:要确保主键字段不为空,我们必须向该字段添加NOTNULL设置。如果我们开始建表的时候没有设置任何字段为主键,那么,现在我们要添加一个主键或者说是要让一个字段变为自动编号,哪么该怎么办呢?>>alt

  • 一文读懂java中的Reference和引用类型

    一文读懂java中的Reference和引用类型简介java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型。java为引用类型专门定义了一个类叫做Reference。Reference是跟java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解java的垃圾回收是怎么工作的。本文先从java中的四种引用类型开始,一步一步揭开Reference的面纱。java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。强引用StrongReferencejava中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。我们看一个例子:publicclassStrongReferenceUsage{ @Test publicvoidstringReference(){ Objectobj=newObject(); } }复制上面我们new了一个Object对象,并将其赋值给obj,这个obj就是newObject()的强引用。强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。软引用So

  • 二阶微分方程的matlab解法,以动力学方程为例

    过冷水最近有接触一点点动力学的知识。作为动力学入门,当然的会解动力学方程了。于是本期过冷就教大家解动力学微分方程。 上图是两个小车通过弹簧链接起来的做来回摆动运动。应用拉克朗日方程建立系统的运动微分方程:需要二阶微分方程组转化为一阶微分方程组:根据得到的一阶微分方程组进行差微分求解就可以解得x1、x2随时间的变换。采用差分法就可以得到小车的运动轨迹具体代码是:x_chuzh1=[0;0.2;0;0]; c1=0.1; c2=0.4; k1=2; k2=3; m1=4; m2=6; [t,x]=ode45('dyna',[0,50],x_chuzh1,[],m1,m2,c1,c2,k1,k2); plot(t,x(:,4),'--b'); holdon set(gca,'FontSize',10,'Fontname','TimesNewRoman'); functionxp=dyna(t,x,flg,m1,m2,c1,c2,k1,k2) xp=zeros(4,1); xp(1)=x(2);

  • 如何进行全方面MySQL调优?

    一、MySQL逻辑架构  MySQL的逻辑架构分为连接层、服务层、存储引擎层和存储层。  和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。1.连接层(Connectors)  最上层是一些客户端和连接服务,包含本地socket通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。2.服务层(MySQLServer)  第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,

  • 2021年值得密切关注的科技趋势!

  • InlineHook新秀Dobby框架

    InlineHook新秀Dobby框架此篇文章为《利用纯地址进行HOOK》的上篇大家注意按照顺序阅读由于最近研究InlineHook(内联钩子),发现了一个不错的框架,Dobby(原名:HOOKZz)。这家伙是一个全平台的inlineHook框架,它用起来就和fishhook一样,我们先看一下如何使用。内联钩子:所谓InlineHook就是直接修改目标函数的头部代码。让它跳转到我们自定义的函数里面执行我们的代码,从而达到Hook的目的。这种Hook技术一般用在静态语言的HOOK上面编译Dobby首先我们将代码clone下来#depth用于指定克隆深度,为1即表示只克隆最近一次commit. gitclonehttps://github.com/jmpews/Dobby.git--depth=1复制注意由于这家伙是跨平台的,所以项目并不是一个Xcode工程,我们要使用cmake将这个工程编译成为Xcode工程。进入Dobby目录,创建一个文件夹,然后cmake编译工程cdDobby&&mkdirbuild_for_ios_arm64&&cdbuild_fo

  • Android客户端首次启动引导界面

    刚做完一个比赛项目,来写点以后能用着的东西–Android客户端的首次启动页面,而且这个以后复用的几率很大,也不怎么修改,特留下为以后准备,同时为初学者提供一个帮助。实现思路是:用SharedPreferences保存一个首次登陆的信息,默认是true,进入MainActivity后对其经行赋flase保存,把GuideActivity作为软件的启动界面,如果是第一次启动,就初始化该activity,不是的话直接跳转到应用主界面activity,这里有些不合理,启动界面如果设置成一个每次app启动都显示的界面最好了,这不是重点,我也就没实现。首先使用viewpager实现首先是引导页的布局文件:直接加入ViewPager就可以了,下面的LinearLayout是底部的小圆圈<?xmlversion="1.0"encoding="utf-8"? <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layou

  • 如何在 CentOS 8 上安装 TeamViewer

    雪梦科技本文最先发布在:https://www.itcoder.tech/posts/how-to-install-teamviewer-on-centos-8/TeamViewer是一个跨平台解决方案,它可以被用来进行远程控制,桌面共享,在线会议,以及计算机之间的文件传输。TeamViewer是一个专有计算机软件,它不被包含在CentOS的源仓库中。本文描述了如何在CentOS8上安装TeamViewer。一、前提条件你需要以root或者其他拥有sudo权限的用户身份登录,以便可以在你的CentOS系统上安装软件包。二、在CentOS上安装TeamViewer执行下面的步骤,在CentOS8上安装TeamViewer。01.TeamViewer依赖的软件包都在EPEL软件源仓库中。如果EPEL在你的系统上没有被启用,输入下面的命令,启用它:sudodnfinstallepel-release复制02.输入下面的命令,导入TeamViewer源仓库的GPGkeys:sudorpm--importhttps://dl.tvcdn.de/download/linux/signature/T

  • groovy使用stream语法递归筛选法求N以内的质数

    本人最近读完一本书《质数的孤独》,里面讲到孪生质数,就想查一下孪生质数的分布情况。其中主要用到了计算质数(素数)的方法,搜了一下,排名前几的都是用for循环来做的,感觉略微麻烦了一些,在比较一些还是觉得用递归筛选法来解决这个问题。新建List<Integer>,然后从第0位开始,如果后面的能被这个数整除,则从数组中移除改元素,以此类推,最后留下的就是质数(素数)。代码如下:staticvoidget(List<Integer>list,inttt){ intnum=list.get(tt); for(inti=tt+1;i<list.size();i++){ if(list.get(i)%num==0)list.remove(i--); } if(list.size()>++tt)get(list,tt); } 复制然后再去做相邻元素差求得孪生质数(孪生素数),贴一下求10000以内孪生质数(孪生素数)全部的代码:List<Integer>list=newArrayList<>(); for(inti=2;i<10000

  • 我以为我对Mysql索引很了解,直到我被阿里面试官22连击

    来源:Java之道整理:Java之道 本文来自一位不愿意透露姓名的粉丝投稿,由Hollis整理并"还原"了面试现场。相信很多人对于MySQL的索引都不陌生,索引(Index)是帮助MySQL高效获取数据的数据结构。因为索引是MySQL中比较重点的知识,相信很多人都有一定的了解,尤其是在面试中出现的频率特别高。楼主自认为自己对MySQL的索引相关知识有很多了解,而且因为最近在找工作面试,所以单独复习了很多关于索引的知识。但是,我还是图样图森破,直到我被阿里的面试官虐过之后我才知道,自己在索引方面的知识,只是个小学生水平。以下,是我总结的一次阿里面试中关于索引有关的问题以及知识点。1 索引概念、索引模型我们是怎么聊到索引的呢,是因为我提到我们的业务量比较大,每天大概有几百万的新数据生成,于是有了以下对话:Q:你们每天这么大的数据量,都是保存在关系型数据库中吗?A:是的,我们线上使用的是MySQL数据库Q:每天几百万数据,一个月就是几千万了,那你们有没有对于查询做一些优化呢?A:我们在数据库中创建了一些索引(我现在非常后悔我当时说了这句话)这里可以看到,阿里的面试官并不会

  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9977862.html写在前面千呼万唤始出来,首先,请允许我长吸一口气!真没想到一份来自28岁老程序员的自白这篇文章会这么火,更没想到的是张善友队长的公众号居然也转载了这篇文章,这就导致两天的时间就有两百多位读者朋友加入了.NETCore实战项目交流群(欢迎更多小伙伴进入交流.NETCore经验,群号:637326624)!这让我顿感亚历山大!我自己的文笔有多差我是知道的,所以就有点担心写不好!同时我也得到了很多朋友的鼓励,所以我会很认真的来分享每一篇文章,希望能对大家入门.NETCore有所帮助!当然一个人的能力是有限的,如果我的文章中有出现错误的话,也希望大家能够帮我指正,这样才能更好地服务更多的后来者!同时教程的编写我会采用敏捷开发的思想,先大致梳理下,后期会做持续更新的!这个系列我尽量每周三篇的速度来进行编写!面向的对象由于加群的大部分读者朋友都没怎么接触过.NETCore,甚至只是刚听说过.NETCore所以我会从最基础的概念开始写起,通过一个简单的CMS系统的实战项目,让你知其然更

  • 腾讯云人脸核身PaaS化服务

    PaaS化服务中,如何获取动作顺序与数字验证码?您需要调用以下对应的接口,以获取动作顺序或数字验证码(除地域信息外,无需传其他参数): 获取动作顺序。 获取数字验证码。 PaaS化服务中,传入视频大于5M,如何进行压缩?视频格式:H264编码标准的视频格式(如mp4,mov,avi,webm),分辨率支持270p~1080p,(推荐wxh=320x*),视频大小不超过8M,如果前端手机录制H5视频过大,推荐使用ffmpeg先进行压缩再传到核身服务中。 推荐压缩命令:./ffmpeg-y-verror-iSRC_VIDEO_FILE_PATH-presetveryfast-b:v1048576-vfformat=pix_fmts=yuv420p,fps=25,scale=320:-16DEST_VIDEO_FILE_NAME(只需要调整SRC_VIDEO_FILE_PATH,DEST_VIDEO_FILE_NAME两个参数) PaaS化服务中,数字活体模式中的四位数字可否自定义?不支持,四位数字需要调用GetLiveCode接口随机生成,调用接口可保证数字时实时生成而非事先规定,可以

  • 基于SSM风格的Java源代码生成器 一对一、一对多、多对多连接查询 摸鱼神器

    一、序言 UCodeCms是一款Maven版的Java源代码生成器,是快速构建项目的利器。代码生成器模块属于可拆卸模块,即按需引入。代码生成器生成SSM(Spring、SpringBoot、MybatisPlus)风格的源代码。 面试时经常提到面向对象编程,实际开发中常常是面向数据库编程,随着需求的快速变化,数据库的库表结构也需要相应变化,如何根据库表结构的变化,快速响应到源代码层次,是Java代码生成器主要的关切点。 功能亮点 实时读取库表结构元数据信息,比如表名、字段名、字段类型、注释等,选中修改后的表,点击一键生成,代码成即可提现出表结构的变化。 单表快速转化restful风格的API接口并对外暴露服务。对于百余张表的数据库,使用代码生成器让开发事半功倍。 多表连接查询。多表连接查询默认不开启,需要在全局文件中手动配置。开启后代码生成器会自动读取数据库元数据信息中的主外键关系,分别生成一对一、一对多、多对多风格的源代码。 生成的代码接口可通过Swagger暴露。 二、运行依赖服务 代码生成器运行依赖Mysql数据库、Redis服务,版本不限。 Mysql数据库 Mysql数据库中

  • 力扣刷题

    力扣刷题 你们总在悲痛或需要的时候祈祷,我愿你们也在完满的欢乐中及丰富的日子里祈祷。——《先知》纪伯伦 目录力扣刷题简单难度[1]两数之和[7]整数反转解法一:解法二:[20]有效括号解法一解法二解法三[21]合并两个有序链表解法一:解法二[26]删除有序数组中的重复项[27]移除元素解法一:♥[28]实现strStr()解法一:❤解法二:[34]在排序数组中查找元素的第一个和最后一个位置解法一:解法二:[66]加一解法一:[70]爬楼梯解法一:[83]删除排序链表中的重复元素解法一:解法二:[88]合并两个有序数组解法一:解法二:[94]二叉树的中序遍历解法一:❤解法二:❤解法三:[98]验证二叉搜索树解法一:解法二:[100]相同的树解法一:[101]对称二叉树解法一:解法二:解法三:[104]二叉树的最大深度解法一:❤解法二:[108]将有序数组转换为二叉搜索树解法一:[111]二叉树的最小深度解法一:解法二:[114]二叉树展开为链表解法一:[118]杨辉三角解法一:[121]买卖股票的最佳时机解法一:[125]验证回文串解法一:解法二:♥[136]只出现一次的数字解法一:

  • 关于NAT hairpin 功能相关知识

    参考: NATHairpin技术实验介绍-南京建策科技股份有限公司(jiancenj.com) 在NAT中的hairpin是什么意思_百度知道(baidu.com) [史上最详细]H3C路由器NAT典型配置案例-百度文库(baidu.com) 一、nathairpin(端口回流或者NAT回环)功能产生的背景 1、内网主机使用域名方式访问内网服务器。 2、从安全角度,防止内网主机直接访问内网服务器(提防内鬼),必须通过防火墙绕行才允许访问内网服务器。 二、典型组网图   三、配置命令 1.配置EasyIP [H3C]aclbasic2000 [H3C-acl-ipv4-basic-2000]rule0permitsource192.168.1.00.0.0.255 [H3C]interfaceGigabitEthernet0/0 [H3C-GigabitEthernet0/0]natoutbound2000 配置作用:将私网用户访问FTP服务器的报文中的源IP地址转换为GigabitEthernet0/0接口的IP地址。 2.配置NATServer [H3C-GigabitE

  • 项目测试的测试工作

      031302620马凛凛(队长) 031302619吕昆明 031302319汪毓顺 031302404陈俊达   测试工作安排:       陈俊达:数据库结构测试;       吕昆明:页面显示效果代码测试;       汪毓顺:php功能代码测试;       马凛凛:模块整合测试       以上为项目冲刺初始时指定的测试分工,具体实施时根据实际情况作调整;   测试工具选择应用:       马凛凛:模块整合测试       页面显示效果测试:chrome浏览器       php功能测试:手动       模块整合测试:手工整合       由于我们组采用的web代码基本只包含一下几类:html、css、js和php,其中页面语言只要用chrome和firefox浏览器自带的f12开发者工具测试即可,而php语句由于涉及的基本是web开发和mysql部分,因此基本错误类型都采用手工检查的方式进行测试,具体测试情况和样例见后文。   测试用例和文档:         1.数据库模块测试            这个模块要测试的主要是

  • AtCoder Beginner Contest 222 D - Between Two Arrays

    题意 题解 公理1:此题支持O(N2)。 公理2:对于每个合法序列,其值单调不减。 灵感1(公理1):是否可以DP? 性质1(公理2):在一个序列不断"递增"的过程中,其能够选择的最小值单调不降。 公理3:方案数具有累加性,不具有后效性。 答案1(灵感1、公理3):可以保存下所有状态,状态设计为dp[i][j],其中i为处理到第几个数字,j为当前能够接受的最小值,dp[i][j]为当前方案数。 性质2(公理2、公理3):对于每一个数字$i\left(1\leqi\leqn\right)$,当设置"能够选择的最小值"为$j$时,其方案数为:数字为i-1,且"能够选择的最小值"<=j的方案数的和。 答案2(性质2):$dp[i][j]=\sum_{0}^{j}dp[i-1][j]$ #include<cstdio> #include<iostream> #defineintlonglong usingnamespacestd; constintMAXN=4000; constintMOD=998244353; inta[MAXN],b[MAXN];

  • docker 3

    docker3 导出/导入镜像 导出dockerimagesavehello-world-oC:\Users\1998亢小乐1007\Desktop\hello-world.taz  导入dockerimageload-ihello-world.taz   docker容器管理 dockerrun镜像名称这个过程可以理解为:把镜像文件创建成docker容器的一部分,然后再进行启动。特别需要注意的是:容器内的进程必须是前台运行状态,否则容器直接退出 容器命令 在运行镜像中,后面需要带一些指令的信息,这些指令的信息具体汇总为如下: •-d:后台运行 •-it:交互式命令(bash) •--rm:容器挂掉后自动被删除 •--name:给容器起一个名字 •-p:端口映射 -P:指定任意的端口   log查看 查看容器实时的错误日志:dockerlogs-fCONTAINERID linuxmav查看 只显示后几行dockerlogsCONTAINERID|tall-n 只显示前几行dockerlogsCONTAINERID|head-n   保存

相关推荐

推荐阅读