Commits (4)
package db
import (
"github.com/sirupsen/logrus"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// DB 全局数据库实例(保持不变)
var DB *gorm.DB
// InitDB 初始化 PostgreSQL 数据库(修改:返回error类型)
func InitDB() error { // 关键:添加error返回值
// PostgreSQL 连接字符串(替换为你的实际配置)
dsn := "host=localhost port=5432 user=go_zyz password=zyz757605 dbname=mygo sslmode=disable client_encoding=utf8"
var err error
// 连接数据库
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
logrus.WithError(err).Error("数据库连接失败") // 改为Error(不退出)
return err // 返回错误
}
// 自动迁移表(创建/更新Comment表)
if err := DB.AutoMigrate(&Comment{}); err != nil {
logrus.WithError(err).Error("数据库迁移失败") // 改为Error(不退出)
return err // 返回错误
}
logrus.Info("PostgreSQL 数据库初始化成功")
return nil // 成功返回nil
}
package db
import (
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// Comment 数据库模型
type Comment struct {
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"not null"`
Comment string `json:"content" gorm:"not null"`
}
// CreateComment 创建评论
func CreateComment(name, content string) (*Comment, error) {
comment := &Comment{
Name: name,
Comment: content,
}
if err := DB.Create(comment).Error; err != nil {
logrus.WithError(err).Error("创建评论失败")
return nil, err
}
logrus.WithFields(logrus.Fields{
"comment_id": comment.ID,
"name": comment.Name,
}).Info("评论创建成功")
return comment, nil
}
// GetComments 获取评论(支持分页)
func GetComments(page, size int) ([]Comment, int64, error) {
var comments []Comment
var total int64
// 查询总数
if err := DB.Model(&Comment{}).Count(&total).Error; err != nil {
logrus.WithError(err).Error("获取评论总数失败")
return nil, 0, err
}
// 分页查询
if size == -1 {
// 返回所有评论
if err := DB.Find(&comments).Error; err != nil {
logrus.WithError(err).Error("获取所有评论失败")
return nil, 0, err
}
} else {
// 分页
offset := (page - 1) * size
if err := DB.Limit(size).Offset(offset).Find(&comments).Error; err != nil {
logrus.WithError(err).Error("获取分页评论失败")
return nil, 0, err
}
}
logrus.WithFields(logrus.Fields{
"page": page,
"size": size,
"total": total,
}).Info("评论查询成功")
return comments, total, nil
}
// DeleteComment 删除评论
func DeleteComment(id int) error {
result := DB.Where("id = ?", id).Delete(&Comment{})
if err := result.Error; err != nil {
logrus.WithError(err).Error("删除评论失败")
return err
}
if result.RowsAffected == 0 {
logrus.WithField("comment_id", id).Warn("评论不存在")
return gorm.ErrRecordNotFound
}
logrus.WithField("comment_id", id).Info("评论删除成功")
return nil
}
module github.com/zyz/comment-api
go 1.21
require (
github.com/sirupsen/logrus v1.9.3
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.30.0
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
package main
import (
"net/http"
"github.com/sirupsen/logrus"
"github.com/zyz/comment-api/db" // 保留数据库包导入(正确)
)
// ------------------------------
// CORS中间件(保持不变,解决跨域问题)
// ------------------------------
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 允许的前端Origin(修改为你的前端实际地址,如http://localhost:5500)
allowedOrigins := []string{
"http://localhost:5500", // 保留原有的localhost
"http://127.0.0.1:5500", // 添加前端实际的Origin(Live Server的地址)
}
origin := r.Header.Get("Origin")
for _, o := range allowedOrigins {
if o == origin {
w.Header().Set("Access-Control-Allow-Origin", origin)
break
}
}
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
// 初始化日志(保持不变)
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.Info("启动服务器...")
// 初始化数据库(修改:接收error并处理)
if err := db.InitDB(); err != nil { // 现在db.InitDB()返回error,可正确接收
logrus.WithError(err).Fatal("数据库初始化失败") // 这里用Fatal退出程序
}
logrus.Info("PostgreSQL 数据库初始化成功")
// 注册路由(直接使用main包中的函数,无需前缀)
mux := http.NewServeMux()
mux.HandleFunc("/comment/get", GetCommentsHandler) // 直接使用GetCommentsHandler(来自server.go)
mux.HandleFunc("/comment/add", AddCommentHandler) // 直接使用AddCommentHandler(来自server.go)
mux.HandleFunc("/comment/delete", DeleteCommentHandler) // 直接使用DeleteCommentHandler(来自server.go)
// 应用CORS中间件(保持不变)
handler := corsMiddleware(mux)
// 启动服务器(保持不变)
addr := ":8080"
logrus.WithField("address", addr).Info("服务器启动")
if err := http.ListenAndServe(addr, handler); err != nil {
logrus.WithError(err).Fatal("服务器启动失败")
}
}
package main // 必须是main包,和main.go同目录
import (
"encoding/json"
"net/http"
"strconv"
"github.com/sirupsen/logrus"
"github.com/zyz/comment-api/db"
"gorm.io/gorm"
)
// Response 统一响应格式(保持不变)
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// GetCommentsResponse 获取评论响应(保持不变)
type GetCommentsResponse struct {
Total int64 `json:"total"`
Comments []db.Comment `json:"comments"`
}
// ------------------------------
// 导出函数(首字母大写,main包中可直接使用)
// ------------------------------
func GetCommentsHandler(w http.ResponseWriter, r *http.Request) { // 首字母大写,导出
log := logrus.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
})
if r.Method != http.MethodGet {
respondError(w, http.StatusMethodNotAllowed, "方法不被允许", nil, log)
return
}
page, size := parsePaginationParams(r)
log = log.WithFields(logrus.Fields{
"page": page,
"size": size,
})
comments, total, err := db.GetComments(page, size)
if err != nil {
respondError(w, http.StatusInternalServerError, "查询评论失败", nil, log.WithError(err))
return
}
data := GetCommentsResponse{
Total: total,
Comments: comments,
}
respondSuccess(w, data, log)
}
func AddCommentHandler(w http.ResponseWriter, r *http.Request) { // 首字母大写,导出
log := logrus.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
})
if r.Method != http.MethodPost {
respondError(w, http.StatusMethodNotAllowed, "方法不被允许", nil, log)
return
}
var req struct {
Name string `json:"name"`
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "无效的请求体", nil, log.WithError(err))
return
}
if req.Name == "" || req.Content == "" {
respondError(w, http.StatusBadRequest, "姓名和内容为必填项", nil, log)
return
}
comment, err := db.CreateComment(req.Name, req.Content)
if err != nil {
respondError(w, http.StatusInternalServerError, "创建评论失败", nil, log.WithError(err))
return
}
respondSuccess(w, comment, log)
}
func DeleteCommentHandler(w http.ResponseWriter, r *http.Request) { // 首字母大写,导出
log := logrus.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
})
if r.Method != http.MethodPost {
respondError(w, http.StatusMethodNotAllowed, "方法不被允许", nil, log)
return
}
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
respondError(w, http.StatusBadRequest, "无效的评论 ID", nil, log.WithError(err))
return
}
if err := db.DeleteComment(id); err != nil {
if err == gorm.ErrRecordNotFound {
respondError(w, http.StatusNotFound, "评论不存在", nil, log)
} else {
respondError(w, http.StatusInternalServerError, "删除评论失败", nil, log.WithError(err))
}
return
}
respondSuccess(w, nil, log)
}
// parsePaginationParams 解析分页参数(保持不变)
func parsePaginationParams(r *http.Request) (page, size int) {
pageStr := r.URL.Query().Get("page")
sizeStr := r.URL.Query().Get("size")
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
size, err = strconv.Atoi(sizeStr)
if err != nil || size < -1 || size == 0 {
size = 10
}
return page, size
}
// respondSuccess 发送成功响应(保持不变)
func respondSuccess(w http.ResponseWriter, data interface{}, log *logrus.Entry) {
resp := Response{
Code: 0,
Msg: "成功",
Data: data,
}
sendResponse(w, http.StatusOK, resp, log)
}
// respondError 发送错误响应(保持不变)
func respondError(w http.ResponseWriter, status int, msg string, data interface{}, log *logrus.Entry) {
resp := Response{
Code: status,
Msg: msg,
Data: data,
}
log.WithFields(logrus.Fields{
"status": status,
"msg": msg,
}).Error("请求失败")
sendResponse(w, status, resp, log)
}
// sendResponse 发送JSON响应(保持不变)
func sendResponse(w http.ResponseWriter, status int, resp Response, log *logrus.Entry) {
// 关键:添加charset=utf-8
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.WithError(err).Error("发送响应失败")
}
}