From 5c437d10f47c9bad18c119885852726385ca35c9 Mon Sep 17 00:00:00 2001 From: Haderacher <144252706+Haderacher@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:32:13 +0800 Subject: [PATCH 1/2] Finish! --- Haderacher/HDU-QA-Platform/.dockerignore | 3 + Haderacher/HDU-QA-Platform/.gitignore | 16 + Haderacher/HDU-QA-Platform/Dockerfile | 35 ++ Haderacher/HDU-QA-Platform/README.md | 45 ++ Haderacher/HDU-QA-Platform/api/http/v1/api.go | 370 ++++++++++++ .../HDU-QA-Platform/api/http/v1/entity.go | 59 ++ Haderacher/HDU-QA-Platform/cmd/main.go | 16 + Haderacher/HDU-QA-Platform/conf/app.yml | 26 + Haderacher/HDU-QA-Platform/config/config.go | 129 ++++ Haderacher/HDU-QA-Platform/doc/img/img.png | Bin 0 -> 48208 bytes Haderacher/HDU-QA-Platform/docker-compose.yml | 62 ++ Haderacher/HDU-QA-Platform/go.mod | 54 ++ Haderacher/HDU-QA-Platform/go.sum | 569 ++++++++++++++++++ .../HDU-QA-Platform/internal/cache/cache.go | 123 ++++ .../HDU-QA-Platform/internal/dao/answer.go | 41 ++ .../HDU-QA-Platform/internal/dao/question.go | 54 ++ .../HDU-QA-Platform/internal/dao/user.go | 45 ++ .../HDU-QA-Platform/internal/model/model.go | 50 ++ .../HDU-QA-Platform/internal/router/router.go | 108 ++++ .../internal/service/answer.go | 51 ++ .../internal/service/entity.go | 69 +++ .../internal/service/question.go | 73 +++ .../HDU-QA-Platform/internal/service/user.go | 259 ++++++++ Haderacher/HDU-QA-Platform/ollama-restfull | 1 + .../HDU-QA-Platform/pkg/constant/const.go | 19 + Haderacher/HDU-QA-Platform/utils/db.go | 50 ++ Haderacher/HDU-QA-Platform/utils/redis.go | 65 ++ Haderacher/HDU-QA-Platform/utils/utils.go | 45 ++ 28 files changed, 2437 insertions(+) create mode 100644 Haderacher/HDU-QA-Platform/.dockerignore create mode 100644 Haderacher/HDU-QA-Platform/.gitignore create mode 100644 Haderacher/HDU-QA-Platform/Dockerfile create mode 100644 Haderacher/HDU-QA-Platform/README.md create mode 100644 Haderacher/HDU-QA-Platform/api/http/v1/api.go create mode 100644 Haderacher/HDU-QA-Platform/api/http/v1/entity.go create mode 100644 Haderacher/HDU-QA-Platform/cmd/main.go create mode 100644 Haderacher/HDU-QA-Platform/conf/app.yml create mode 100644 Haderacher/HDU-QA-Platform/config/config.go create mode 100644 Haderacher/HDU-QA-Platform/doc/img/img.png create mode 100644 Haderacher/HDU-QA-Platform/docker-compose.yml create mode 100644 Haderacher/HDU-QA-Platform/go.mod create mode 100644 Haderacher/HDU-QA-Platform/go.sum create mode 100644 Haderacher/HDU-QA-Platform/internal/cache/cache.go create mode 100644 Haderacher/HDU-QA-Platform/internal/dao/answer.go create mode 100644 Haderacher/HDU-QA-Platform/internal/dao/question.go create mode 100644 Haderacher/HDU-QA-Platform/internal/dao/user.go create mode 100644 Haderacher/HDU-QA-Platform/internal/model/model.go create mode 100644 Haderacher/HDU-QA-Platform/internal/router/router.go create mode 100644 Haderacher/HDU-QA-Platform/internal/service/answer.go create mode 100644 Haderacher/HDU-QA-Platform/internal/service/entity.go create mode 100644 Haderacher/HDU-QA-Platform/internal/service/question.go create mode 100644 Haderacher/HDU-QA-Platform/internal/service/user.go create mode 160000 Haderacher/HDU-QA-Platform/ollama-restfull create mode 100644 Haderacher/HDU-QA-Platform/pkg/constant/const.go create mode 100644 Haderacher/HDU-QA-Platform/utils/db.go create mode 100644 Haderacher/HDU-QA-Platform/utils/redis.go create mode 100644 Haderacher/HDU-QA-Platform/utils/utils.go diff --git a/Haderacher/HDU-QA-Platform/.dockerignore b/Haderacher/HDU-QA-Platform/.dockerignore new file mode 100644 index 0000000..958573f --- /dev/null +++ b/Haderacher/HDU-QA-Platform/.dockerignore @@ -0,0 +1,3 @@ +# 忽略版本控制文件 +.git +.gitignore diff --git a/Haderacher/HDU-QA-Platform/.gitignore b/Haderacher/HDU-QA-Platform/.gitignore new file mode 100644 index 0000000..151a29d --- /dev/null +++ b/Haderacher/HDU-QA-Platform/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.xml +*.iml +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/Haderacher/HDU-QA-Platform/Dockerfile b/Haderacher/HDU-QA-Platform/Dockerfile new file mode 100644 index 0000000..917ff57 --- /dev/null +++ b/Haderacher/HDU-QA-Platform/Dockerfile @@ -0,0 +1,35 @@ +# Use the official Golang image as the base image +FROM golang:1.23-alpine AS builder + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go.mod and go.sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Build the Go app +RUN go build -o main ./cmd/main.go + +# Start a new stage from scratch +FROM alpine:latest + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy the Pre-built binary file from the previous stage +COPY --from=builder /app/main /app/main + +# Copy the configuration files +COPY --from=builder /app/conf /app/conf + +# Expose port 8082 to the outside world +EXPOSE 8082 + +# Command to run the executable +ENTRYPOINT ["/app/main"] \ No newline at end of file diff --git a/Haderacher/HDU-QA-Platform/README.md b/Haderacher/HDU-QA-Platform/README.md new file mode 100644 index 0000000..e399461 --- /dev/null +++ b/Haderacher/HDU-QA-Platform/README.md @@ -0,0 +1,45 @@ +# 介绍 + +这是以github上的一个开源框架为基础,并借助了ai的帮助,由我开发的用户管理系统+问答系统。 + +本项目支持docker部署! + +## 用户管理系统 +实现了基于session会话机制的用户登录系统。注册时在mysql中存放用户名和密码。 +以后每次登录,服务端都会保存一个最新session会话存放于redis缓存,同时客户端保存为cookie,这个session有过期时间。以后每次登录,都会更新这个session。 + +实现了用于对请求进行身份验证的中间件函数`AuthMiddleWare`,在使用本系统的其他所有服务,如用户信息请求,访问问答系统时,都需要经过这个中间件进行 +鉴权,鉴权成功才能访问。 + +实现了用户登出功能。当客户端请求登出,会在服务端删除redis中的session,这样客户端的cookie就失效了,需要重新登录。 + +## 问答系统 +没什么好说的,都是对问题和回答的增删改查 + +## 数据库设计 +### mysql负责存放三张表: +1. 用户(users):存放用户信息和密码 + - 主键:用户id +2. 问题(questions):存放问题 + - 主键:回答id + - 外键:用户id、 +3. 回答(answers):存放回答 + - 主键:回答id + - 外键:问题id、用户id + +即问题与回答之间是一对多的关系 +问题与用户、回答与用户是一对一的关系 + +![img.png](doc/img/img.png) + + +## 系统架构设计/设计模式 +1. 采用了分层解耦的思想。采用传统web开发的分层架构,主要分为controller、service、dao三层。使得代码功能之间的耦合度减小,每一层代码只服务于 +特定的功能,增加了代码的可维护性。 +2. 实现了基于session会话机制的用户登录系统。用户不用每次都输入密码,且redis读写速度快。优化了用户体验。 +3. 实现了单例设计模式。将所有的配置信息都放在配置文件中,通过`sync.Once`来保证并发安全,同时确保配置信息、数据库连接只会被初始化一次。 +4. 介入了llama ai,用flask框架写了一个服务器,与本项目之间通过restfulapi通信。 + + +## 带改进 +1. 没有体现依赖注入的思想 \ No newline at end of file diff --git a/Haderacher/HDU-QA-Platform/api/http/v1/api.go b/Haderacher/HDU-QA-Platform/api/http/v1/api.go new file mode 100644 index 0000000..7a2b780 --- /dev/null +++ b/Haderacher/HDU-QA-Platform/api/http/v1/api.go @@ -0,0 +1,370 @@ +package v1 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "gouse/config" + "gouse/internal/dao" + "gouse/internal/service" + "gouse/pkg/constant" + "gouse/utils" + "io/ioutil" + "net/http" + "strconv" + "time" +) + +// Ping 健康检查 +// Ping() 函数的参数 c 是一个 gin.Context 上下文对象,代表了一个 HTTP 请求的上下文。 +// 通过该对象,我们可以获取请求的信息、设置响应的内容等操作。 +func Ping(c *gin.Context) { + // 获取这个包含了应用程序的配置信息的全局配置对象,app 服务配置信息 + appConfig := config.GetGlobalConf().AppConfig + + // 将 appConfig 这个全局配置对象转换成格式化的 JSON 字符串 + // "" 是 JSON 字符串中的字段间的分隔符," " 是每一级缩进的字符串。 + confInfo, _ := json.MarshalIndent(appConfig, "", " ") + + // 将配置信息和应用程序信息格式化为一个字符串。(这是一个格式化操作) + appInfo := fmt.Sprintf("app_name: %s\nversion: %s\n\n%s", appConfig.AppName, appConfig.Version, + string(confInfo)) + + // 使用 gin.Context 的 String 方法, + // 将 HTTP 状态码设置为 200(OK),并将 appInfo 字符串作为响应内容发送给客户端。 + // 所以这段代码的逻辑就是,客户端 Ping 服务器,服务器返回这个响应内容 appInfo 给客户端 + c.String(http.StatusOK, appInfo) +} + +// Register 注册 +func Register(c *gin.Context) { + // req 变量用于存储注册请求信息 + req := &service.RegisterRequest{} + + // rsp 变量用于存储 http请求的响应信息 + rsp := &HttpResponse{} + + // ShouldBindJSON(&req) 解析 JSON 格式的请求体,并将解析结果存储在 req 变量中。 + // 如果解析过程中发生错误,错误信息将存储在 err 变量中 + err := c.ShouldBindJSON(&req) + + // 如果解析请求参数时出现错误,将错误信息打印到日志中, + // 并通过 rsp.ResponseWithError 方法返回带有错误信息的 HTTP 响应。 + if err != nil { + log.Errorf("request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + // 如果没有解析错误,则调用名为 Register 的服务函数处理注册业务逻辑。 + // 如果处理过程中发生错误,将错误信息通过 rsp.ResponseWithError 方法返回给客户端。 + if err := service.Register(req); err != nil { + rsp.ResponseWithError(c, CodeRegisterErr, err.Error()) + return + } + + // 如果注册逻辑执行成功, + // 调用 rsp.ResponseSuccess 方法返回一个表示成功的 HTTP 响应给客户端 + rsp.ResponseSuccess(c) +} + +// Login 登录 +func Login(c *gin.Context) { + // req 存储登录请求的信息 + req := &service.LoginRequest{} + + // rsp 存储 HTTP请求的响应信息 + rsp := &HttpResponse{} + + // ShouldBindJSON(&req) 解析 JSON 格式的请求体,并将解析结果存储在 req 变量中。 + // 如果解析过程中发生错误,错误信息将存储在 err 变量中 + err := c.ShouldBindJSON(&req) + if err != nil { + log.Errorf("request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + // 生成一个唯一的 uuid + // 这里使用用户名和当前时间拼接后进行 MD5 哈希算法生成 + uuid := utils.Md5String(req.UserName + time.Now().GoString()) + + // 将生成的 uuid 存入上下文(context)中,以便后续使用 + ctx := context.WithValue(context.Background(), "uuid", uuid) + + // 输出登录的开始日志,记录用户名和密码 + log.Infof("loggin start,user:%s, password:%s", req.UserName, req.PassWord) + + // 调用 service.Login 函数(检查用户名和密码是否正确) + // 如果登录失败,将返回错误信息,并使用 rsp 对象构建错误响应 + session, err := service.Login(ctx, req) + if err != nil { + rsp.ResponseWithError(c, CodeLoginErr, err.Error()) + return + } + + // 登陆成功,使用 c.SetCookie 函数设置一个名为 constant.SessionKey 的 Cookie。下面是函数参数介绍: + // SessionKey 是 cookie 的名称; session 是登录成功生成的 session 值; CookieExpire 决定了 cookie 的有效期 + // “/” 是 cookie 的路径(表示该 Cookie 对所有路径都有效); "" 是 cookie 的域名(空字符串表示该 Cookie 对所有域名都有效) + // false 是指定该 Cookie 只能通过 HTTP 协议传输,不能通过 JavaScript 访问; true 是指定该 Cookie 在安全的 HTTPS 连接中也会被传输 + c.SetCookie(constant.SessionKey, session, constant.CookieExpire, "/", "", false, true) + + // 如果注册逻辑执行成功, + // 调用 rsp.ResponseSuccess 方法返回一个表示成功的 HTTP 响应给客户端 + rsp.ResponseSuccess(c) +} + +// Logout 登出 +func Logout(c *gin.Context) { + // 获取请求中的名为 SessionKey 的 cookie 值,并赋值给 session 变量 + session, _ := c.Cookie(constant.SessionKey) + + // 创建一个 SessionKey 和 session 的键值对到上下文中 + ctx := context.WithValue(context.Background(), constant.SessionKey, session) + + // req 存放登出请求的结构体对象指针 + req := &service.LogoutRequest{} + + // rsp 存储 HTTP请求的响应信息 + rsp := &HttpResponse{} + + // ShouldBindJSON(&req) 解析 JSON 格式的请求体,并将解析结果存储在 req 变量中 + err := c.ShouldBindJSON(req) + if err != nil { + log.Errorf("bind get logout request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + // 生成一个唯一的 uuid + uuid := utils.Md5String(req.UserName + time.Now().GoString()) + + // 更新上下文对象,把 uuid 存储进去(这样上下文就存在两个键值对了) + ctx = context.WithValue(ctx, "uuid", uuid) + + // 实现 Logout() 登出操作的具体逻辑 + if err := service.Logout(ctx, req); err != nil { + rsp.ResponseWithError(c, CodeLogoutErr, err.Error()) + return + } + + // 设置一个过期时间为负值的 Cookie,实现删除客户端浏览器中存储的会话标识的目的,即实现用户的登出操作 + c.SetCookie(constant.SessionKey, session, -1, "/", "", false, true) + rsp.ResponseSuccess(c) +} + +// GetUserInfo 获取用户信息 +func GetUserInfo(c *gin.Context) { + // 从 HTTP 请求的查询参数中获取用户名 userName + userName := c.Query("username") + + // 从 HTTP 请求中获取会话标识 session + session, _ := c.Cookie(constant.SessionKey) + + // 创建了个上下文,往里面存了个键值对 + ctx := context.WithValue(context.Background(), constant.SessionKey, session) + + // req 存的是获取用户请求的结构体对象,顺带初始化了 UserName 字段 + req := &service.GetUserInfoRequest{ + UserName: userName, + } + + // rsp 存储 HTTP请求的响应信息 + rsp := &HttpResponse{} + + // 生成一个唯一的 uuid + uuid := utils.Md5String(req.UserName + time.Now().GoString()) + + // 把 uuid 也存进上下文 + ctx = context.WithValue(ctx, "uuid", uuid) + + // 从缓存中获取用户信息 + userInfo, err := service.GetUserInfo(ctx, req) + if err != nil { + rsp.ResponseWithError(c, CodeGetUserInfoErr, err.Error()) + return + } + + // 这个返回函数给客户端多返回了一个 data 也就是实际的数据(从缓存中获取的用户信息) + rsp.ResponseWithData(c, userInfo) +} + +// UpdateNickName 更新用户昵称 +func UpdateNickName(c *gin.Context) { + // 存放修改用户信息返回的结构体对象 + req := &service.UpdateNickNameRequest{} + + // rsp 存储 HTTP请求的响应信息 + rsp := &HttpResponse{} + + // 解析 JSON 格式的请求体,并将解析结果存储在 req 变量中 + err := c.ShouldBindJSON(req) + if err != nil { + log.Errorf("bind update user info request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + // 获取 Cookie 值 + session, _ := c.Cookie(constant.SessionKey) + log.Infof("UpdateNickName|session=%s", session) + + // 创建了个上下文,往里面存了个键值对 + ctx := context.WithValue(context.Background(), constant.SessionKey, session) + + // 生成唯一的 uuid,也存进上下文中 + uuid := utils.Md5String(req.UserName + time.Now().GoString()) + ctx = context.WithValue(ctx, "uuid", uuid) + + // 更改用户信息 + if err := service.UpdateUserNickName(ctx, req); err != nil { + rsp.ResponseWithError(c, CodeUpdateUserInfoErr, err.Error()) + return + } + rsp.ResponseSuccess(c) +} + +func CreateQuestion(c *gin.Context) { + // 获取请求中的名为 SessionKey 的 cookie 值,并赋值给 session 变量 + session, _ := c.Cookie(constant.SessionKey) + + // 创建一个 SessionKey 和 session 的键值对到上下文中 + ctx := context.WithValue(context.Background(), constant.SessionKey, session) + // req 变量用于存储创建问题请求信息 + req := &service.CreateQuestionRequest{} + + // rsp 变量用于存储 http请求的响应信息 + rsp := &HttpResponse{} + + err := c.ShouldBindJSON(&req) + if err != nil { + log.Errorf("request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + // 如果没有解析错误,则调用名为 CreateQuestion 的服务函数处理创建问题业务逻辑。 + // 如果处理过程中发生错误,将错误信息通过 rsp.ResponseWithError 方法返回给客户端。 + if err := service.CreateQuestion(ctx, req); err != nil { + rsp.ResponseWithError(c, CodeRegisterErr, err.Error()) + return + } + + rsp.ResponseSuccess(c) +} + +func ModifyQuestion(c *gin.Context) { + // 存放修改问题的结构体对象 + req := &service.ModifyQuestionRequest{} + + // rsp 存储 HTTP请求的响应信息 + rsp := &HttpResponse{} + + // 解析 JSON 格式的请求体,并将解析结果存储在 req 变量中 + err := c.ShouldBindJSON(req) + if err != nil { + log.Errorf("bind update user info request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + if err := service.ModifyQuestion(req); err != nil { + rsp.ResponseWithError(c, CodeUpdateUserInfoErr, err.Error()) + return + } + rsp.ResponseSuccess(c) +} + +func DeleteQuestion(c *gin.Context) { + // 存放修改问题的结构体对象 + req := &service.DeleteQuestionRequest{} + + // rsp 存储 HTTP请求的响应信息 + rsp := &HttpResponse{} + + // 解析 JSON 格式的请求体,并将解析结果存储在 req 变量中 + err := c.ShouldBindJSON(req) + if err != nil { + log.Errorf("bind update user info request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + err = service.DeleteQuestion(req) + if err != nil { + rsp.ResponseWithError(c, CodeRegisterErr, err.Error()) + return + } +} + +func CreateAnswer(c *gin.Context) { + // 获取请求中的名为 SessionKey 的 cookie 值,并赋值给 session 变量 + session, _ := c.Cookie(constant.SessionKey) + + // 创建一个 SessionKey 和 session 的键值对到上下文中 + ctx := context.WithValue(context.Background(), constant.SessionKey, session) + // req 变量用于存储创建问题请求信息 + req := &service.CreateAnswerRequest{} + + // rsp 变量用于存储 http请求的响应信息 + rsp := &HttpResponse{} + + err := c.ShouldBindJSON(&req) + if err != nil { + log.Errorf("request json err %v", err) + rsp.ResponseWithError(c, CodeBodyBindErr, err.Error()) + return + } + + if err := service.CreateAnswer(ctx, req); err != nil { + rsp.ResponseWithError(c, CodeRegisterErr, err.Error()) + return + } + + rsp.ResponseSuccess(c) +} + +func ShowQuestion(c *gin.Context) { + + idStr := c.Query("id") + id, err := strconv.Atoi(idStr) + if err != nil { + // Handle the error, for example, return a bad request response + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + info, _ := dao.ShowQuestionInDetail(id) + c.JSON(http.StatusOK, info) +} + +func AnswerByAi(c *gin.Context) { + + url := "http://localhost:5000/chat" + jsonStr := []byte(`{"msg":"hello"}`) // Replace with your JSON payload + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) + if err != nil { + fmt.Println("Error creating request:", err) + return + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response body:", err) + return + } + + fmt.Println("Response:", string(body)) + c.JSON(http.StatusOK, string(body)) +} diff --git a/Haderacher/HDU-QA-Platform/api/http/v1/entity.go b/Haderacher/HDU-QA-Platform/api/http/v1/entity.go new file mode 100644 index 0000000..438d869 --- /dev/null +++ b/Haderacher/HDU-QA-Platform/api/http/v1/entity.go @@ -0,0 +1,59 @@ +package v1 + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +// 全局常量,用于设置错误码 +const ( + CodeSuccess ErrCode = 0 // http请求成功 + CodeBodyBindErr ErrCode = 10001 // 参数绑定错误 + CodeParamErr ErrCode = 10002 // 请求参数不合法 + CodeRegisterErr ErrCode = 10003 // 注册错误 + CodeLoginErr ErrCode = 10003 // 登录错误 + CodeLogoutErr ErrCode = 10004 // 登出错误 + CodeGetUserInfoErr ErrCode = 10005 // 获取用户信息错误 + CodeUpdateUserInfoErr ErrCode = 10006 // 更新用户信息错误 +) + +type ( + DebugType int // debug类型 + ErrCode int // 错误码 +) + +// HttpResponse http独立请求返回结构体 +type HttpResponse struct { + Code ErrCode `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` +} + +// ResponseWithError http请求返回处理函数 +func (rsp *HttpResponse) ResponseWithError(c *gin.Context, code ErrCode, msg string) { + // 将错误信息填充到 HttpResponse 结构体中的 Code 和 Msg 字段 + rsp.Code = code + rsp.Msg = msg + + // 将该结构体通过 JSON 格式返回给客户端 + // 并指定返回的 HTTP 状态码为 500(http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, rsp) +} + +func (rsp *HttpResponse) ResponseSuccess(c *gin.Context) { + // 将成功的信息填充到 HttpResponse 结构体中的 Code 和 Msg 字段 + rsp.Code = CodeSuccess + rsp.Msg = "success" + + // 将该结构体通过 JSON 格式返回给客户端 + // 并指定返回的 HTTP 状态码为 200(http.StatusOK) + c.JSON(http.StatusOK, rsp) +} + +// ResponseWithData 这个返回函数给客户端多返回了一个 data 也就是实际的数据(从缓存中获取的用户信息) +func (rsp *HttpResponse) ResponseWithData(c *gin.Context, data interface{}) { + rsp.Code = CodeSuccess + rsp.Msg = "success" + rsp.Data = data + c.JSON(http.StatusOK, rsp) +} diff --git a/Haderacher/HDU-QA-Platform/cmd/main.go b/Haderacher/HDU-QA-Platform/cmd/main.go new file mode 100644 index 0000000..f27e3e2 --- /dev/null +++ b/Haderacher/HDU-QA-Platform/cmd/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "gouse/config" + "gouse/internal/router" +) + +func Init() { + // 调用了 config 包中的 InitConfig 函数,用于初始化日志信息。 + config.InitConfig() +} + +func main() { + Init() // 初始化日志信息 + router.InitRouterAndServe() // 初始化路由并启动 HTTP服务器。 +} diff --git a/Haderacher/HDU-QA-Platform/conf/app.yml b/Haderacher/HDU-QA-Platform/conf/app.yml new file mode 100644 index 0000000..005637f --- /dev/null +++ b/Haderacher/HDU-QA-Platform/conf/app.yml @@ -0,0 +1,26 @@ +app: + app_name: "HDU-QA-Platform" + version: "v1.0.1" + port: 8082 + run_mode: dev + +db: + host: "localhost" + port: 33061 + user: "root" + password: "3223061" + dbname: "qa-platform" + max_idle_conn: 5 + max_open_conn: 20 + max_idle_time: 300 + +redis: + rhost: "localhost" + rport: 6379 + rdb: 0 + passwd: '' + poolsize: 100 + +cache: + session_expired: 7200 + user_expired: 300 \ No newline at end of file diff --git a/Haderacher/HDU-QA-Platform/config/config.go b/Haderacher/HDU-QA-Platform/config/config.go new file mode 100644 index 0000000..0340322 --- /dev/null +++ b/Haderacher/HDU-QA-Platform/config/config.go @@ -0,0 +1,129 @@ +package config + +import ( + log "github.com/sirupsen/logrus" + + "sync" + + "github.com/spf13/viper" +) + +// 这两个变量都没有被赋初值,它们的零值将会被使用, +// 即 config 的零值为 GlobalConfig 结构体的零值,once 的零值为默认的初始状态。 +var ( + config GlobalConfig // 全局业务配置文件 + once sync.Once // sync.Once 是一个并发安全的标志,用于确保某个函数只会被执行一次 +) + +// DbConf 数据库配置结构 +// 补充: +// 空闲连接是指:处于连接池中且当前没有被使用的数据库连接 +// 连接最大空闲时间是指:连接可以保持空闲的最长时间 +type DbConf struct { + Host string `yaml:"host" mapstructure:"host"` // db主机地址 + Port string `yaml:"port" mapstructure:"port"` // db端口 + User string `yaml:"user" mapstructure:"user"` // 用户名 + Password string `yaml:"password" mapstructure:"password"` // 密码 + Dbname string `yaml:"dbname" mapstructure:"dbname"` // db名 + MaxIdleConn int `yaml:"max_idle_conn" mapstructure:"max_idle_conn"` // 最大空闲连接数 + MaxOpenConn int `yaml:"max_open_conn" mapstructure:"max_open_conn"` // 最大打开的连接数 + MaxIdleTime int64 `yaml:"max_idle_time" mapstructure:"max_idle_time"` // 连接最大空闲时间 +} + +// 补充知识:标签 +// yaml:"app_name":这是一个用于 yaml 包的标签。 +// 它告诉编译器在解析一个 YAML 配置文件时,将字段名 app_name 映射到结构体字段 AppName 上。 +// 也就是说,当从 YAML 配置文件读取值时,将使用 app_name 作为字段的键,将对应的值赋给 AppName 字段。 +// +// mapstructure:"app_name":这是一个用于 mapstructure 包的标签。 +// mapstructure 提供了一种方式将一个 map 或 struct 赋值给另一个 struct 的字段,并且可以对字段名进行自定义映射。 +// 在这个标签中,app_name 被用作字段名的映射规则,将对应的值赋给 AppName 字段。 +// +// 也就是说,设置这两个标签可以使得 AppName 字段支持两种不同的赋值方式: +// +//当使用 yaml 解析配置文件时,解析器会将配置文件中的 app_name 键对应的值赋给 AppName 字段。 +//当使用 mapstructure 将一个 map 或 struct 赋值给结构体时,如果该 map 或 struct 中存在 app_name 键,那么其对应的值也会被赋给 AppName 字段。 + +// AppConf 服务配置 +// 这里我就简单记一下,服务配置包含:业务名称,版本号,端口号,运行模式 +// 其实并不是这个服务配置需要这几项,主要是你需要用到的信息,以及你需要在配置文件 app.yml 里面写好 +type AppConf struct { + AppName string `yaml:"app_name" mapstructure:"app_name"` // 业务名 + Version string `yaml:"version" mapstructure:"version"` // 版本号 + Port int `yaml:"port" mapstructure:"port"` // 端口号 + RunMode string `yaml:"run_mode" mapstructure:"run_mode"` // 运行模式 +} + +// RedisConf Redis 配置 +type RedisConf struct { + Host string `yaml:"rhost" mapstructure:"rhost"` // 主机地址 + Port int `yaml:"rport" mapstructure:"rport"` // 端口 + DB int `yaml:"rdb" mapstructure:"rdb"` // 编号 + PassWord string `yaml:"passwd" mapstructure:"passwd"` // 密码 + PoolSize int `yaml:"poolsize" mapstructure:"poolsize"` // 连接池大小 +} + +// 缓存配置 +type Cache struct { + SessionExpired int `yaml:"session_expired" mapstructure:"session_expired"` // 会话缓存过期时间 + UserExpired int `yaml:"user_expired" mapstructure:"user_expired"` // 用户缓存过期时间 +} + +// GlobalConfig 业务配置结构体 +type GlobalConfig struct { + AppConfig AppConf `yaml:"app" mapstructure:"app"` // 服务配置 + DbConfig DbConf `yaml:"db" mapstructure:"db"` // 数据库配置 + RedisConfig RedisConf `yaml:"redis" mapstructure:"redis"` // redis 配置 + Cache Cache `yaml:"cache" mapstructure:"cache"` // cache 配置 +} + +// GetGlobalConf 获取全局配置文件 +func GetGlobalConf() *GlobalConfig { + // 通过 sync.Once 包的 Do 方法,确保 readConf 函数只会被执行一次 + // 我就直接理解成一个线程安全的单例设计模式了 + once.Do(readConf) + return &config +} + +// 读取配置信息 +func readConf() { + // 使用 viper 包设置配置文件的名称为 "app",格式为 YAML + // 我们前面通过标签的设置支持了两种格式,其中一种就是 YAML 格式(也就是这里使用的格式) + viper.SetConfigName("app") + viper.SetConfigType("yml") + + // 然后添加配置文件的搜索路径, + // 包括当前目录,当前目录下的 "config" 目录,以及上级目录下的 "config" 目录。 + viper.AddConfigPath(".") + viper.AddConfigPath("./conf") + viper.AddConfigPath("../conf") + + // 读取配置文件 + err := viper.ReadInConfig() + if err != nil { + panic("read config file err:" + err.Error()) + } + + // 将读取到的配置信息解析为 config 变量对应的结构体类型 + err = viper.Unmarshal(&config) + if err != nil { + panic("config file unmarshal err:" + err.Error()) + } + + // 通过日志输出 config 变量的内容,以便于确认配置文件是否被正确读取和解析 + log.Infof("config === %+v", config) +} + +// InitConfig 初始化日志 +func InitConfig() { + // Initialize the global configuration + GetGlobalConf() + + // Set up logging configuration + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + }) + log.SetLevel(log.InfoLevel) + + log.Info("Configuration and logging initialized successfully") +} diff --git a/Haderacher/HDU-QA-Platform/doc/img/img.png b/Haderacher/HDU-QA-Platform/doc/img/img.png new file mode 100644 index 0000000000000000000000000000000000000000..1b5fb4c8ae09f90f10de24ce683f1ebefe1a8edb GIT binary patch literal 48208 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 Date: Sun, 6 Oct 2024 16:54:35 +0800 Subject: [PATCH 2/2] Finish!!!!! --- Haderacher/HDU-QA-Platform/ollama-restfull | 1 - .../HDU-QA-Platform/ollama-restfull/main.py | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) delete mode 160000 Haderacher/HDU-QA-Platform/ollama-restfull create mode 100644 Haderacher/HDU-QA-Platform/ollama-restfull/main.py diff --git a/Haderacher/HDU-QA-Platform/ollama-restfull b/Haderacher/HDU-QA-Platform/ollama-restfull deleted file mode 160000 index 93cd467..0000000 --- a/Haderacher/HDU-QA-Platform/ollama-restfull +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 93cd467f9fd5332dd69cf1797bec5ef5f8460e03 diff --git a/Haderacher/HDU-QA-Platform/ollama-restfull/main.py b/Haderacher/HDU-QA-Platform/ollama-restfull/main.py new file mode 100644 index 0000000..ff3f62f --- /dev/null +++ b/Haderacher/HDU-QA-Platform/ollama-restfull/main.py @@ -0,0 +1,31 @@ +# 导入必要的库 +from flask import Flask, request, jsonify +import ollama + +# 创建Flask应用 +app = Flask(__name__) + +# 定义POST端点 +@app.route('/chat', methods=['POST']) +def chat(): + try: + # 从请求中获取消息 + data = request.json + msg = data.get('msg', '') + + # 使用ollama处理消息 + response = ollama.chat(model='llama3.1:8b', messages=[ + { + 'role': 'user', + 'content': msg, + }, + ]) + + # 返回响应 + return jsonify({'response': response['message']['content']}) + except ollama._types.ResponseError as e: + return jsonify({'error': f"An error occurred: {e.text} (Status code: {e.status_code})"}), 500 + +# 运行Flask应用 +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file