From 8b2e2d61afcef88a9c4b488b6aa870e57139229f Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 10 Sep 2024 15:24:36 +0800 Subject: [PATCH 01/41] file list api support pagination --- api/handler/upload_handler.go | 41 +++++++++++++++++++++++------------ web/.env.development | 2 +- web/.env.production | 2 +- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/api/handler/upload_handler.go b/api/handler/upload_handler.go index d9c75ec2..2020b873 100644 --- a/api/handler/upload_handler.go +++ b/api/handler/upload_handler.go @@ -64,7 +64,9 @@ func (h *NetHandler) Upload(c *gin.Context) { func (h *NetHandler) List(c *gin.Context) { var data struct { - Urls []string `json:"urls,omitempty"` + Urls []string `json:"urls,omitempty"` + Page int `json:"page"` + PageSize int `json:"page_size"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -79,21 +81,32 @@ func (h *NetHandler) List(c *gin.Context) { if len(data.Urls) > 0 { session = session.Where("url IN ?", data.Urls) } - session.Find(&items) - if len(items) > 0 { - for _, v := range items { - var file vo.File - err := utils.CopyObject(v, &file) - if err != nil { - logger.Error(err) - continue - } - file.CreatedAt = v.CreatedAt.Unix() - files = append(files, file) - } + // 统计总数 + var total int64 + session.Model(&model.File{}).Count(&total) + + if data.Page > 0 && data.PageSize > 0 { + offset := (data.Page - 1) * data.PageSize + session = session.Offset(offset).Limit(data.PageSize) + } + err := session.Order("id desc").Find(&items).Error + if err != nil { + resp.ERROR(c, err.Error()) + return } - resp.SUCCESS(c, files) + for _, v := range items { + var file vo.File + err := utils.CopyObject(v, &file) + if err != nil { + logger.Error(err) + continue + } + file.CreatedAt = v.CreatedAt.Unix() + files = append(files, file) + } + + resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, files)) } // Remove remove files diff --git a/web/.env.development b/web/.env.development index 1e54193a..97c67f7e 100644 --- a/web/.env.development +++ b/web/.env.development @@ -6,6 +6,6 @@ VUE_APP_ADMIN_USER=admin VUE_APP_ADMIN_PASS=admin123 VUE_APP_KEY_PREFIX=GeekAI_DEV_ VUE_APP_TITLE="Geek-AI 创作系统" -VUE_APP_VERSION=v4.1.3 +VUE_APP_VERSION=v4.1.4 VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai diff --git a/web/.env.production b/web/.env.production index 056d8338..fee207e7 100644 --- a/web/.env.production +++ b/web/.env.production @@ -1,6 +1,6 @@ VUE_APP_API_HOST= VUE_APP_WS_HOST= VUE_APP_KEY_PREFIX=GeekAI_ -VUE_APP_VERSION=v4.1.3 +VUE_APP_VERSION=v4.1.4 VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai From 97eff6085aa0db4e3b3b16992dcf16e71c4de915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=8F=8C=E6=98=8E?= Date: Tue, 10 Sep 2024 18:14:34 +0800 Subject: [PATCH 02/41] =?UTF-8?q?feat:=20210=20AI=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=88=86=E9=A1=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/App.vue | 3 +- web/src/components/FileSelect.vue | 122 ++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 43 deletions(-) diff --git a/web/src/App.vue b/web/src/App.vue index 059f4ffe..b94eed26 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -66,7 +66,8 @@ html, body { margin 0; .el-dialog__body { - max-height 90vh + max-height 80vh + overflow-y auto } } } diff --git a/web/src/components/FileSelect.vue b/web/src/components/FileSelect.vue index d340c460..de1f8739 100644 --- a/web/src/components/FileSelect.vue +++ b/web/src/components/FileSelect.vue @@ -1,57 +1,64 @@ + + \ No newline at end of file diff --git a/web/src/views/admin/Users.vue b/web/src/views/admin/Users.vue index 9c6930e2..1282ae3a 100644 --- a/web/src/views/admin/Users.vue +++ b/web/src/views/admin/Users.vue @@ -18,6 +18,8 @@ + + @@ -73,6 +75,12 @@ + + + + + + From 5c77e67b0fd091b220d948004bec53736211b1fd Mon Sep 17 00:00:00 2001 From: RockYang Date: Thu, 12 Sep 2024 17:25:19 +0800 Subject: [PATCH 05/41] fixed bug for reset password --- api/core/app_server.go | 3 +- api/handler/admin/api_key_handler.go | 4 -- api/handler/admin/chat_app_type_handler.go | 13 ++++--- api/handler/chat_app_type_handler.go | 43 ++++++++++++++++++++++ api/handler/chat_role_handler.go | 36 ++++++++++++++++-- api/handler/user_handler.go | 2 +- api/main.go | 3 +- api/service/smtp_sms_service.go | 2 +- api/store/model/app_type.go | 1 + api/store/vo/app_type.go | 14 +++---- database/update-v4.1.4.sql | 12 ++++++ web/src/views/ChatApps.vue | 5 +-- web/src/views/ChatPlus.vue | 2 +- web/src/views/mobile/ChatList.vue | 4 +- web/src/views/mobile/ChatSession.vue | 2 +- web/src/views/mobile/Index.vue | 2 +- 16 files changed, 115 insertions(+), 33 deletions(-) create mode 100644 api/handler/chat_app_type_handler.go create mode 100644 database/update-v4.1.4.sql diff --git a/api/core/app_server.go b/api/core/app_server.go index b7187d21..e911af72 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -204,7 +204,8 @@ func needLogin(c *gin.Context) bool { c.Request.URL.Path == "/api/chat/history" || c.Request.URL.Path == "/api/chat/detail" || c.Request.URL.Path == "/api/chat/list" || - c.Request.URL.Path == "/api/role/list" || + c.Request.URL.Path == "/api/app/list" || + c.Request.URL.Path == "/api/app/list/user" || c.Request.URL.Path == "/api/model/list" || c.Request.URL.Path == "/api/mj/imgWall" || c.Request.URL.Path == "/api/mj/client" || diff --git a/api/handler/admin/api_key_handler.go b/api/handler/admin/api_key_handler.go index ef1d93d1..b6454252 100644 --- a/api/handler/admin/api_key_handler.go +++ b/api/handler/admin/api_key_handler.go @@ -74,7 +74,6 @@ func (h *ApiKeyHandler) Save(c *gin.Context) { func (h *ApiKeyHandler) List(c *gin.Context) { status := h.GetBool(c, "status") t := h.GetTrim(c, "type") - platform := h.GetTrim(c, "platform") session := h.DB.Session(&gorm.Session{}) if status { @@ -83,9 +82,6 @@ func (h *ApiKeyHandler) List(c *gin.Context) { if t != "" { session = session.Where("type", t) } - if platform != "" { - session = session.Where("platform", platform) - } var items []model.ApiKey var keys = make([]vo.ApiKey, 0) diff --git a/api/handler/admin/chat_app_type_handler.go b/api/handler/admin/chat_app_type_handler.go index 2e121a13..d462f42c 100644 --- a/api/handler/admin/chat_app_type_handler.go +++ b/api/handler/admin/chat_app_type_handler.go @@ -25,6 +25,7 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { var data struct { Id uint `json:"id"` Name string `json:"name"` + Enable bool `json:"enable"` Icon string `json:"icon"` SortNum int `json:"sort_num"` } @@ -36,12 +37,13 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { if data.Id == 0 { // for add err := h.DB.Where("name", data.Name).First(&model.AppType{}).Error if err == nil { - resp.ERROR(c, "App类型已存在") + resp.ERROR(c, "当前分类已经存在") return } err = h.DB.Create(&model.AppType{ Name: data.Name, Icon: data.Icon, + Enabled: data.Enable, SortNum: data.SortNum, }).Error if err != nil { @@ -49,9 +51,10 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { return } } else { // for update - err := h.DB.Where("id", data.Id).Updates(map[string]interface{}{ - "name": data.Name, - "icon": data.Icon, + err := h.DB.Model(&model.AppType{}).Where("id", data.Id).Updates(map[string]interface{}{ + "name": data.Name, + "icon": data.Icon, + "enabled": data.Enable, }).Error if err != nil { resp.ERROR(c, err.Error()) @@ -65,7 +68,7 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { func (h *ChatAppTypeHandler) List(c *gin.Context) { var items []model.AppType var appTypes = make([]vo.AppType, 0) - err := h.DB.Order("created_at DESC").Find(&items).Error + err := h.DB.Order("sort_num ASC").Find(&items).Error if err != nil { resp.ERROR(c, err.Error()) return diff --git a/api/handler/chat_app_type_handler.go b/api/handler/chat_app_type_handler.go new file mode 100644 index 00000000..fc5e277e --- /dev/null +++ b/api/handler/chat_app_type_handler.go @@ -0,0 +1,43 @@ +package handler + +import ( + "geekai/core" + "geekai/store/model" + "geekai/store/vo" + "geekai/utils" + "geekai/utils/resp" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type ChatAppTypeHandler struct { + BaseHandler +} + +func NewChatAppTypeHandler(app *core.AppServer, db *gorm.DB) *ChatAppTypeHandler { + return &ChatAppTypeHandler{BaseHandler: BaseHandler{App: app, DB: db}} +} + +// List 获取App类型列表 +func (h *ChatAppTypeHandler) List(c *gin.Context) { + var items []model.AppType + var appTypes = make([]vo.AppType, 0) + err := h.DB.Order("sort_num ASC").Find(&items).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + for _, v := range items { + var appType vo.AppType + err = utils.CopyObject(v, &appType) + if err != nil { + continue + } + appType.Id = v.Id + appType.CreatedAt = v.CreatedAt.Unix() + appTypes = append(appTypes, appType) + } + + resp.SUCCESS(c, appTypes) +} diff --git a/api/handler/chat_role_handler.go b/api/handler/chat_role_handler.go index ce18ec8e..8ef899be 100644 --- a/api/handler/chat_role_handler.go +++ b/api/handler/chat_role_handler.go @@ -29,10 +29,37 @@ func NewChatRoleHandler(app *core.AppServer, db *gorm.DB) *ChatRoleHandler { // List 获取用户聊天应用列表 func (h *ChatRoleHandler) List(c *gin.Context) { + tid := h.GetInt(c, "tid", 0) + var roles []model.ChatRole + session := h.DB.Where("enable", true) + if tid > 0 { + session = session.Where("tid", tid) + } + err := session.Order("sort_num ASC").Find(&roles).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + var roleVos = make([]vo.ChatRole, 0) + for _, r := range roles { + var v vo.ChatRole + err := utils.CopyObject(r, &v) + if err == nil { + v.Id = r.Id + roleVos = append(roleVos, v) + } + } + resp.SUCCESS(c, roleVos) +} + +// ListByUser 获取用户添加的角色列表 +func (h *ChatRoleHandler) ListByUser(c *gin.Context) { id := h.GetInt(c, "id", 0) userId := h.GetLoginUserId(c) var roles []model.ChatRole - query := h.DB.Where("enable", true) + session := h.DB.Where("enable", true) + // 如果用户没登录,则获取所有角色 if userId > 0 { var user model.User h.DB.First(&user, userId) @@ -42,12 +69,13 @@ func (h *ChatRoleHandler) List(c *gin.Context) { resp.ERROR(c, "角色解析失败!") return } - query = query.Where("marker IN ?", roleKeys) + session = session.Where("marker IN ?", roleKeys) } + if id > 0 { - query = query.Or("id", id) + session = session.Or("id", id) } - res := h.DB.Where("enable", true).Order("sort_num ASC").Find(&roles) + res := session.Order("sort_num ASC").Find(&roles) if res.Error != nil { resp.ERROR(c, res.Error.Error()) return diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 6b4ab0a9..de7a9657 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -601,7 +601,7 @@ func (h *UserHandler) ResetPass(c *gin.Context) { session = session.Where("email", data.Email) key = CodeStorePrefix + data.Email } else if data.Type == "mobile" { - session = session.Where("mobile", data.Email) + session = session.Where("mobile", data.Mobile) key = CodeStorePrefix + data.Mobile } else { resp.ERROR(c, "验证类别错误") diff --git a/api/main.go b/api/main.go index f240f893..dd725536 100644 --- a/api/main.go +++ b/api/main.go @@ -226,8 +226,9 @@ func main() { // 注册路由 fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) { - group := s.Engine.Group("/api/role/") + group := s.Engine.Group("/api/app/") group.GET("list", h.List) + group.GET("list/user", h.ListByUser) group.POST("update", h.UpdateRole) }), fx.Invoke(func(s *core.AppServer, h *handler.UserHandler) { diff --git a/api/service/smtp_sms_service.go b/api/service/smtp_sms_service.go index 025ffa39..c6abf586 100644 --- a/api/service/smtp_sms_service.go +++ b/api/service/smtp_sms_service.go @@ -29,7 +29,7 @@ func NewSmtpService(appConfig *types.AppConfig) *SmtpService { func (s *SmtpService) SendVerifyCode(to string, code int) error { subject := fmt.Sprintf("%s 注册验证码", s.config.AppName) - body := fmt.Sprintf("您正在注册 %s 账户,注册验证码为 %d,请不要告诉他人。如非本人操作,请忽略此邮件。", s.config.AppName, code) + body := fmt.Sprintf("【%s】:您的验证码为 %d,请不要告诉他人。如非本人操作,请忽略此邮件。", s.config.AppName, code) auth := smtp.PlainAuth("", s.config.From, s.config.Password, s.config.Host) if s.config.UseTls { diff --git a/api/store/model/app_type.go b/api/store/model/app_type.go index de10cb16..ee1aceed 100644 --- a/api/store/model/app_type.go +++ b/api/store/model/app_type.go @@ -6,6 +6,7 @@ type AppType struct { Id uint `gorm:"primarykey"` Name string Icon string + Enabled bool SortNum int CreatedAt time.Time } diff --git a/api/store/vo/app_type.go b/api/store/vo/app_type.go index 714b398a..26dfe345 100644 --- a/api/store/vo/app_type.go +++ b/api/store/vo/app_type.go @@ -1,12 +1,10 @@ package vo type AppType struct { - BaseVo - Name string `json:"name"` - Type string `json:"type"` - Value string `json:"value"` // API Key 的值 - ApiURL string `json:"api_url"` - Enabled bool `json:"enabled"` - ProxyURL string `json:"proxy_url"` - LastUsedAt int64 `json:"last_used_at"` // 最后使用时间 + Id uint `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + SortNum int `json:"sort_num"` + Enabled bool `json:"enabled"` + CreatedAt int64 `json:"created_at"` } diff --git a/database/update-v4.1.4.sql b/database/update-v4.1.4.sql new file mode 100644 index 00000000..86d47b63 --- /dev/null +++ b/database/update-v4.1.4.sql @@ -0,0 +1,12 @@ +CREATE TABLE `chatgpt_app_types` ( + `id` int NOT NULL, + `name` varchar(50) NOT NULL COMMENT '名称', + `icon` varchar(255) NOT NULL COMMENT '图标URL', + `sort_num` tinyint(3) NOT NULL COMMENT '排序', + `enabled` tinyint(1) NOT NULL COMMENT '是否启用', + `created_at` datetime NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='应用分类表'; + +ALTER TABLE `chatgpt_app_types`ADD PRIMARY KEY (`id`); +ALTER TABLE `chatgpt_app_types` MODIFY `id` int NOT NULL AUTO_INCREMENT; +ALTER TABLE `chatgpt_chat_roles` ADD `tid` INT NOT NULL COMMENT '分类ID' AFTER `name`; \ No newline at end of file diff --git a/web/src/views/ChatApps.vue b/web/src/views/ChatApps.vue index 48193f8e..45ab5677 100644 --- a/web/src/views/ChatApps.vue +++ b/web/src/views/ChatApps.vue @@ -64,7 +64,6 @@ import {arrayContains, removeArrayItem, substr} from "@/utils/libs"; import {useRouter} from "vue-router"; import {useSharedStore} from "@/store/sharedata"; import ItemList from "@/components/ItemList.vue"; -import {Plus} from "@element-plus/icons-vue"; const listBoxHeight = window.innerHeight - 87 const list = ref([]) @@ -72,7 +71,7 @@ const roles = ref([]) const store = useSharedStore(); onMounted(() => { - httpGet("/api/role/list").then((res) => { + httpGet("/api/app/list").then((res) => { const items = res.data // 处理 hello message for (let i = 0; i < items.length; i++) { @@ -112,7 +111,7 @@ const updateRole = (row, opt) => { } roles.value = removeArrayItem(roles.value, row.key) } - httpPost("/api/role/update", {keys: roles.value}).then(() => { + httpPost("/api/app/update", {keys: roles.value}).then(() => { ElMessage.success({message: title.value + "成功!", duration: 1000}) }).catch(e => { ElMessage.error(title.value + "失败:" + e.message) diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index 3552f5b9..48161be4 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -364,7 +364,7 @@ const initData = () => { modelID.value = models.value[0].id } // 加载角色列表 - httpGet(`/api/role/list`,{id:roleId.value}).then((res) => { + httpGet(`/api/app/list/user`,{id:roleId.value}).then((res) => { roles.value = res.data; if (!roleId.value) { roleId.value = roles.value[0]['id'] diff --git a/web/src/views/mobile/ChatList.vue b/web/src/views/mobile/ChatList.vue index a85af846..20864cf3 100644 --- a/web/src/views/mobile/ChatList.vue +++ b/web/src/views/mobile/ChatList.vue @@ -105,7 +105,7 @@ checkSession().then((user) => { loginUser.value = user isLogin.value = true // 加载角色列表 - httpGet(`/api/role/list`).then((res) => { + httpGet(`/api/app/list/user`).then((res) => { if (res.data) { const items = res.data for (let i = 0; i < items.length; i++) { @@ -139,7 +139,7 @@ checkSession().then((user) => { finished.value = true // 加载角色列表 - httpGet('/api/role/list').then((res) => { + httpGet('/api/app/list/user').then((res) => { if (res.data) { const items = res.data for (let i = 0; i < items.length; i++) { diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index f30db613..7a458bdd 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -186,7 +186,7 @@ httpGet('/api/model/list').then(res => { } modelValue.value = getModelName(modelId.value) // 加载角色列表 - httpGet(`/api/role/list`).then((res) => { + httpGet(`/api/app/list/user`).then((res) => { roles.value = res.data; if (!roleId.value) { roleId.value = roles.value[0]['id'] diff --git a/web/src/views/mobile/Index.vue b/web/src/views/mobile/Index.vue index 5ebd16a0..dd5430dd 100644 --- a/web/src/views/mobile/Index.vue +++ b/web/src/views/mobile/Index.vue @@ -109,7 +109,7 @@ onMounted(() => { }) const fetchApps = () => { - httpGet("/api/role/list").then((res) => { + httpGet("/api/app/list/user").then((res) => { const items = res.data // 处理 hello message for (let i = 0; i < items.length; i++) { From 6c7fa17e506297d4a95461089929ec730f317510 Mon Sep 17 00:00:00 2001 From: RockYang Date: Fri, 13 Sep 2024 17:03:05 +0800 Subject: [PATCH 06/41] fixed bug, filelist page support pagination, do not load captcha component for user login first time --- CHANGELOG.md | 1 + api/handler/admin/chat_app_type_handler.go | 6 +++--- api/handler/user_handler.go | 12 +++++++++--- web/.env.production | 2 +- web/src/components/ChatPrompt.vue | 2 +- web/src/components/FileList.vue | 1 + web/src/components/FileSelect.vue | 2 +- web/src/components/LoginDialog.vue | 10 +++++++--- web/src/views/ChatPlus.vue | 7 +++---- web/src/views/Login.vue | 7 +++++-- web/src/views/Suno.vue | 2 +- 11 files changed, 33 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08e9116e..857a200c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## v4.1.4 * 功能优化:用户文件列表组件增加分页功能支持 * Bug修复:修复用户注册失败Bug,注册操作只弹出一次行为验证码 +* 功能优化:首次登录不需要验证码,直接登录,登录失败之后才弹出验证码 ## v4.1.3 * 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信 diff --git a/api/handler/admin/chat_app_type_handler.go b/api/handler/admin/chat_app_type_handler.go index d462f42c..d842a279 100644 --- a/api/handler/admin/chat_app_type_handler.go +++ b/api/handler/admin/chat_app_type_handler.go @@ -25,7 +25,7 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { var data struct { Id uint `json:"id"` Name string `json:"name"` - Enable bool `json:"enable"` + Enabled bool `json:"enabled"` Icon string `json:"icon"` SortNum int `json:"sort_num"` } @@ -43,7 +43,7 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { err = h.DB.Create(&model.AppType{ Name: data.Name, Icon: data.Icon, - Enabled: data.Enable, + Enabled: data.Enabled, SortNum: data.SortNum, }).Error if err != nil { @@ -54,7 +54,7 @@ func (h *ChatAppTypeHandler) Save(c *gin.Context) { err := h.DB.Model(&model.AppType{}).Where("id", data.Id).Updates(map[string]interface{}{ "name": data.Name, "icon": data.Icon, - "enabled": data.Enable, + "enabled": data.Enabled, }).Error if err != nil { resp.ERROR(c, err.Error()) diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index de7a9657..3b93310b 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -244,8 +244,10 @@ func (h *UserHandler) Login(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } + verifyKey := fmt.Sprintf("users/verify/%s", data.Username) + needVerify, err := h.redis.Get(c, verifyKey).Bool() - if h.App.SysConfig.EnabledVerify { + if h.App.SysConfig.EnabledVerify && needVerify { var check bool if data.X != 0 { check = h.captcha.SlideCheck(data) @@ -261,12 +263,14 @@ func (h *UserHandler) Login(c *gin.Context) { var user model.User res := h.DB.Where("username = ?", data.Username).First(&user) if res.Error != nil { + h.redis.Set(c, verifyKey, true, 0) resp.ERROR(c, "用户名不存在") return } password := utils.GenPassword(data.Password, user.Salt) if password != user.Password { + h.redis.Set(c, verifyKey, true, 0) resp.ERROR(c, "用户名或密码错误") return } @@ -299,11 +303,13 @@ func (h *UserHandler) Login(c *gin.Context) { return } // 保存到 redis - key := fmt.Sprintf("users/%d", user.Id) - if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil { + sessionKey := fmt.Sprintf("users/%d", user.Id) + if _, err = h.redis.Set(c, sessionKey, tokenString, 0).Result(); err != nil { resp.ERROR(c, "error with save token: "+err.Error()) return } + // 移除登录行为验证码 + h.redis.Del(c, verifyKey) resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username}) } diff --git a/web/.env.production b/web/.env.production index fee207e7..056d8338 100644 --- a/web/.env.production +++ b/web/.env.production @@ -1,6 +1,6 @@ VUE_APP_API_HOST= VUE_APP_WS_HOST= VUE_APP_KEY_PREFIX=GeekAI_ -VUE_APP_VERSION=v4.1.4 +VUE_APP_VERSION=v4.1.3 VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai diff --git a/web/src/components/ChatPrompt.vue b/web/src/components/ChatPrompt.vue index 92dd392c..518ce2f4 100644 --- a/web/src/components/ChatPrompt.vue +++ b/web/src/components/ChatPrompt.vue @@ -143,7 +143,7 @@ onMounted(() => { const links = props.data.content.match(linkRegex); if (links) { httpPost("/api/upload/list", {urls: links}).then(res => { - files.value = res.data + files.value = res.data.items for (let link of links) { if (isExternalImg(link, files.value)) { diff --git a/web/src/components/FileList.vue b/web/src/components/FileList.vue index b503b922..8bf96897 100644 --- a/web/src/components/FileList.vue +++ b/web/src/components/FileList.vue @@ -60,6 +60,7 @@ const removeFile = (file) => { display flex flex-flow row margin-right 10px + max-width 600px position relative .el-image { diff --git a/web/src/components/FileSelect.vue b/web/src/components/FileSelect.vue index d340c460..554fb209 100644 --- a/web/src/components/FileSelect.vue +++ b/web/src/components/FileSelect.vue @@ -68,7 +68,7 @@ const fileList = ref([]) const fetchFiles = () => { show.value = true httpPost("/api/upload/list").then(res => { - fileList.value = res.data + fileList.value = res.data.items }).catch(() => { }) } diff --git a/web/src/components/LoginDialog.vue b/web/src/components/LoginDialog.vue index 6af69740..58f81428 100644 --- a/web/src/components/LoginDialog.vue +++ b/web/src/components/LoginDialog.vue @@ -263,8 +263,8 @@ watch(() => props.show, (newValue) => { const login = ref(true) const data = ref({ - username: "", - password: "", + username: process.env.VUE_APP_USER, + password: process.env.VUE_APP_PASS, mobile: "", email: "", repass: "", @@ -285,6 +285,8 @@ const action = ref("login") const enableVerify = ref(false) const showResetPass = ref(false) const router = useRouter() +// 是否需要验证码,输入一次密码错之后就要验证码 +const needVerify = ref(false) onMounted(() => { const returnURL = `${location.protocol}//${location.host}/login/callback?action=login` @@ -338,7 +340,7 @@ const submitLogin = () => { if (data.value.password === '') { return ElMessage.error('请输入密码'); } - if (enableVerify.value) { + if (enableVerify.value && needVerify.value) { captchaRef.value.loadCaptcha() action.value = "login" } else { @@ -355,8 +357,10 @@ const doLogin = (verifyData) => { ElMessage.success("登录成功!") emits("hide") emits('success') + needVerify.value = false }).catch((e) => { ElMessage.error('登录失败,' + e.message) + needVerify.value = true }) } diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index 48161be4..389c2b1a 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -349,7 +349,6 @@ onMounted(() => { onUnmounted(() => { if (socket.value !== null) { - socket.value.close() socket.value = null } }) @@ -518,9 +517,9 @@ const loadChat = function (chat) { modelID.value = chat.model_id; chatId.value = chat.chat_id; showStopGenerate.value = false; - router.push(`/chat/${chatId.value}`) loadHistory.value = true - socket.value.close() + connect() + router.replace(`/chat/${chatId.value}`) } // 编辑会话标题 @@ -757,7 +756,7 @@ const sendMessage = function () { if (files.value.length === 1) { content += files.value.map(file => file.url).join(" ") } else if (files.value.length > 1) { - showMessageError("当前只支持一个文件!") + showMessageError("当前只支持上传一个文件!") return false } // 追加消息 diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index d0ad1b66..9a9f7a14 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -76,7 +76,6 @@ import {setUserToken} from "@/store/session"; import ResetPass from "@/components/ResetPass.vue"; import {showMessageError} from "@/utils/dialog"; import Captcha from "@/components/Captcha.vue"; -import QRCode from "qrcode"; import {setRoute} from "@/store/system"; const router = useRouter(); @@ -89,6 +88,8 @@ const licenseConfig = ref({}) const wechatLoginURL = ref('') const enableVerify = ref(false) const captchaRef = ref(null) +// 是否需要验证码,输入一次密码错之后就要验证码 +const needVerify = ref(false) onMounted(() => { // 获取系统配置 @@ -137,7 +138,7 @@ const login = function () { return showMessageError('请输入密码'); } - if (enableVerify.value) { + if (enableVerify.value && needVerify.value) { captchaRef.value.loadCaptcha() } else { doLogin({}) @@ -153,6 +154,7 @@ const doLogin = (verifyData) => { x: verifyData.x }).then((res) => { setUserToken(res.data.token) + needVerify.value = false if (isMobile()) { router.push('/mobile') } else { @@ -161,6 +163,7 @@ const doLogin = (verifyData) => { }).catch((e) => { showMessageError('登录失败,' + e.message) + needVerify.value = true }) } diff --git a/web/src/views/Suno.vue b/web/src/views/Suno.vue index b5a58832..92aa4fcc 100644 --- a/web/src/views/Suno.vue +++ b/web/src/views/Suno.vue @@ -610,7 +610,7 @@ const publishJob = (item) => { } const getShareURL = (item) => { - return `${location.protocol}//${location.host}/song/${item.id}` + return `${location.protocol}//${location.host}/song/${item.song_id}` } const uploadCover = (file) => { From dcdc0d891853e78bd494fdfda647ea7cab14117f Mon Sep 17 00:00:00 2001 From: RockYang Date: Fri, 13 Sep 2024 18:32:13 +0800 Subject: [PATCH 07/41] add tid field for chat app role --- api/handler/admin/chat_app_handler.go | 15 +++++++++++++++ api/store/model/chat_role.go | 1 + api/store/vo/chat_role.go | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/api/handler/admin/chat_app_handler.go b/api/handler/admin/chat_app_handler.go index 8b54eec9..e068c645 100644 --- a/api/handler/admin/chat_app_handler.go +++ b/api/handler/admin/chat_app_handler.go @@ -75,13 +75,18 @@ func (h *ChatAppHandler) List(c *gin.Context) { // initialize model mane for role modelIds := make([]int, 0) + typeIds := make([]int, 0) for _, v := range items { if v.ModelId > 0 { modelIds = append(modelIds, v.ModelId) } + if v.Tid > 0 { + typeIds = append(typeIds, v.Tid) + } } modelNameMap := make(map[int]string) + typeNameMap := make(map[int]string) if len(modelIds) > 0 { var models []model.ChatModel tx := h.DB.Where("id IN ?", modelIds).Find(&models) @@ -91,6 +96,15 @@ func (h *ChatAppHandler) List(c *gin.Context) { } } } + if len(typeIds) > 0 { + var appTypes []model.AppType + tx := h.DB.Where("id IN ?", typeIds).Find(&appTypes) + if tx.Error == nil { + for _, m := range appTypes { + typeNameMap[int(m.Id)] = m.Name + } + } + } for _, v := range items { var role vo.ChatRole @@ -100,6 +114,7 @@ func (h *ChatAppHandler) List(c *gin.Context) { role.CreatedAt = v.CreatedAt.Unix() role.UpdatedAt = v.UpdatedAt.Unix() role.ModelName = modelNameMap[role.ModelId] + role.TypeName = typeNameMap[role.Tid] roles = append(roles, role) } } diff --git a/api/store/model/chat_role.go b/api/store/model/chat_role.go index 50e438bf..95dfbce6 100644 --- a/api/store/model/chat_role.go +++ b/api/store/model/chat_role.go @@ -2,6 +2,7 @@ package model type ChatRole struct { BaseModel + Tid int Key string `gorm:"column:marker;unique"` // 角色唯一标识 Name string // 角色名称 Context string `gorm:"column:context_json"` // 角色语料信息 json diff --git a/api/store/vo/chat_role.go b/api/store/vo/chat_role.go index 4bd530f8..ad82d949 100644 --- a/api/store/vo/chat_role.go +++ b/api/store/vo/chat_role.go @@ -4,7 +4,8 @@ import "geekai/core/types" type ChatRole struct { BaseVo - Key string `json:"key"` // 角色唯一标识 + Key string `json:"key"` // 角色唯一标识 + Tid uint `json:"tid"` Name string `json:"name"` // 角色名称 Context []types.Message `json:"context"` // 角色语料信息 HelloMsg string `json:"hello_msg"` // 打招呼的消息 @@ -13,4 +14,5 @@ type ChatRole struct { SortNum int `json:"sort"` // 排序 ModelId int `json:"model_id"` // 绑定模型 ID ModelName string `json:"model_name"` // 模型名称 + TypeName string `json:"type_name"` // 分类名称 } From 866564370d4125124f2379ff337ebd5ddd9de2b9 Mon Sep 17 00:00:00 2001 From: RockYang Date: Sat, 14 Sep 2024 05:54:55 +0800 Subject: [PATCH 08/41] return at least one chat role for getUserRoles API --- api/handler/admin/user_handler.go | 2 +- api/handler/chat_role_handler.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index 6cd03e6a..4c247fbe 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -269,7 +269,7 @@ func (h *UserHandler) Remove(c *gin.Context) { } } if err != nil { - resp.ERROR(c, "删除失败") + resp.ERROR(c, err.Error()) tx.Rollback() return } diff --git a/api/handler/chat_role_handler.go b/api/handler/chat_role_handler.go index 8ef899be..aa579a51 100644 --- a/api/handler/chat_role_handler.go +++ b/api/handler/chat_role_handler.go @@ -69,7 +69,10 @@ func (h *ChatRoleHandler) ListByUser(c *gin.Context) { resp.ERROR(c, "角色解析失败!") return } - session = session.Where("marker IN ?", roleKeys) + // 保证用户至少有一个角色可用 + if len(roleKeys) > 0 { + session = session.Where("marker IN ?", roleKeys) + } } if id > 0 { From 131efd6ba5d4a795e5c3cf737beb8d20d48690bf Mon Sep 17 00:00:00 2001 From: RockYang Date: Sat, 14 Sep 2024 07:11:45 +0800 Subject: [PATCH 09/41] refactor chat message body struct --- CHANGELOG.md | 2 + api/core/types/chat.go | 19 ++-- api/core/types/web.go | 11 ++- api/handler/chatimpl/chat_handler.go | 122 ++++++++++++++----------- api/handler/chatimpl/openai_handler.go | 52 +++++++++-- api/handler/dalle_handler.go | 2 +- api/handler/markmap_handler.go | 10 +- api/store/model/chat_history.go | 21 +++-- api/store/vo/chat_role.go | 2 +- api/utils/net.go | 11 ++- database/update-v4.1.4.sql | 4 +- 11 files changed, 161 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 857a200c..c119bd0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * 功能优化:用户文件列表组件增加分页功能支持 * Bug修复:修复用户注册失败Bug,注册操作只弹出一次行为验证码 * 功能优化:首次登录不需要验证码,直接登录,登录失败之后才弹出验证码 +* 功能新增:给 AI 应用(角色)增加分类 +* 功能优化:允许用户在聊天页面设置是否使用流式输出或者一次性输出,兼容 GPT-O1 模型。 ## v4.1.3 * 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信 diff --git a/api/core/types/chat.go b/api/core/types/chat.go index 42c86a2b..95a55397 100644 --- a/api/core/types/chat.go +++ b/api/core/types/chat.go @@ -9,14 +9,14 @@ package types // ApiRequest API 请求实体 type ApiRequest struct { - Model string `json:"model,omitempty"` // 兼容百度文心一言 - Temperature float32 `json:"temperature"` - MaxTokens int `json:"max_tokens,omitempty"` // 兼容百度文心一言 - Stream bool `json:"stream"` - Messages []interface{} `json:"messages,omitempty"` - Prompt []interface{} `json:"prompt,omitempty"` // 兼容 ChatGLM - Tools []Tool `json:"tools,omitempty"` - Functions []interface{} `json:"functions,omitempty"` // 兼容中转平台 + Model string `json:"model,omitempty"` + Temperature float32 `json:"temperature"` + MaxTokens int `json:"max_tokens,omitempty"` + MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` // 兼容GPT O1 模型 + Stream bool `json:"stream,omitempty"` + Messages []interface{} `json:"messages,omitempty"` + Tools []Tool `json:"tools,omitempty"` + Functions []interface{} `json:"functions,omitempty"` // 兼容中转平台 ToolChoice string `json:"tool_choice,omitempty"` @@ -57,7 +57,8 @@ type ChatSession struct { ClientIP string `json:"client_ip"` // 客户端 IP ChatId string `json:"chat_id"` // 客户端聊天会话 ID, 多会话模式专用字段 Model ChatModel `json:"model"` // GPT 模型 - Tools string `json:"tools"` // 函数 + Tools []int `json:"tools"` // 工具函数列表 + Stream bool `json:"stream"` // 是否采用流式输出 } type ChatModel struct { diff --git a/api/core/types/web.go b/api/core/types/web.go index 408d9a58..8ca9b90f 100644 --- a/api/core/types/web.go +++ b/api/core/types/web.go @@ -17,8 +17,8 @@ type BizVo struct { Data interface{} `json:"data,omitempty"` } -// WsMessage Websocket message -type WsMessage struct { +// ReplyMessage 对话回复消息结构 +type ReplyMessage struct { Type WsMsgType `json:"type"` // 消息类别,start, end, img Content interface{} `json:"content"` } @@ -32,6 +32,13 @@ const ( WsErr = WsMsgType("error") ) +// InputMessage 对话输入消息结构 +type InputMessage struct { + Content string `json:"content"` + Tools []int `json:"tools"` // 允许调用工具列表 + Stream bool `json:"stream"` // 是否采用流式输出 +} + type BizCode int const ( diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index 730043e9..561a7918 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -73,13 +73,12 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { roleId := h.GetInt(c, "role_id", 0) chatId := c.Query("chat_id") modelId := h.GetInt(c, "model_id", 0) - tools := c.Query("tools") client := types.NewWsClient(ws) var chatRole model.ChatRole res := h.DB.First(&chatRole, roleId) if res.Error != nil || !chatRole.Enable { - utils.ReplyMessage(client, "当前聊天角色不存在或者未启用,连接已关闭!!!") + utils.ReplyErrorMessage(client, "当前聊天角色不存在或者未启用,对话已关闭!!!") c.Abort() return } @@ -91,7 +90,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { var chatModel model.ChatModel res = h.DB.First(&chatModel, modelId) if res.Error != nil || chatModel.Enabled == false { - utils.ReplyMessage(client, "当前AI模型暂未启用,连接已关闭!!!") + utils.ReplyErrorMessage(client, "当前AI模型暂未启用,对话已关闭!!!") c.Abort() return } @@ -100,7 +99,6 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { SessionId: sessionId, ClientIP: c.ClientIP(), UserId: h.GetLoginUserId(c), - Tools: tools, } // use old chat data override the chat model and role ID @@ -137,20 +135,16 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { return } - var message types.WsMessage + var message types.InputMessage err = utils.JsonDecode(string(msg), &message) if err != nil { continue } - // 心跳消息 - if message.Type == "heartbeat" { - logger.Debug("收到 Chat 心跳消息:", message.Content) - continue - } - - logger.Info("Receive a message: ", message.Content) + logger.Infof("Receive a message:%+v", message) + session.Tools = message.Tools + session.Stream = message.Stream ctx, cancel := context.WithCancel(context.Background()) h.ReqCancelFunc.Put(sessionId, cancel) // 回复消息 @@ -159,7 +153,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { logger.Error(err) utils.ReplyMessage(client, err.Error()) } else { - utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd}) + utils.ReplyChunkMessage(client, types.ReplyMessage{Type: types.WsEnd}) logger.Infof("回答完毕: %v", message.Content) } @@ -208,16 +202,21 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio } var req = types.ApiRequest{ - Model: session.Model.Value, - Stream: true, + Model: session.Model.Value, + Temperature: session.Model.Temperature, + } + // 兼容 GPT-O1 模型 + if strings.HasPrefix(session.Model.Value, "o1-") { + req.MaxCompletionTokens = session.Model.MaxTokens + req.Stream = false + } else { + req.MaxTokens = session.Model.MaxTokens + req.Stream = session.Stream } - req.Temperature = session.Model.Temperature - req.MaxTokens = session.Model.MaxTokens - if session.Tools != "" { - toolIds := strings.Split(session.Tools, ",") + if len(session.Tools) > 0 && !strings.HasPrefix(session.Model.Value, "o1-") { var items []model.Function - res = h.DB.Where("enabled", true).Where("id IN ?", toolIds).Find(&items) + res = h.DB.Where("enabled", true).Where("id IN ?", session.Tools).Find(&items) if res.Error == nil { var tools = make([]types.Tool, 0) for _, v := range items { @@ -279,7 +278,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio for i := len(messages) - 1; i >= 0; i-- { v := messages[i] - tks, _ := utils.CalcTokens(v.Content, req.Model) + tks, _ = utils.CalcTokens(v.Content, req.Model) // 上下文 token 超出了模型的最大上下文长度 if tokens+tks >= session.Model.MaxContext { break @@ -500,10 +499,17 @@ func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, p } } +type Usage struct { + Prompt string + Content string + PromptTokens int + CompletionTokens int + TotalTokens int +} + func (h *ChatHandler) saveChatHistory( req types.ApiRequest, - prompt string, - contents []string, + usage Usage, message types.Message, chatCtx []types.Message, session *types.ChatSession, @@ -514,8 +520,8 @@ func (h *ChatHandler) saveChatHistory( if message.Role == "" { message.Role = "assistant" } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} + message.Content = usage.Content + useMsg := types.Message{Role: "user", Content: usage.Prompt} // 更新上下文消息,如果是调用函数则不需要更新上下文 if h.App.SysConfig.EnableContext { @@ -526,42 +532,52 @@ func (h *ChatHandler) saveChatHistory( // 追加聊天记录 // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) + var promptTokens, replyTokens, totalTokens int + if usage.PromptTokens > 0 { + promptTokens = usage.PromptTokens + } else { + promptTokens, _ = utils.CalcTokens(usage.Content, req.Model) } + historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: true, - Model: req.Model, + UserId: userVo.Id, + ChatId: session.ChatId, + RoleId: role.Id, + Type: types.PromptMsg, + Icon: userVo.Avatar, + Content: template.HTMLEscapeString(usage.Prompt), + Tokens: promptTokens, + TotalTokens: promptTokens, + UseContext: true, + Model: req.Model, } historyUserMsg.CreatedAt = promptCreatedAt historyUserMsg.UpdatedAt = promptCreatedAt - err = h.DB.Save(&historyUserMsg).Error + err := h.DB.Save(&historyUserMsg).Error if err != nil { logger.Error("failed to save prompt history message: ", err) } // for reply // 计算本次对话消耗的总 token 数量 - replyTokens, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyTokens + getTotalTokens(req) + if usage.CompletionTokens > 0 { + replyTokens = usage.CompletionTokens + totalTokens = usage.TotalTokens + } else { + replyTokens, _ = utils.CalcTokens(message.Content, req.Model) + totalTokens = replyTokens + getTotalTokens(req) + } historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: message.Content, - Tokens: totalTokens, - UseContext: true, - Model: req.Model, + UserId: userVo.Id, + ChatId: session.ChatId, + RoleId: role.Id, + Type: types.ReplyMsg, + Icon: role.Icon, + Content: message.Content, + Tokens: replyTokens, + TotalTokens: totalTokens, + UseContext: true, + Model: req.Model, } historyReplyMsg.CreatedAt = replyCreatedAt historyReplyMsg.UpdatedAt = replyCreatedAt @@ -572,7 +588,7 @@ func (h *ChatHandler) saveChatHistory( // 更新用户算力 if session.Model.Power > 0 { - h.subUserPower(userVo, session, promptToken, replyTokens) + h.subUserPower(userVo, session, promptTokens, replyTokens) } // 保存当前会话 var chatItem model.ChatItem @@ -582,10 +598,10 @@ func (h *ChatHandler) saveChatHistory( chatItem.UserId = userVo.Id chatItem.RoleId = role.Id chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." + if utf8.RuneCountInString(usage.Prompt) > 30 { + chatItem.Title = string([]rune(usage.Prompt)[:30]) + "..." } else { - chatItem.Title = prompt + chatItem.Title = usage.Prompt } chatItem.Model = req.Model err = h.DB.Create(&chatItem).Error diff --git a/api/handler/chatimpl/openai_handler.go b/api/handler/chatimpl/openai_handler.go index 775c8275..ccefe74f 100644 --- a/api/handler/chatimpl/openai_handler.go +++ b/api/handler/chatimpl/openai_handler.go @@ -23,6 +23,28 @@ import ( "time" ) +type respVo struct { + Id string `json:"id"` + Object string `json:"object"` + Created int `json:"created"` + Model string `json:"model"` + SystemFingerprint string `json:"system_fingerprint"` + Choices []struct { + Index int `json:"index"` + Message struct { + Role string `json:"role"` + Content string `json:"content"` + } `json:"message"` + Logprobs interface{} `json:"logprobs"` + FinishReason string `json:"finish_reason"` + } `json:"choices"` + Usage struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` + } `json:"usage"` +} + // OPenAI 消息发送实现 func (h *ChatHandler) sendOpenAiMessage( chatCtx []types.Message, @@ -49,6 +71,10 @@ func (h *ChatHandler) sendOpenAiMessage( defer response.Body.Close() } + if response.StatusCode != 200 { + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, body) + } contentType := response.Header.Get("Content-Type") if strings.Contains(contentType, "text/event-stream") { replyCreatedAt := time.Now() // 记录回复时间 @@ -106,8 +132,8 @@ func (h *ChatHandler) sendOpenAiMessage( if res.Error == nil { toolCall = true callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label) - utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart}) - utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: callMsg}) + utils.ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsStart}) + utils.ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsMiddle, Content: callMsg}) contents = append(contents, callMsg) } continue @@ -125,10 +151,10 @@ func (h *ChatHandler) sendOpenAiMessage( content := responseBody.Choices[0].Delta.Content contents = append(contents, utils.InterfaceToString(content)) if isNew { - utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart}) + utils.ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsStart}) isNew = false } - utils.ReplyChunkMessage(ws, types.WsMessage{ + utils.ReplyChunkMessage(ws, types.ReplyMessage{ Type: types.WsMiddle, Content: utils.InterfaceToString(responseBody.Choices[0].Delta.Content), }) @@ -161,13 +187,13 @@ func (h *ChatHandler) sendOpenAiMessage( } if errMsg != "" || apiRes.Code != types.Success { msg := "调用函数工具出错:" + apiRes.Message + errMsg - utils.ReplyChunkMessage(ws, types.WsMessage{ + utils.ReplyChunkMessage(ws, types.ReplyMessage{ Type: types.WsMiddle, Content: msg, }) contents = append(contents, msg) } else { - utils.ReplyChunkMessage(ws, types.WsMessage{ + utils.ReplyChunkMessage(ws, types.ReplyMessage{ Type: types.WsMiddle, Content: apiRes.Data, }) @@ -177,11 +203,17 @@ func (h *ChatHandler) sendOpenAiMessage( // 消息发送成功 if len(contents) > 0 { - h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) + usage := Usage{ + Prompt: prompt, + Content: strings.Join(contents, ""), + PromptTokens: 0, + CompletionTokens: 0, + TotalTokens: 0, + } + h.saveChatHistory(req, usage, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } - } else { - body, _ := io.ReadAll(response.Body) - return fmt.Errorf("请求 OpenAI API 失败:%s", body) + } else { // 非流式输出 + } return nil diff --git a/api/handler/dalle_handler.go b/api/handler/dalle_handler.go index bcf44ba8..80b993ee 100644 --- a/api/handler/dalle_handler.go +++ b/api/handler/dalle_handler.go @@ -73,7 +73,7 @@ func (h *DallJobHandler) Client(c *gin.Context) { return } - var message types.WsMessage + var message types.ReplyMessage err = utils.JsonDecode(string(msg), &message) if err != nil { continue diff --git a/api/handler/markmap_handler.go b/api/handler/markmap_handler.go index b4147deb..9c624961 100644 --- a/api/handler/markmap_handler.go +++ b/api/handler/markmap_handler.go @@ -64,7 +64,7 @@ func (h *MarkMapHandler) Client(c *gin.Context) { return } - var message types.WsMessage + var message types.ReplyMessage err = utils.JsonDecode(string(msg), &message) if err != nil { continue @@ -85,7 +85,7 @@ func (h *MarkMapHandler) Client(c *gin.Context) { err = h.sendMessage(client, utils.InterfaceToString(message.Content), modelId, userId) if err != nil { logger.Error(err) - utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsErr, Content: err.Error()}) + utils.ReplyErrorMessage(client, err.Error()) } } @@ -170,16 +170,16 @@ func (h *MarkMapHandler) sendMessage(client *types.WsClient, prompt string, mode } if isNew { - utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsStart}) + utils.ReplyChunkMessage(client, types.ReplyMessage{Type: types.WsStart}) isNew = false } - utils.ReplyChunkMessage(client, types.WsMessage{ + utils.ReplyChunkMessage(client, types.ReplyMessage{ Type: types.WsMiddle, Content: utils.InterfaceToString(responseBody.Choices[0].Delta.Content), }) } // end for - utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd}) + utils.ReplyChunkMessage(client, types.ReplyMessage{Type: types.WsEnd}) } else { body, _ := io.ReadAll(response.Body) diff --git a/api/store/model/chat_history.go b/api/store/model/chat_history.go index 36abeb4e..876c427f 100644 --- a/api/store/model/chat_history.go +++ b/api/store/model/chat_history.go @@ -4,16 +4,17 @@ import "gorm.io/gorm" type ChatMessage struct { BaseModel - ChatId string // 会话 ID - UserId uint // 用户 ID - RoleId uint // 角色 ID - Model string // AI模型 - Type string - Icon string - Tokens int - Content string - UseContext bool // 是否可以作为聊天上下文 - DeletedAt gorm.DeletedAt + ChatId string // 会话 ID + UserId uint // 用户 ID + RoleId uint // 角色 ID + Model string // AI模型 + Type string + Icon string + Tokens int + TotalTokens int // 总 token 消耗 + Content string + UseContext bool // 是否可以作为聊天上下文 + DeletedAt gorm.DeletedAt } func (ChatMessage) TableName() string { diff --git a/api/store/vo/chat_role.go b/api/store/vo/chat_role.go index ad82d949..9ab49cf6 100644 --- a/api/store/vo/chat_role.go +++ b/api/store/vo/chat_role.go @@ -5,7 +5,7 @@ import "geekai/core/types" type ChatRole struct { BaseVo Key string `json:"key"` // 角色唯一标识 - Tid uint `json:"tid"` + Tid int `json:"tid"` Name string `json:"name"` // 角色名称 Context []types.Message `json:"context"` // 角色语料信息 HelloMsg string `json:"hello_msg"` // 打招呼的消息 diff --git a/api/utils/net.go b/api/utils/net.go index 5f02922c..127f0f51 100644 --- a/api/utils/net.go +++ b/api/utils/net.go @@ -33,9 +33,14 @@ func ReplyChunkMessage(client *types.WsClient, message interface{}) { // ReplyMessage 回复客户端一条完整的消息 func ReplyMessage(ws *types.WsClient, message interface{}) { - ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart}) - ReplyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: message}) - ReplyChunkMessage(ws, types.WsMessage{Type: types.WsEnd}) + ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsStart}) + ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsMiddle, Content: message}) + ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsEnd}) +} + +// ReplyErrorMessage 向客户端发送错误消息 +func ReplyErrorMessage(ws *types.WsClient, message interface{}) { + ReplyChunkMessage(ws, types.ReplyMessage{Type: types.WsErr, Content: message}) } func DownloadImage(imageURL string, proxy string) ([]byte, error) { diff --git a/database/update-v4.1.4.sql b/database/update-v4.1.4.sql index 86d47b63..28d93e5c 100644 --- a/database/update-v4.1.4.sql +++ b/database/update-v4.1.4.sql @@ -9,4 +9,6 @@ CREATE TABLE `chatgpt_app_types` ( ALTER TABLE `chatgpt_app_types`ADD PRIMARY KEY (`id`); ALTER TABLE `chatgpt_app_types` MODIFY `id` int NOT NULL AUTO_INCREMENT; -ALTER TABLE `chatgpt_chat_roles` ADD `tid` INT NOT NULL COMMENT '分类ID' AFTER `name`; \ No newline at end of file +ALTER TABLE `chatgpt_chat_roles` ADD `tid` INT NOT NULL COMMENT '分类ID' AFTER `name`; + +ALTER TABLE `chatgpt_chat_history` ADD `total_tokens` INT NOT NULL COMMENT '消耗总Token长度' AFTER `tokens`; \ No newline at end of file From aaea23f785b20d72198edf6a9a56fb04b698b5c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=8F=8C=E6=98=8E?= Date: Sat, 14 Sep 2024 11:05:49 +0800 Subject: [PATCH 10/41] =?UTF-8?q?feat:=20=E5=BA=94=E7=94=A8=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/assets/css/chat-app.styl | 41 +++++- web/src/assets/css/luma.styl | 2 +- web/src/router.js | 4 +- web/src/views/ChatApps.vue | 54 ++++++-- web/src/views/Luma.vue | 4 +- web/src/views/admin/AppType.vue | 208 ++++++++++++++++++++++++++++++- web/src/views/admin/Apps.vue | 31 +++++ web/vue.config.js | 6 + 8 files changed, 328 insertions(+), 22 deletions(-) diff --git a/web/src/assets/css/chat-app.styl b/web/src/assets/css/chat-app.styl index c7692bd4..a6dff71e 100644 --- a/web/src/assets/css/chat-app.styl +++ b/web/src/assets/css/chat-app.styl @@ -2,10 +2,49 @@ background-color: #282c34; height 100% + .apps-type-nav{ + height 43px + padding 8px 0; + margin-bottom 3px + } + + .scrollbar-type-nav{ + display flex + align-items center + height 43px + padding 0 5px + li{ + flex-shrink 0 + display flex + align-items center + justify-content center + margin 0 10px + height 26px + border-radius 4px + border 1px solid rgb(80,80,80) + padding 2px 12px + background rgba(60,60,60 0.9) + color #fff + font-size 14px + cursor pointer + + .image { + width 22px + height 22px + overflow hidden + margin-right 5px + } + &.active{ + background #21aa93; + } + } + } + + .inner { display flex color #ffffff - padding 15px; + padding 2px 15px; overflow-y visible overflow-x hidden diff --git a/web/src/assets/css/luma.styl b/web/src/assets/css/luma.styl index 212a47c8..a3b95efb 100644 --- a/web/src/assets/css/luma.styl +++ b/web/src/assets/css/luma.styl @@ -141,7 +141,7 @@ display flex flex-flow row align-items center - height 100px + min-height 100px padding 10px 15px border-radius 10px cursor pointer diff --git a/web/src/router.js b/web/src/router.js index 8507382a..e1ca5cd6 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -170,13 +170,13 @@ const routes = [ { path: '/admin/app', name: 'admin-app', - meta: {title: '应用管理'}, + meta: {title: '应用列表'}, component: () => import('@/views/admin/Apps.vue'), }, { path: '/admin/app/type', name: 'admin-app-type', - meta: {title: '应用管理'}, + meta: {title: '应用分类'}, component: () => import('@/views/admin/AppType.vue'), }, { diff --git a/web/src/views/ChatApps.vue b/web/src/views/ChatApps.vue index 45ab5677..8794de95 100644 --- a/web/src/views/ChatApps.vue +++ b/web/src/views/ChatApps.vue @@ -1,6 +1,20 @@ @@ -80,12 +72,6 @@ getSystemInfo().then(res => { ElMessage.error("加载系统配置失败: " + e.message) }) -const keyupHandle = (e) => { - if (e.key === 'Enter') { - login(); - } -} - const login = function () { if (username.value === '') { return ElMessage.error('请输入用户名'); diff --git a/web/src/views/mobile/ChatList.vue b/web/src/views/mobile/ChatList.vue index 20864cf3..8d9de46d 100644 --- a/web/src/views/mobile/ChatList.vue +++ b/web/src/views/mobile/ChatList.vue @@ -225,7 +225,7 @@ const newChat = (item) => { } showPicker.value = false const options = item.selectedOptions - router.push(`/mobile/chat/session?title=新对话&role_id=${options[0].value}&model_id=${options[1].value}&chat_id=0}`) + router.push(`/mobile/chat/session?title=新对话&role_id=${options[0].value}&model_id=${options[1].value}`) } const changeChat = (chat) => { diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index 7a458bdd..e96ff6fe 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -123,7 +123,7 @@ diff --git a/web/src/utils/libs.js b/web/src/utils/libs.js index 06b65d5c..d57cbbd1 100644 --- a/web/src/utils/libs.js +++ b/web/src/utils/libs.js @@ -232,4 +232,9 @@ export const replaceImg =(img) => { } return img } +export function isChrome() { + const userAgent = navigator.userAgent.toLowerCase(); + return /chrome/.test(userAgent) && !/edg/.test(userAgent); +} + From 158db83965439db118943a19e820e002607ccbae Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 18 Sep 2024 07:03:46 +0800 Subject: [PATCH 13/41] add geek payment --- CHANGELOG.md | 2 + api/core/types/config.go | 34 ++--- api/handler/payment_handler.go | 193 ++++++++++++------------- api/handler/test_handler.go | 4 +- api/res/img/geek-pay.jpg | Bin 0 -> 27461 bytes api/res/img/qq-pay.jpg | Bin 0 -> 17178 bytes api/service/payment/geekpay_service.go | 136 +++++++++++++++++ api/service/payment/payjs_service.go | 153 -------------------- api/store/model/order.go | 3 +- api/store/vo/order.go | 1 + database/update-v4.1.4.sql | 3 +- web/src/assets/css/member.styl | 8 +- web/src/assets/iconfont/iconfont.css | 22 ++- web/src/assets/iconfont/iconfont.js | 2 +- web/src/assets/iconfont/iconfont.json | 28 ++++ web/src/assets/iconfont/iconfont.ttf | Bin 29056 -> 30468 bytes web/src/assets/iconfont/iconfont.woff | Bin 19360 -> 20304 bytes web/src/assets/iconfont/iconfont.woff2 | Bin 16812 -> 17612 bytes web/src/views/Member.vue | 157 ++++++-------------- 19 files changed, 355 insertions(+), 391 deletions(-) create mode 100644 api/res/img/geek-pay.jpg create mode 100644 api/res/img/qq-pay.jpg create mode 100644 api/service/payment/geekpay_service.go delete mode 100644 api/service/payment/payjs_service.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a64d10..f610f799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * 功能优化:首次登录不需要验证码,直接登录,登录失败之后才弹出验证码 * 功能新增:给 AI 应用(角色)增加分类,前端支持分类筛选 * 功能优化:允许用户在聊天页面设置是否使用流式输出或者一次性输出,兼容 GPT-O1 模型。 +* 功能优化:移除PayJS支付渠道支持,PayJs已经关闭注册服务,请使用其他支付方式。 +* 功能新增:新增GeeK易支付支付渠道,支持支付宝,微信支付,QQ钱包,京东支付,抖音支付,Paypal支付等支付方式 ## v4.1.3 * 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信 diff --git a/api/core/types/config.go b/api/core/types/config.go index 9638f620..f6e59727 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -12,24 +12,23 @@ import ( ) type AppConfig struct { - Path string `toml:"-"` - Listen string - Session Session - AdminSession Session - ProxyURL string - MysqlDns string // mysql 连接地址 - StaticDir string // 静态资源目录 - StaticUrl string // 静态资源 URL - Redis RedisConfig // redis 连接信息 - ApiConfig ApiConfig // ChatPlus API authorization configs - SMS SMSConfig // send mobile message config - OSS OSSConfig // OSS config - + Path string `toml:"-"` + Listen string + Session Session + AdminSession Session + ProxyURL string + MysqlDns string // mysql 连接地址 + StaticDir string // 静态资源目录 + StaticUrl string // 静态资源 URL + Redis RedisConfig // redis 连接信息 + ApiConfig ApiConfig // ChatPlus API authorization configs + SMS SMSConfig // send mobile message config + OSS OSSConfig // OSS config + SmtpConfig SmtpConfig // 邮件发送配置 XXLConfig XXLConfig AlipayConfig AlipayConfig // 支付宝支付渠道配置 HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置 - SmtpConfig SmtpConfig // 邮件发送配置 - JPayConfig JPayConfig // payjs 支付配置 + GeekPayConfig GeekPayConfig // GEEK 支付配置 WechatPayConfig WechatPayConfig // 微信支付渠道配置 TikaHost string // TiKa 服务器地址 } @@ -83,10 +82,9 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置 ReturnURL string // 支付成功返回地址 } -// JPayConfig PayJs 支付配置 -type JPayConfig struct { +// GeekPayConfig GEEK支付配置 +type GeekPayConfig struct { Enabled bool - Name string // 支付名称,默认 wechat AppId string // 商户 ID PrivateKey string // 私钥 ApiURL string // API 网关 diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go index 5d807f79..8a0112a9 100644 --- a/api/handler/payment_handler.go +++ b/api/handler/payment_handler.go @@ -19,7 +19,6 @@ import ( "geekai/utils" "geekai/utils/resp" "github.com/shopspring/decimal" - "math" "net/http" "net/url" "sync" @@ -34,19 +33,12 @@ type PayWay struct { Value string `json:"value"` } -var ( - PayWayAlipay = PayWay{Name: "支付宝", Value: "alipay"} - PayWayXunHu = PayWay{Name: "虎皮椒", Value: "hupi"} - PayWayJs = PayWay{Name: "PayJS", Value: "payjs"} - PayWayWechat = PayWay{Name: "微信支付", Value: "wechat"} -) - // PaymentHandler 支付服务回调 handler type PaymentHandler struct { BaseHandler alipayService *payment.AlipayService huPiPayService *payment.HuPiPayService - jsPayService *payment.JPayService + geekPayService *payment.GeekPayService wechatPayService *payment.WechatPayService snowflake *service.Snowflake fs embed.FS @@ -58,7 +50,7 @@ func NewPaymentHandler( server *core.AppServer, alipayService *payment.AlipayService, huPiPayService *payment.HuPiPayService, - jsPayService *payment.JPayService, + geekPayService *payment.GeekPayService, wechatPayService *payment.WechatPayService, db *gorm.DB, snowflake *service.Snowflake, @@ -66,7 +58,7 @@ func NewPaymentHandler( return &PaymentHandler{ alipayService: alipayService, huPiPayService: huPiPayService, - jsPayService: jsPayService, + geekPayService: geekPayService, wechatPayService: wechatPayService, snowflake: snowflake, fs: fs, @@ -81,10 +73,9 @@ func NewPaymentHandler( func (h *PaymentHandler) DoPay(c *gin.Context) { orderNo := h.GetTrim(c, "order_no") - payWay := h.GetTrim(c, "pay_way") t := h.GetInt(c, "t", 0) sign := h.GetTrim(c, "sign") - signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, payWay, t, h.signKey) + signStr := fmt.Sprintf("%s-%d-%s", orderNo, t, h.signKey) newSign := utils.Sha256(signStr) if newSign != sign { resp.ERROR(c, "订单签名错误!") @@ -118,7 +109,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { // 更新扫码状态 h.DB.Model(&order).UpdateColumn("status", types.OrderScanned) - if payWay == "alipay" { // 支付宝 + if order.PayWay == "alipay" { // 支付宝 amount := fmt.Sprintf("%.2f", order.Amount) uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject) if err != nil { @@ -128,7 +119,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { c.Redirect(302, uri) return - } else if payWay == "hupi" { // 虎皮椒支付 + } else if order.PayWay == "hupi" { // 虎皮椒支付 params := payment.HuPiPayReq{ Version: "1.1", TradeOrderId: orderNo, @@ -144,15 +135,41 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { } c.Redirect(302, r.URL) + } else if order.PayWay == "wechat" { + uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + c.Redirect(302, uri) + } else if order.PayWay == "geek" { + params := payment.GeekPayParams{ + OutTradeNo: orderNo, + Method: "web", + Name: order.Subject, + Money: fmt.Sprintf("%f", order.Amount), + ClientIP: c.ClientIP(), + Device: "pc", + Type: "alipay", + } + + s, err := h.geekPayService.Pay(params) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + resp.SUCCESS(c, s) } - resp.ERROR(c, "Invalid operations") + //resp.ERROR(c, "Invalid operations") } // PayQrcode 生成支付 URL 二维码 func (h *PaymentHandler) PayQrcode(c *gin.Context) { var data struct { - PayWay string `json:"pay_way"` // 支付方式 - ProductId uint `json:"product_id"` + PayWay string `json:"pay_way"` // 支付方式 + PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq... + ProductId uint `json:"product_id"` // 支付产品ID } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -177,24 +194,22 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) { return } - var payWay string var notifyURL string switch data.PayWay { case "hupi": - payWay = PayWayXunHu.Value notifyURL = h.App.Config.HuPiPayConfig.NotifyURL break - case "payjs": - payWay = PayWayJs.Value - notifyURL = h.App.Config.JPayConfig.NotifyURL + case "geek": + notifyURL = h.App.Config.GeekPayConfig.NotifyURL break - case "alipay": - payWay = PayWayAlipay.Value + case "alipay": // 支付宝商户支付 notifyURL = h.App.Config.AlipayConfig.NotifyURL break - default: - payWay = PayWayWechat.Value + case "wechat": // 微信商户支付 notifyURL = h.App.Config.WechatPayConfig.NotifyURL + default: + resp.ERROR(c, "Invalid pay way") + return } // 创建订单 @@ -215,7 +230,8 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) { Subject: product.Name, Amount: amount, Status: types.OrderNotPaid, - PayWay: payWay, + PayWay: data.PayWay, + PayType: data.PayType, Remark: utils.JsonEncode(remark), } res = h.DB.Create(&order) @@ -224,36 +240,26 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) { return } - // PayJs 单独处理,只能用官方生成的二维码 - if data.PayWay == "payjs" { - params := payment.JPayReq{ - TotalFee: int(math.Ceil(order.Amount * 100)), - OutTradeNo: order.OrderNo, - Subject: product.Name, - } - r := h.jsPayService.Pay(params) - if r.IsOK() { - resp.SUCCESS(c, gin.H{"order_no": order.OrderNo, "image": r.Qrcode}) - return - } else { - resp.ERROR(c, "error with generating payment qrcode: "+r.ReturnMsg) - return - } - } - var logo string - if data.PayWay == "alipay" { + switch data.PayType { + case "alipay": logo = "res/img/alipay.jpg" - } else if data.PayWay == "hupi" { - if h.App.Config.HuPiPayConfig.Name == "wechat" { - logo = "res/img/wechat-pay.jpg" - } else { - logo = "res/img/alipay.jpg" - } - } else if data.PayWay == "wechat" { + break + case "wechat": + logo = "res/img/wechat-pay.jpg" + break + case "qq": + logo = "res/img/qq-pay.jpg" + break + default: + logo = "res/img/geek-pay.jpg" + + } + if data.PayType == "alipay" { + logo = "res/img/alipay.jpg" + } else if data.PayType == "wechat" { logo = "res/img/wechat-pay.jpg" } - file, err := h.fs.Open(logo) if err != nil { resp.ERROR(c, "error with open qrcode log file: "+err.Error()) @@ -268,31 +274,21 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) { timestamp := time.Now().Unix() signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey) sign := utils.Sha256(signStr) - var imageURL string - if data.PayWay == "wechat" { - payUrl, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(math.Floor(order.Amount*100)), product.Name) - if err != nil { - resp.ERROR(c, "error with generating wechat payment qrcode: "+err.Error()) - return - } else { - imageURL = payUrl - } - } else { - imageURL = fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, timestamp, sign) - } - imgData, err := utils.GenQrcode(imageURL, 400, file) + payUrl := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&pay_type=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, data.PayType, timestamp, sign) + imgData, err := utils.GenQrcode(payUrl, 400, file) if err != nil { resp.ERROR(c, err.Error()) return } imgDataBase64 := base64.StdEncoding.EncodeToString(imgData) - resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL}) + resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": payUrl}) } // Mobile 移动端支付 func (h *PaymentHandler) Mobile(c *gin.Context) { var data struct { - PayWay string `json:"pay_way"` // 支付方式 + PayWay string `json:"pay_way"` // 支付方式 + PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq... ProductId uint `json:"product_id"` } if err := c.ShouldBindJSON(&data); err != nil { @@ -319,12 +315,10 @@ func (h *PaymentHandler) Mobile(c *gin.Context) { } amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64() - var payWay string var notifyURL, returnURL string var payURL string switch data.PayWay { case "hupi": - payWay = PayWayXunHu.Name notifyURL = h.App.Config.HuPiPayConfig.NotifyURL returnURL = h.App.Config.HuPiPayConfig.ReturnURL parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL) @@ -349,20 +343,16 @@ func (h *PaymentHandler) Mobile(c *gin.Context) { return } payURL = r.URL - case "payjs": - payWay = PayWayJs.Name - notifyURL = h.App.Config.JPayConfig.NotifyURL - returnURL = h.App.Config.JPayConfig.ReturnURL - totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart() - params := url.Values{} - params.Add("total_fee", fmt.Sprintf("%d", totalFee)) - params.Add("out_trade_no", orderNo) - params.Add("body", product.Name) - params.Add("notify_url", notifyURL) - params.Add("auto", "0") - payURL = h.jsPayService.PayH5(params) + case "geek": + //totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart() + //params := url.Values{} + //params.Add("total_fee", fmt.Sprintf("%d", totalFee)) + //params.Add("out_trade_no", orderNo) + //params.Add("body", product.Name) + //params.Add("notify_url", notifyURL) + //params.Add("auto", "0") + //payURL = h.geekPayService.Pay(params) case "alipay": - payWay = PayWayAlipay.Name payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name) if err != nil { errMsg := "error with generating Alipay URL: " + err.Error() @@ -370,7 +360,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) { return } case "wechat": - payWay = PayWayWechat.Name payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP()) if err != nil { errMsg := "error with generating Wechat URL: " + err.Error() @@ -399,7 +388,8 @@ func (h *PaymentHandler) Mobile(c *gin.Context) { Subject: product.Name, Amount: amount, Status: types.OrderNotPaid, - PayWay: payWay, + PayWay: data.PayWay, + PayType: data.PayType, Remark: utils.JsonEncode(remark), } res = h.DB.Create(&order) @@ -506,20 +496,25 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error { // GetPayWays 获取支付方式 func (h *PaymentHandler) GetPayWays(c *gin.Context) { - data := gin.H{} + payWays := make([]gin.H, 0) if h.App.Config.AlipayConfig.Enabled { - data["alipay"] = gin.H{"name": "alipay"} + payWays = append(payWays, gin.H{"pay_way": "alipay", "pay_type": "alipay"}) } if h.App.Config.HuPiPayConfig.Enabled { - data["hupi"] = gin.H{"name": h.App.Config.HuPiPayConfig.Name} + payWays = append(payWays, gin.H{"pay_way": "hupi", "pay_type": h.App.Config.HuPiPayConfig.Name}) } - if h.App.Config.JPayConfig.Enabled { - data["payjs"] = gin.H{"name": h.App.Config.JPayConfig.Name} + if h.App.Config.GeekPayConfig.Enabled { + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "alipay"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wechat"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qq"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jd"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"}) } if h.App.Config.WechatPayConfig.Enabled { - data["wechat"] = gin.H{"name": "wechat"} + payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wechat"}) } - resp.SUCCESS(c, data) + resp.SUCCESS(c, payWays) } // HuPiPayNotify 虎皮椒支付异步回调 @@ -593,12 +588,12 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) { // 校验订单支付状态 tradeNo := c.Request.Form.Get("payjs_order_id") - err = h.jsPayService.TradeVerify(tradeNo) - if err != nil { - logger.Error("订单校验失败:", err) - c.String(http.StatusOK, "fail") - return - } + //err = h.geekPayService.TradeVerify(tradeNo) + //if err != nil { + // logger.Error("订单校验失败:", err) + // c.String(http.StatusOK, "fail") + // return + //} err = h.notify(orderNo, tradeNo) if err != nil { diff --git a/api/handler/test_handler.go b/api/handler/test_handler.go index fe38bc38..88e95a2f 100644 --- a/api/handler/test_handler.go +++ b/api/handler/test_handler.go @@ -11,10 +11,10 @@ import ( type TestHandler struct { db *gorm.DB snowflake *service.Snowflake - js *payment.JPayService + js *payment.GeekPayService } -func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.JPayService) *TestHandler { +func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.GeekPayService) *TestHandler { return &TestHandler{db: db, snowflake: snowflake, js: js} } diff --git a/api/res/img/geek-pay.jpg b/api/res/img/geek-pay.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48baf80d101b4ebd13d02ea0b6066dc082fe2b5a GIT binary patch literal 27461 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 b!CNu*|9I{A8}R760b;up{C^zzejEG`INjh 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 apVj@?{rKxp|JN}8dqnO3 0 { - pList = append(pList, key+"="+value) - } - } - var src = strings.Join(pList, "&") - src += "&key=" + js.config.PrivateKey - - md5bs := md5.Sum([]byte(src)) - md5res := hex.EncodeToString(md5bs[:]) - return strings.ToUpper(md5res) -} - -// TradeVerify 查询订单支付状态 -// @param tradeNo 支付平台交易 ID -func (js *JPayService) TradeVerify(tradeNo string) error { - apiURL := fmt.Sprintf("%s/api/check", js.config.ApiURL) - params := url.Values{} - params.Add("payjs_order_id", tradeNo) - params.Add("sign", js.sign(params)) - data := strings.NewReader(params.Encode()) - resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", data) - if err != nil { - return fmt.Errorf("error with http reqeust: %v", err) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("error with reading response: %v", err) - } - - var r struct { - ReturnCode int `json:"return_code"` - Status int `json:"status"` - } - err = utils.JsonDecode(string(body), &r) - if err != nil { - return fmt.Errorf("error with decode response: %v", err) - } - - if r.ReturnCode == 1 && r.Status == 1 { - return nil - } else { - logger.Errorf("PayJs 支付验证响应:%s", string(body)) - return errors.New("order not paid") - } -} diff --git a/api/store/model/order.go b/api/store/model/order.go index a1c6929f..86e81ca7 100644 --- a/api/store/model/order.go +++ b/api/store/model/order.go @@ -18,6 +18,7 @@ type Order struct { Status types.OrderStatus Remark string PayTime int64 - PayWay string // 支付方式 + PayWay string // 支付渠道 + PayType string // 支付类型 DeletedAt gorm.DeletedAt } diff --git a/api/store/vo/order.go b/api/store/vo/order.go index c076d002..39693649 100644 --- a/api/store/vo/order.go +++ b/api/store/vo/order.go @@ -16,5 +16,6 @@ type Order struct { Status types.OrderStatus `json:"status"` PayTime int64 `json:"pay_time"` PayWay string `json:"pay_way"` + PayType string `json:"pay_type"` Remark types.OrderRemark `json:"remark"` } diff --git a/database/update-v4.1.4.sql b/database/update-v4.1.4.sql index 28d93e5c..0ea7f36f 100644 --- a/database/update-v4.1.4.sql +++ b/database/update-v4.1.4.sql @@ -11,4 +11,5 @@ ALTER TABLE `chatgpt_app_types`ADD PRIMARY KEY (`id`); ALTER TABLE `chatgpt_app_types` MODIFY `id` int NOT NULL AUTO_INCREMENT; ALTER TABLE `chatgpt_chat_roles` ADD `tid` INT NOT NULL COMMENT '分类ID' AFTER `name`; -ALTER TABLE `chatgpt_chat_history` ADD `total_tokens` INT NOT NULL COMMENT '消耗总Token长度' AFTER `tokens`; \ No newline at end of file +ALTER TABLE `chatgpt_chat_history` ADD `total_tokens` INT NOT NULL COMMENT '消耗总Token长度' AFTER `tokens`; +ALTER TABLE `chatgpt_orders` ADD `pay_type` VARCHAR(30) NOT NULL COMMENT '支付类型' AFTER `pay_way`; \ No newline at end of file diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl index c1f61bb5..9dab8325 100644 --- a/web/src/assets/css/member.styl +++ b/web/src/assets/css/member.styl @@ -110,6 +110,7 @@ overflow hidden cursor pointer transition: all 0.3s ease; /* 添加过渡效果 */ + margin-bottom 20px .image-container { display flex @@ -175,10 +176,11 @@ .pay-way { padding 10px 0 display flex - justify-content: space-between + justify-content: center + flex-wrap wrap - .iconfont { - margin-right 5px + .el-button { + margin 10px 5px 0 5px } } } diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index 31901bb5..a0959ced 100644 --- a/web/src/assets/iconfont/iconfont.css +++ b/web/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4125778 */ - src: url('iconfont.woff2?t=1725929120246') format('woff2'), - url('iconfont.woff?t=1725929120246') format('woff'), - url('iconfont.ttf?t=1725929120246') format('truetype'); + src: url('iconfont.woff2?t=1726612860394') format('woff2'), + url('iconfont.woff?t=1726612860394') format('woff'), + url('iconfont.ttf?t=1726612860394') format('truetype'); } .iconfont { @@ -13,6 +13,22 @@ -moz-osx-font-smoothing: grayscale; } +.icon-douyin:before { + content: "\e852"; +} + +.icon-paypal:before { + content: "\e60f"; +} + +.icon-qq:before { + content: "\e69f"; +} + +.icon-jd-pay:before { + content: "\e8dd"; +} + .icon-luma:before { content: "\e704"; } diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index ad94b5e2..32973473 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var t,h,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}t=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),t()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(i=t,o=a.document,z=!1,p(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,s())})}function s(){z||(z=!0,i())}function p(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(p,50)}s()}})(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}p()}})(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index f114daa2..7dc3877f 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -5,6 +5,34 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "22174321", + "name": "抖音支付", + "font_class": "douyin", + "unicode": "e852", + "unicode_decimal": 59474 + }, + { + "icon_id": "1238433", + "name": "social-paypal", + "font_class": "paypal", + "unicode": "e60f", + "unicode_decimal": 58895 + }, + { + "icon_id": "1244217", + "name": "qq", + "font_class": "qq", + "unicode": "e69f", + "unicode_decimal": 59039 + }, + { + "icon_id": "18166714", + "name": "京东支付", + "font_class": "jd-pay", + "unicode": "e8dd", + "unicode_decimal": 59613 + }, { "icon_id": "41645421", "name": "luma-logo", diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index f00663eccf08e1b716563b5d864c8fdaa3285968..8b2283596f825847456513e0dc78c13b446c83fb 100644 GIT binary patch delta 3422 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 diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index ff63b66a4981c48ca2526ecbb3b8c06394358231..426fce0c2100df24c4d077b53b5391fa230d46af 100644 GIT binary patch delta 19950 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 diff --git a/web/src/assets/iconfont/iconfont.woff2 b/web/src/assets/iconfont/iconfont.woff2 index a6df0ee8c0360ad8b6a3fb72f289f09e646827b9..906588c2f5ac1d27de06f38d88671691e531f21e 100644 GIT binary patch literal 17612 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 literal 16812 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 fZMbq&mNBJcx=f{<&Oc)cqH7LZ)2hwvby>$9 - - - + + +

消费账单

@@ -132,7 +138,7 @@ - 请打开手机{{ payName }}扫码支付 + 请打开手机扫码支付 @@ -146,7 +152,6 @@ import {onMounted, ref} from "vue" import {ElMessage} from "element-plus"; import {httpGet, httpPost} from "@/utils/http"; -import ItemList from "@/components/ItemList.vue"; import {InfoFilled, SuccessFilled} from "@element-plus/icons-vue"; import {checkSession, getSystemInfo} from "@/store/cache"; import UserProfile from "@/components/UserProfile.vue"; @@ -176,19 +181,16 @@ const text = ref("") const user = ref(null) const isLogin = ref(false) const router = useRouter() -const curPayProduct = ref(null) const activeOrderNo = ref("") const countDownRef = ref(null) const orderTimeout = ref(1800) const loading = ref(true) const orderPayInfoText = ref("") -const vipMonthPower = ref(0) -const powerPrice = ref(0) -const payWays = ref({}) +const payWays = ref([]) const amount = ref(0) -const payName = ref("支付宝") -const curPay = ref("alipay") // 当前支付方式 +const curPayProduct = ref(null) +const curPayWay = ref({pay_way: "", pay_type:""}) const vipInfoText = ref("") const store = useSharedStore() const profileKey = ref(0) @@ -215,8 +217,6 @@ onMounted(() => { if (res.data['order_pay_timeout'] > 0) { orderTimeout.value = res.data['order_pay_timeout'] } - vipMonthPower.value = res.data['vip_month_power'] - powerPrice.value = res.data['power_price'] vipInfoText.value = res.data['vip_info_text'] }).catch(e => { ElMessage.error("获取系统配置失败:" + e.message) @@ -231,22 +231,24 @@ onMounted(() => { // refresh payment qrcode const refreshPayCode = () => { - if (curPay.value === 'alipay') { - alipay(curPayProduct.value) - } else if (curPay.value === 'hupi') { - huPiPay(curPayProduct.value) - } else if (curPay.value === 'payjs') { - PayJs(curPayProduct.value) - } + genPayQrcode(curPayProduct.value, curPayWay.value) } -const genPayQrcode = () => { +const genPayQrcode = (product, payWay) => { + if (!isLogin.value) { + store.setShowLoginDialog(true) + return + } + loading.value = true text.value = "" + curPayProduct.value = product + curPayWay.value = payWay + amount.value = (product.price - product.discount).toFixed(2) httpPost("/api/payment/qrcode", { - pay_way: curPay.value, + pay_way: payWay.pay_way, + pay_type: payWay.pay_type, product_id: curPayProduct.value.id, - user_id: user.value.id }).then(res => { showPayDialog.value = true qrcode.value = res.data['image'] @@ -262,67 +264,6 @@ const genPayQrcode = () => { }) } -const alipay = (row) => { - payName.value = "支付宝" - curPay.value = "alipay" - amount.value = (row.price - row.discount).toFixed(2) - if (!isLogin.value) { - store.setShowLoginDialog(true) - return - } - - if (row) { - curPayProduct.value = row - } - genPayQrcode() -} - -// 虎皮椒支付 -const huPiPay = (row) => { - payName.value = payWays.value["hupi"]["name"] === "wechat" ? '微信' : '支付宝' - curPay.value = "hupi" - amount.value = (row.price - row.discount).toFixed(2) - if (!isLogin.value) { - store.setShowLoginDialog(true) - return - } - - if (row) { - curPayProduct.value = row - } - genPayQrcode() -} - -// PayJS 支付 -const PayJs = (row) => { - payName.value = '微信' - curPay.value = "payjs" - amount.value = (row.price - row.discount).toFixed(2) - if (!isLogin.value) { - store.setShowLoginDialog(true) - return - } - - if (row) { - curPayProduct.value = row - } - genPayQrcode() -} - -const wechatPay = (row) => { - payName.value = '微信' - curPay.value = "wechat" - amount.value = (row.price - row.discount).toFixed(2) - if (!isLogin.value) { - store.setShowLoginDialog(true) - return - } - - if (row) { - curPayProduct.value = row - } - genPayQrcode() -} const queryOrder = (orderNo) => { httpGet("/api/order/query", {order_no: orderNo}).then(res => { @@ -331,11 +272,7 @@ const queryOrder = (orderNo) => { queryOrder(orderNo) } else if (res.data.status === 2) { text.value = "支付成功,正在刷新页面" - if (curPay.value === "payjs") { - setTimeout(() => location.reload(), 3000) - } else { - setTimeout(() => location.reload(), 500) - } + setTimeout(() => location.reload(), 500) } else { // 如果当前订单没有过期,继续等待订单的下一个状态 if (activeOrderNo.value === orderNo) { From d8cb92d8d4e1eac5d4c3adf64ba7dced24b93227 Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 18 Sep 2024 18:07:49 +0800 Subject: [PATCH 14/41] Geek Pay notify is ready --- api/core/app_server.go | 5 +- api/core/types/config.go | 22 +- api/handler/payment_handler.go | 416 +++++++------------------ api/main.go | 11 +- api/service/payment/alipay_service.go | 34 +- api/service/payment/geekpay_service.go | 92 +++--- api/service/payment/wepay_service.go | 33 +- web/src/assets/css/member.styl | 21 ++ web/src/assets/iconfont/iconfont.css | 14 +- web/src/assets/iconfont/iconfont.js | 2 +- web/src/assets/iconfont/iconfont.json | 20 +- web/src/assets/iconfont/iconfont.ttf | Bin 30468 -> 30892 bytes web/src/assets/iconfont/iconfont.woff | Bin 20304 -> 20536 bytes web/src/assets/iconfont/iconfont.woff2 | Bin 17612 -> 17888 bytes web/src/components/UserOrder.vue | 2 +- web/src/views/Member.vue | 142 ++------- 16 files changed, 270 insertions(+), 544 deletions(-) diff --git a/api/core/app_server.go b/api/core/app_server.go index 9990ddba..efa63091 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -219,10 +219,6 @@ func needLogin(c *gin.Context) bool { c.Request.URL.Path == "/api/product/list" || c.Request.URL.Path == "/api/menu/list" || c.Request.URL.Path == "/api/markMap/client" || - c.Request.URL.Path == "/api/payment/alipay/notify" || - c.Request.URL.Path == "/api/payment/hupipay/notify" || - c.Request.URL.Path == "/api/payment/payjs/notify" || - c.Request.URL.Path == "/api/payment/wechat/notify" || c.Request.URL.Path == "/api/payment/doPay" || c.Request.URL.Path == "/api/payment/payWays" || c.Request.URL.Path == "/api/suno/client" || @@ -231,6 +227,7 @@ func needLogin(c *gin.Context) bool { c.Request.URL.Path == "/api/download" || c.Request.URL.Path == "/api/video/client" || strings.HasPrefix(c.Request.URL.Path, "/api/test") || + strings.HasPrefix(c.Request.URL.Path, "/api/payment/notify/") || strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") || strings.HasPrefix(c.Request.URL.Path, "/api/config/") || strings.HasPrefix(c.Request.URL.Path, "/api/function/") || diff --git a/api/core/types/config.go b/api/core/types/config.go index f6e59727..dda50c00 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -57,8 +57,7 @@ type AlipayConfig struct { PublicKey string // 用户公钥文件路径 AlipayPublicKey string // 支付宝公钥文件路径 RootCert string // Root 秘钥路径 - NotifyURL string // 异步通知回调 - ReturnURL string // 支付成功返回地址 + NotifyHost string // 通知回调地址 } type WechatPayConfig struct { @@ -68,18 +67,16 @@ type WechatPayConfig struct { SerialNo string // 商户证书的证书序列号 PrivateKey string // 用户私钥文件路径 ApiV3Key string // API V3 秘钥 - NotifyURL string // 异步通知回调 - ReturnURL string // 支付成功返回地址 + NotifyHost string // 通知回调地址 } type HuPiPayConfig struct { //虎皮椒第四方支付配置 - Enabled bool // 是否启用该支付通道 - Name string // 支付名称,如:wechat/alipay - AppId string // App ID - AppSecret string // app 密钥 - ApiURL string // 支付网关 - NotifyURL string // 异步通知回调 - ReturnURL string // 支付成功返回地址 + Enabled bool // 是否启用该支付通道 + Name string // 支付名称,如:wechat/alipay + AppId string // App ID + AppSecret string // app 密钥 + ApiURL string // 支付网关 + NotifyHost string // 通知回调地址 } // GeekPayConfig GEEK支付配置 @@ -88,8 +85,7 @@ type GeekPayConfig struct { AppId string // 商户 ID PrivateKey string // 私钥 ApiURL string // API 网关 - NotifyURL string // 异步回调地址 - ReturnURL string // 支付成功返回地址 + NotifyHost string // 通知回调地址 } type XXLConfig struct { // XXL 任务调度配置 diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go index 8a0112a9..0fba56b4 100644 --- a/api/handler/payment_handler.go +++ b/api/handler/payment_handler.go @@ -9,7 +9,6 @@ package handler import ( "embed" - "encoding/base64" "fmt" "geekai/core" "geekai/core/types" @@ -20,7 +19,6 @@ import ( "geekai/utils/resp" "github.com/shopspring/decimal" "net/http" - "net/url" "sync" "time" @@ -71,62 +69,94 @@ func NewPaymentHandler( } } -func (h *PaymentHandler) DoPay(c *gin.Context) { - orderNo := h.GetTrim(c, "order_no") - t := h.GetInt(c, "t", 0) - sign := h.GetTrim(c, "sign") - signStr := fmt.Sprintf("%s-%d-%s", orderNo, t, h.signKey) - newSign := utils.Sha256(signStr) - if newSign != sign { - resp.ERROR(c, "订单签名错误!") +func (h *PaymentHandler) Pay(c *gin.Context) { + payWay := c.Query("pay_way") + payType := c.Query("pay_type") + productId := c.Query("pid") + device := c.Query("device") + userId := c.Query("user_id") + + var product model.Product + err := h.DB.First(&product, productId).Error + if err != nil { + resp.ERROR(c, "Product not found") return } - // 检查二维码是否过期 - if time.Now().Unix()-int64(t) > int64(h.App.SysConfig.OrderPayTimeout) { - resp.ERROR(c, "支付二维码已过期,请重新生成!") + orderNo, err := h.snowflake.Next(false) + if err != nil { + resp.ERROR(c, "error with generate trade no: "+err.Error()) + return + } + var user model.User + err = h.DB.Where("id", userId).First(&user).Error + if err != nil { + resp.NotAuth(c) + return + } + // 创建订单 + remark := types.OrderRemark{ + Days: product.Days, + Power: product.Power, + Name: product.Name, + Price: product.Price, + Discount: product.Discount, + } + + amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64() + + order := model.Order{ + UserId: user.Id, + Username: user.Username, + ProductId: product.Id, + OrderNo: orderNo, + Subject: product.Name, + Amount: amount, + Status: types.OrderNotPaid, + PayWay: payWay, + PayType: payType, + Remark: utils.JsonEncode(remark), + } + err = h.DB.Create(&order).Error + if err != nil { + resp.ERROR(c, "error with create order: "+err.Error()) return } - if orderNo == "" { - resp.ERROR(c, types.InvalidArgs) - return - } - - var order model.Order - res := h.DB.Where("order_no = ?", orderNo).First(&order) - if res.Error != nil { - resp.ERROR(c, "Order not found") - return - } - - // fix: 这里先检查一下订单状态,如果已经支付了,就直接返回 - if order.Status == types.OrderPaidSuccess { - resp.ERROR(c, "订单已支付成功,无需重复支付!") - return - } - - // 更新扫码状态 - h.DB.Model(&order).UpdateColumn("status", types.OrderScanned) - - if order.PayWay == "alipay" { // 支付宝 - amount := fmt.Sprintf("%.2f", order.Amount) - uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject) + var payURL string + if payWay == "alipay" { // 支付宝 + money := fmt.Sprintf("%.2f", order.Amount) + notifyURL := fmt.Sprintf("%s/api/payment/notify/alipay", h.App.Config.AlipayConfig.NotifyHost) + returnURL := fmt.Sprintf("%s/member", h.App.Config.AlipayConfig.NotifyHost) + if device == "mobile" { + payURL, err = h.alipayService.PayMobile(payment.AlipayParams{ + OutTradeNo: orderNo, + Subject: product.Name, + TotalFee: money, + ReturnURL: returnURL, + NotifyURL: notifyURL, + }) + } else { + payURL, err = h.alipayService.PayPC(payment.AlipayParams{ + OutTradeNo: orderNo, + Subject: product.Name, + TotalFee: money, + ReturnURL: returnURL, + NotifyURL: notifyURL, + }) + } if err != nil { resp.ERROR(c, "error with generate pay url: "+err.Error()) return } - - c.Redirect(302, uri) - return } else if order.PayWay == "hupi" { // 虎皮椒支付 params := payment.HuPiPayReq{ Version: "1.1", TradeOrderId: orderNo, TotalFee: fmt.Sprintf("%f", order.Amount), Title: order.Subject, - NotifyURL: h.App.Config.HuPiPayConfig.NotifyURL, - WapName: "极客学长", + NotifyURL: fmt.Sprintf("%s/api/payment/notify/hupi", h.App.Config.HuPiPayConfig.NotifyHost), + WapName: "GeekAI助手", } r, err := h.huPiPayService.Pay(params) if err != nil { @@ -136,7 +166,8 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { c.Redirect(302, r.URL) } else if order.PayWay == "wechat" { - uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject) + //uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject) + uri, err := h.wechatPayService.PayUrlNative(payment.WechatPayParams{}) if err != nil { resp.ERROR(c, err.Error()) return @@ -144,261 +175,28 @@ func (h *PaymentHandler) DoPay(c *gin.Context) { c.Redirect(302, uri) } else if order.PayWay == "geek" { + notifyURL := fmt.Sprintf("%s/api/payment/notify/geek", h.App.Config.GeekPayConfig.NotifyHost) + returnURL := fmt.Sprintf("%s/member", h.App.Config.GeekPayConfig.NotifyHost) params := payment.GeekPayParams{ OutTradeNo: orderNo, Method: "web", Name: order.Subject, Money: fmt.Sprintf("%f", order.Amount), ClientIP: c.ClientIP(), - Device: "pc", - Type: "alipay", + Device: device, + Type: payType, + ReturnURL: returnURL, + NotifyURL: notifyURL, } - s, err := h.geekPayService.Pay(params) + res, err := h.geekPayService.Pay(params) if err != nil { resp.ERROR(c, err.Error()) return } - resp.SUCCESS(c, s) + payURL = res.PayURL } - //resp.ERROR(c, "Invalid operations") -} - -// PayQrcode 生成支付 URL 二维码 -func (h *PaymentHandler) PayQrcode(c *gin.Context) { - var data struct { - PayWay string `json:"pay_way"` // 支付方式 - PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq... - ProductId uint `json:"product_id"` // 支付产品ID - } - if err := c.ShouldBindJSON(&data); err != nil { - resp.ERROR(c, types.InvalidArgs) - return - } - - var product model.Product - res := h.DB.First(&product, data.ProductId) - if res.Error != nil { - resp.ERROR(c, "Product not found") - return - } - - orderNo, err := h.snowflake.Next(false) - if err != nil { - resp.ERROR(c, "error with generate trade no: "+err.Error()) - return - } - user, err := h.GetLoginUser(c) - if err != nil { - resp.NotAuth(c) - return - } - - var notifyURL string - switch data.PayWay { - case "hupi": - notifyURL = h.App.Config.HuPiPayConfig.NotifyURL - break - case "geek": - notifyURL = h.App.Config.GeekPayConfig.NotifyURL - break - case "alipay": // 支付宝商户支付 - notifyURL = h.App.Config.AlipayConfig.NotifyURL - break - case "wechat": // 微信商户支付 - notifyURL = h.App.Config.WechatPayConfig.NotifyURL - default: - resp.ERROR(c, "Invalid pay way") - return - - } - // 创建订单 - remark := types.OrderRemark{ - Days: product.Days, - Power: product.Power, - Name: product.Name, - Price: product.Price, - Discount: product.Discount, - } - - amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64() - order := model.Order{ - UserId: user.Id, - Username: user.Username, - ProductId: product.Id, - OrderNo: orderNo, - Subject: product.Name, - Amount: amount, - Status: types.OrderNotPaid, - PayWay: data.PayWay, - PayType: data.PayType, - Remark: utils.JsonEncode(remark), - } - res = h.DB.Create(&order) - if res.Error != nil || res.RowsAffected == 0 { - resp.ERROR(c, "error with create order: "+res.Error.Error()) - return - } - - var logo string - switch data.PayType { - case "alipay": - logo = "res/img/alipay.jpg" - break - case "wechat": - logo = "res/img/wechat-pay.jpg" - break - case "qq": - logo = "res/img/qq-pay.jpg" - break - default: - logo = "res/img/geek-pay.jpg" - - } - if data.PayType == "alipay" { - logo = "res/img/alipay.jpg" - } else if data.PayType == "wechat" { - logo = "res/img/wechat-pay.jpg" - } - file, err := h.fs.Open(logo) - if err != nil { - resp.ERROR(c, "error with open qrcode log file: "+err.Error()) - return - } - - parse, err := url.Parse(notifyURL) - if err != nil { - resp.ERROR(c, err.Error()) - return - } - timestamp := time.Now().Unix() - signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey) - sign := utils.Sha256(signStr) - payUrl := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&pay_type=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, data.PayType, timestamp, sign) - imgData, err := utils.GenQrcode(payUrl, 400, file) - if err != nil { - resp.ERROR(c, err.Error()) - return - } - imgDataBase64 := base64.StdEncoding.EncodeToString(imgData) - resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": payUrl}) -} - -// Mobile 移动端支付 -func (h *PaymentHandler) Mobile(c *gin.Context) { - var data struct { - PayWay string `json:"pay_way"` // 支付方式 - PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq... - ProductId uint `json:"product_id"` - } - if err := c.ShouldBindJSON(&data); err != nil { - resp.ERROR(c, types.InvalidArgs) - return - } - - var product model.Product - res := h.DB.First(&product, data.ProductId) - if res.Error != nil { - resp.ERROR(c, "Product not found") - return - } - - orderNo, err := h.snowflake.Next(false) - if err != nil { - resp.ERROR(c, "error with generate trade no: "+err.Error()) - return - } - user, err := h.GetLoginUser(c) - if err != nil { - resp.NotAuth(c) - return - } - - amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64() - var notifyURL, returnURL string - var payURL string - switch data.PayWay { - case "hupi": - notifyURL = h.App.Config.HuPiPayConfig.NotifyURL - returnURL = h.App.Config.HuPiPayConfig.ReturnURL - parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL) - baseURL := fmt.Sprintf("%s://%s", parse.Scheme, parse.Host) - params := payment.HuPiPayReq{ - Version: "1.1", - TradeOrderId: orderNo, - TotalFee: fmt.Sprintf("%f", amount), - Title: product.Name, - NotifyURL: notifyURL, - ReturnURL: returnURL, - CallbackURL: returnURL, - WapName: "极客学长", - WapUrl: baseURL, - Type: "WAP", - } - r, err := h.huPiPayService.Pay(params) - if err != nil { - errMsg := "error with generating Pay Hupi URL: " + err.Error() - logger.Error(errMsg) - resp.ERROR(c, errMsg) - return - } - payURL = r.URL - case "geek": - //totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart() - //params := url.Values{} - //params.Add("total_fee", fmt.Sprintf("%d", totalFee)) - //params.Add("out_trade_no", orderNo) - //params.Add("body", product.Name) - //params.Add("notify_url", notifyURL) - //params.Add("auto", "0") - //payURL = h.geekPayService.Pay(params) - case "alipay": - payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name) - if err != nil { - errMsg := "error with generating Alipay URL: " + err.Error() - resp.ERROR(c, errMsg) - return - } - case "wechat": - payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP()) - if err != nil { - errMsg := "error with generating Wechat URL: " + err.Error() - logger.Error(errMsg) - resp.ERROR(c, errMsg) - return - } - default: - resp.ERROR(c, "Unsupported pay way: "+data.PayWay) - return - } - // 创建订单 - remark := types.OrderRemark{ - Days: product.Days, - Power: product.Power, - Name: product.Name, - Price: product.Price, - Discount: product.Discount, - } - - order := model.Order{ - UserId: user.Id, - Username: user.Username, - ProductId: product.Id, - OrderNo: orderNo, - Subject: product.Name, - Amount: amount, - Status: types.OrderNotPaid, - PayWay: data.PayWay, - PayType: data.PayType, - Remark: utils.JsonEncode(remark), - } - res = h.DB.Create(&order) - if res.Error != nil || res.RowsAffected == 0 { - resp.ERROR(c, "error with create order: "+res.Error.Error()) - return - } - - resp.SUCCESS(c, gin.H{"url": payURL, "order_no": orderNo}) + resp.SUCCESS(c, payURL) } // 异步通知回调公共逻辑 @@ -505,14 +303,14 @@ func (h *PaymentHandler) GetPayWays(c *gin.Context) { } if h.App.Config.GeekPayConfig.Enabled { payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "alipay"}) - payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wechat"}) - payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qq"}) - payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jd"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wxpay"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qqpay"}) + payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jdpay"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"}) } if h.App.Config.WechatPayConfig.Enabled { - payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wechat"}) + payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wxpay"}) } resp.SUCCESS(c, payWays) } @@ -570,32 +368,28 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) { c.String(http.StatusOK, "success") } -// PayJsNotify PayJs 支付异步回调 -func (h *PaymentHandler) PayJsNotify(c *gin.Context) { - err := c.Request.ParseForm() - if err != nil { +// GeekPayNotify 支付异步回调 +func (h *PaymentHandler) GeekPayNotify(c *gin.Context) { + var params = make(map[string]string) + for k := range c.Request.URL.Query() { + params[k] = c.Query(k) + } + + logger.Infof("收到GeekPay订单支付回调:%+v", params) + // 检查支付状态 + if params["trade_status"] != "TRADE_SUCCESS" { + c.String(http.StatusOK, "success") + return + } + + sign := h.geekPayService.Sign(params) + if sign != c.Query("sign") { + logger.Errorf("签名验证失败, %s, %s", sign, c.Query("sign")) c.String(http.StatusOK, "fail") return } - orderNo := c.Request.Form.Get("out_trade_no") - returnCode := c.Request.Form.Get("return_code") - logger.Infof("收到PayJs订单支付回调,订单 NO:%s,支付结果代码:%v", orderNo, returnCode) - // 支付失败 - if returnCode != "1" { - return - } - - // 校验订单支付状态 - tradeNo := c.Request.Form.Get("payjs_order_id") - //err = h.geekPayService.TradeVerify(tradeNo) - //if err != nil { - // logger.Error("订单校验失败:", err) - // c.String(http.StatusOK, "fail") - // return - //} - - err = h.notify(orderNo, tradeNo) + err := h.notify(params["out_trade_no"], params["trade_no"]) if err != nil { c.String(http.StatusOK, "fail") return diff --git a/api/main.go b/api/main.go index 544abc1e..6d00d3fd 100644 --- a/api/main.go +++ b/api/main.go @@ -372,14 +372,11 @@ func main() { }), fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) { group := s.Engine.Group("/api/payment/") - group.GET("doPay", h.DoPay) + group.GET("doPay", h.Pay) group.GET("payWays", h.GetPayWays) - group.POST("qrcode", h.PayQrcode) - group.POST("mobile", h.Mobile) - group.POST("alipay/notify", h.AlipayNotify) - group.POST("hupipay/notify", h.HuPiPayNotify) - group.POST("payjs/notify", h.PayJsNotify) - group.POST("wechat/notify", h.WechatPayNotify) + group.POST("notify/alipay", h.AlipayNotify) + group.GET("notify/geek", h.GeekPayNotify) + group.POST("notify/wechat", h.WechatPayNotify) }), fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) { group := s.Engine.Group("/api/admin/product/") diff --git a/api/service/payment/alipay_service.go b/api/service/payment/alipay_service.go index 8ab93eda..d6c8a139 100644 --- a/api/service/payment/alipay_service.go +++ b/api/service/payment/alipay_service.go @@ -44,9 +44,7 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) { //client.DebugSwitch = gopay.DebugOn // 开启调试模式 client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间 SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8 - SetSignType(alipay.RSA2). // 设置签名类型,不设置默认 RSA2 - SetReturnUrl(config.ReturnURL). // 设置返回URL - SetNotifyUrl(config.NotifyURL) + SetSignType(alipay.RSA2) // 设置签名类型,不设置默认 RSA2 if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil { return nil, fmt.Errorf("error with load payment public key: %v", err) @@ -55,23 +53,33 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) { return &AlipayService{config: &config, client: client}, nil } -func (s *AlipayService) PayUrlMobile(outTradeNo string, amount string, subject string) (string, error) { +type AlipayParams struct { + OutTradeNo string `json:"out_trade_no"` + Subject string `json:"subject"` + TotalFee string `json:"total_fee"` + ReturnURL string `json:"return_url"` + NotifyURL string `json:"notify_url"` +} + +func (s *AlipayService) PayMobile(params AlipayParams) (string, error) { bm := make(gopay.BodyMap) - bm.Set("subject", subject) - bm.Set("out_trade_no", outTradeNo) - bm.Set("quit_url", s.config.ReturnURL) - bm.Set("total_amount", amount) + bm.Set("subject", params.Subject) + bm.Set("out_trade_no", params.OutTradeNo) + bm.Set("quit_url", params.ReturnURL) + bm.Set("total_amount", params.TotalFee) + bm.Set("return_url", params.ReturnURL) + bm.Set("notify_url", params.NotifyURL) bm.Set("product_code", "QUICK_WAP_WAY") return s.client.TradeWapPay(context.Background(), bm) } -func (s *AlipayService) PayUrlPc(outTradeNo string, amount string, subject string) (string, error) { +func (s *AlipayService) PayPC(params AlipayParams) (string, error) { bm := make(gopay.BodyMap) - bm.Set("subject", subject) - bm.Set("out_trade_no", outTradeNo) - bm.Set("total_amount", amount) + bm.Set("subject", params.Subject) + bm.Set("out_trade_no", params.OutTradeNo) + bm.Set("total_amount", params.TotalFee) bm.Set("product_code", "FAST_INSTANT_TRADE_PAY") - return s.client.TradePagePay(context.Background(), bm) + return s.client.SetNotifyUrl(params.NotifyURL).SetReturnUrl(params.ReturnURL).TradePagePay(context.Background(), bm) } // TradeVerify 交易验证 diff --git a/api/service/payment/geekpay_service.go b/api/service/payment/geekpay_service.go index 2018f43f..7348c0d8 100644 --- a/api/service/payment/geekpay_service.go +++ b/api/service/payment/geekpay_service.go @@ -8,15 +8,11 @@ package payment // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/pem" + "encoding/json" + "errors" "fmt" "geekai/core/types" + "geekai/utils" "io" "net/http" "net/url" @@ -46,41 +42,37 @@ type GeekPayParams struct { ClientIP string `json:"clientip"` //用户IP地址 SubOpenId string `json:"sub_openid"` // 微信用户 openid,仅小程序支付需要 SubAppId string `json:"sub_appid"` // 小程序 AppId,仅小程序支付需要 + NotifyURL string `json:"notify_url"` + ReturnURL string `json:"return_url"` } // Pay 支付订单 -func (s *GeekPayService) Pay(params GeekPayParams) (string, error) { - if params.Type == "wechat" { - params.Type = "wxpay" - } +func (s *GeekPayService) Pay(params GeekPayParams) (*GeekPayResp, error) { p := map[string]string{ - "pid": s.config.AppId, - "method": params.Method, + "pid": s.config.AppId, + //"method": params.Method, "device": params.Device, "type": params.Type, "out_trade_no": params.OutTradeNo, "name": params.Name, "money": params.Money, "clientip": params.ClientIP, - "sub_openid": params.SubOpenId, - "sub_appid": params.SubAppId, - "notify_url": s.config.NotifyURL, - "return_url": s.config.ReturnURL, + "notify_url": params.NotifyURL, + "return_url": params.ReturnURL, "timestamp": fmt.Sprintf("%d", time.Now().Unix()), } - sign, err := s.Sign(p) - if err != nil { - return "", err - } - p["sign"] = sign - p["sign_type"] = "RSA" + p["sign"] = s.Sign(p) + p["sign_type"] = "MD5" return s.sendRequest(s.config.ApiURL, p) } -func (s *GeekPayService) Sign(params map[string]string) (string, error) { +func (s *GeekPayService) Sign(params map[string]string) string { // 按字母顺序排序参数 var keys []string for k := range params { + if params[k] == "" || k == "sign" || k == "sign_type" { + continue + } keys = append(keys, k) } sort.Strings(keys) @@ -93,44 +85,48 @@ func (s *GeekPayService) Sign(params map[string]string) (string, error) { signStr.WriteString(params[k]) signStr.WriteString("&") } - signString := strings.TrimSuffix(signStr.String(), "&") + signString := strings.TrimSuffix(signStr.String(), "&") + s.config.PrivateKey - // 使用RSA私钥签名 - block, _ := pem.Decode([]byte(s.config.PrivateKey)) - if block == nil { - return "", fmt.Errorf("failed to decode private key") - } - - priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return "", fmt.Errorf("failed to parse private key: %v", err) - } - - hashed := sha256.Sum256([]byte(signString)) - signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, hashed[:]) - if err != nil { - panic(fmt.Sprintf("failed to sign: %v", err)) - } - - return base64.StdEncoding.EncodeToString(signature), nil + return utils.Md5(signString) } -func (s *GeekPayService) sendRequest(apiEndpoint string, params map[string]string) (string, error) { +type GeekPayResp struct { + Code int `json:"code"` + Msg string `json:"msg"` + TradeNo string `json:"trade_no"` + PayURL string `json:"payurl"` + QrCode string `json:"qrcode"` + UrlScheme string `json:"urlscheme"` // 小程序跳转支付链接 +} + +func (s *GeekPayService) sendRequest(endpoint string, params map[string]string) (*GeekPayResp, error) { form := url.Values{} for k, v := range params { form.Add(k, v) } - resp, err := http.PostForm(apiEndpoint, form) + apiURL := fmt.Sprintf("%s/mapi.php", endpoint) + logger.Infof(apiURL) + + resp, err := http.PostForm(apiURL, form) if err != nil { - return "", err + return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) + logger.Debugf(string(body)) if err != nil { - return "", err + return nil, err } - return string(body), nil + var r GeekPayResp + err = json.Unmarshal(body, &r) + if err != nil { + return nil, errors.New("当前支付渠道暂不支持") + } + if r.Code != 1 { + return nil, errors.New(r.Msg) + } + return &r, nil } diff --git a/api/service/payment/wepay_service.go b/api/service/payment/wepay_service.go index b141a8e9..11554e9f 100644 --- a/api/service/payment/wepay_service.go +++ b/api/service/payment/wepay_service.go @@ -46,18 +46,28 @@ func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) { return &WechatPayService{config: &config, client: client}, nil } -func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject string) (string, error) { +type WechatPayParams struct { + OutTradeNo string `json:"out_trade_no"` + TotalFee int `json:"total_fee"` + Subject string `json:"subject"` + ClientIP string `json:"client_ip"` + ReturnURL string `json:"return_url"` + NotifyURL string `json:"notify_url"` +} + +func (s *WechatPayService) PayUrlNative(params WechatPayParams) (string, error) { expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339) // 初始化 BodyMap bm := make(gopay.BodyMap) bm.Set("appid", s.config.AppId). Set("mchid", s.config.MchId). - Set("description", subject). - Set("out_trade_no", outTradeNo). + Set("description", params.Subject). + Set("out_trade_no", params.OutTradeNo). Set("time_expire", expire). - Set("notify_url", s.config.NotifyURL). + Set("notify_url", params.NotifyURL). + Set("return_url", params.ReturnURL). SetBodyMap("amount", func(bm gopay.BodyMap) { - bm.Set("total", amount). + bm.Set("total", params.TotalFee). Set("currency", "CNY") }) @@ -71,22 +81,23 @@ func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject s return wxRsp.Response.CodeUrl, nil } -func (s *WechatPayService) PayUrlH5(outTradeNo string, amount int, subject string, ip string) (string, error) { +func (s *WechatPayService) PayUrlH5(params WechatPayParams) (string, error) { expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339) // 初始化 BodyMap bm := make(gopay.BodyMap) bm.Set("appid", s.config.AppId). Set("mchid", s.config.MchId). - Set("description", subject). - Set("out_trade_no", outTradeNo). + Set("description", params.Subject). + Set("out_trade_no", params.OutTradeNo). Set("time_expire", expire). - Set("notify_url", s.config.NotifyURL). + Set("notify_url", params.NotifyURL). + Set("return_url", params.ReturnURL). SetBodyMap("amount", func(bm gopay.BodyMap) { - bm.Set("total", amount). + bm.Set("total", params.TotalFee). Set("currency", "CNY") }). SetBodyMap("scene_info", func(bm gopay.BodyMap) { - bm.Set("payer_client_ip", ip). + bm.Set("payer_client_ip", params.ClientIP). SetBodyMap("h5_info", func(bm gopay.BodyMap) { bm.Set("type", "Wap") }) diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl index 9dab8325..4dc2209f 100644 --- a/web/src/assets/css/member.styl +++ b/web/src/assets/css/member.styl @@ -181,6 +181,27 @@ .el-button { margin 10px 5px 0 5px + padding 0 + + .icon-alipay,.icon-wechat-pay { + color #ffffff + } + .icon-qq { + color #15A6E8 + font-size 24px + } + .icon-jd-pay { + color #ffffff + font-size 24px + } + .icon-douyin { + color #0a0a0a + font-size 22px + } + .icon-paypal { + font-size 14px + color #009CDE + } } } } diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index a0959ced..e0dee322 100644 --- a/web/src/assets/iconfont/iconfont.css +++ b/web/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4125778 */ - src: url('iconfont.woff2?t=1726612860394') format('woff2'), - url('iconfont.woff?t=1726612860394') format('woff'), - url('iconfont.ttf?t=1726612860394') format('truetype'); + src: url('iconfont.woff2?t=1726622198991') format('woff2'), + url('iconfont.woff?t=1726622198991') format('woff'), + url('iconfont.ttf?t=1726622198991') format('truetype'); } .iconfont { @@ -13,12 +13,12 @@ -moz-osx-font-smoothing: grayscale; } -.icon-douyin:before { - content: "\e852"; +.icon-paypal:before { + content: "\e666"; } -.icon-paypal:before { - content: "\e60f"; +.icon-douyin:before { + content: "\e8db"; } .icon-qq:before { diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index 32973473..74291ab4 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}p()}})(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}p()}})(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index 7dc3877f..a38cbab5 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -6,18 +6,18 @@ "description": "", "glyphs": [ { - "icon_id": "22174321", - "name": "抖音支付", - "font_class": "douyin", - "unicode": "e852", - "unicode_decimal": 59474 + "icon_id": "7443846", + "name": "PayPal", + "font_class": "paypal", + "unicode": "e666", + "unicode_decimal": 58982 }, { - "icon_id": "1238433", - "name": "social-paypal", - "font_class": "paypal", - "unicode": "e60f", - "unicode_decimal": 58895 + "icon_id": "18166694", + "name": "抖音", + "font_class": "douyin", + "unicode": "e8db", + "unicode_decimal": 59611 }, { "icon_id": "1244217", diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 8b2283596f825847456513e0dc78c13b446c83fb..e5b3d7deac1d95400b67e65ab91f69700136c6c8 100644 GIT binary patch delta 1442 zcma)*e@s(X6vxkfzwYaMudgj_U+JT?w%8xg0=7W=hX$%fL?CV&lf}(t;E%!tq1h@9 z|DbGh(?sGVFS?izXLQMMV@^W0EE$Pre=KZsL^u9$sM(@~RD&inaX2&W?nB77#3g$- z=biiR{haf?C-=Mq_t401=xE8VM*wI509tnJX-yCI9=QTQX8`zgTW4?T$^$hGAdLW| zZ0);RcUFZT-XZL-#NzE_!E(v8pUlUJEpOkGdE@<4`O75V9I->4-8))G`%WAO5N8Gt zxkImA1fbf|-LGeMfAbSj+#4cFZvrCk{(L`dGo8-=o_}DRADmQu6DHOBifPpY5E+sh z;Ff8+XUP+4pK)Fj&^xG9JBnH;)9JDM=oku4>A8r;B+v`92=M~RJ_QVQnrvhs2`p4= z=jb3xX;XB<(~@h;b>t4_zRrE0yOz6=o4bwjd4O4%6=tPbvsT7<2-(0SjQ=9F0j5_Z zC6bH>^r6XYiB`$BXoGAcLfSOjH#mR-1|;wSCL!FJ)g01l@rkipcpMp0 zM2r}qu_40+q_v3+hHC&hVnAslVs#cZq5-O!-b|cd=Edt&lyxhm*J%0j_|xNB{qwP#3|`rqh#zdnN9c2TP&9fhO9Sjg?5|0);_LO zC>I?Y9bXi77LGer=a*_k{jexjH10~ejuiWfFS~v2lOD>Gt@m8_p7K8Qoi901a&>v1 zKjyy|RD=DYq0n3@SDGqQ%cjD<@JNJ-oQuqsuPUFe*jBlsa(Km&=>F(r^k%HDirj#H z3;7$yd0GW0l*4*>3Ho3FK7$MJGu$TCm-sk4ZBtgFSiB}t9oKy#9tc*TEavnPMo(8A ziaz}Ri9d^Gbs7f)B|aHpPI0(VRV)!tMDe1YwK{-ha)MwWQA@TK(=P@P{r?g1G{%I; zfy2nhIjkgVp#~HW_?PM;I{K!--WQea67Ca>E~~Mr=dgu07usL+Vn>137V*0zESFk6 zW+O%Y5x6U6?QJ5T5tT(0n+lb!#YDhs3;T-*T4wc_1d5_YGgx8`#k-T;4F+s?x#}yD zaaCqS+#xEfrL(vxTBE8wZqyy#v;=o;Fkq#~wZ1$VFOpf2?+_N?#%O#A&WarSXLE+P z&>yU03dFP^F>m!^LDW~!+qA@7*)H;}3k9WlfmREI$HxR&!lR-kla6gFDqc2IX18$k z87$GP*Oll`B?utckts^JLv#v&AbIEh0KB%fCeH zylTygx@GFe|Ad~+n5@bK#&>7*;3o$^5*41KDh1_-#{+^~AW$8GY|wvU$Y<$K=zb_8 zAHG1)qUb$DnjsjdCS9^4PCCNA&^tCaiq)bTOBI<^+3B-&`>Ig5w7y}up}sV%PqsLn zg-(Cc?@u<>`9H1gs6$snfhVW7HgAZKXG8O7b7MHdd?kd|JhwI&TpOgu*Q8f}4lw4D an^VSgYj3)>liS(7xA)bqvAvo1sJ{Sz5LL$j delta 1002 zcmZ8fZD?Cn7=F*aC->&7iEVCjlQy~OO=glMS=*b$OtV?yjI(5|V_~cex0$rvT3Oq~ zElwk{#4@$R(Y0JGDpLI6Dh!EY+}xnsP^5n>r9XtptWp?+>j=fE6H(iEGWCaYczC~_ z_j%vLIUn9dXV%fQ`{=g-=o|o~2gfqGiz^op0G$U=j|`6%j$M80XET8QeSo3-$kEJ@ zU-;-3!d)YXj*x`<23{oo0>PG%vHXi~Y}QwZUX5UKG&`6%J9%OjK({(GDfphV0#L)b z?8*FrTZ?Z4oGg(^uRvp7sa}FZc)k8>{T{wEv)t+`95}fB8RO1-puq@&K1 zl2(S5(+tRT3fE| zx;^CpN6N7*goNTLr@nnf^oeV(Pu*&FdCS0qf+y+u*t_Jj`=Y+ttw}%jr~IpdNZ|XR zB{;fGyX{;@y5pP9>s=SSHl-G+9EvsW2v9HK#>1N6VE~gD5|Fu>Ga$F<>Oc=QN4gPm zdAyAbiAfX=@n(|>m% zg>y&_hZK_>uSqG1F5M^o-9V*C!)C%MY_2EriA4TvK0(j0Lgdki(-{#cXShF1K0Hkp zEz+0CqCA9Q7Zd@BA>P6=tN{g(i!maGb-T8kx{=CDY&T(U7)+BS#8eVtDR+uN}w7??UZWwNTg*z}1}hfCOgJVe{#79{TO zG;{dtZla})K==6nY*htc@%PMxRz0$ODbkN^Mx diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index 426fce0c2100df24c4d077b53b5391fa230d46af..c20755b7a8f6759f87b0f00101eeb4617e0c5c54 100644 GIT binary patch delta 19959 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 G0002;hnkH5 delta 19730 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 P(dw>innk;mX&$NyeA%qa literal 17612 zcmV(}K+wN;Pew8T0RR9107T3H3jhEB0Cxlc07QEL0RR9100000000000000000000 z0000SR0d!GkZ1~l$aI09NC7qiBm;v83xP%e1Rw>3X9tG?8_i$`RIi!ipgSPBUKxBw zM8&ETMHQzc`~N>9K@Az>e|iISW>yISGJ~v=CgqTGT`NjodPY_Dv?(R%OE0aIWo{|+ zmNEzi>6@Xc?P{d9csn+K^itx3#lcv~Vl!;Ad1EK8f<#K5nEt`dz;Vd^P09KY*&LB^ zXUt2_OafEUqXZ82*Q?xbkIVGX-x2KOkfdlr#P8Q;^-K_zPBKs4E*4s*lluFV#1PD_ zTd`uSfsMhU=_OHXVuK!$BL*!XY4D6Pu>u669?&;1ihsf=tb79_UwQQXe;XqDy>4Gg zx{Q>W2$eYK_7B3;v$$5IKq0?GI4*VCzq~=RUO<*ZN=iUmC*0a$?>6>lxmVbnOyx_> z1(~SlLfTu>9qx9ms7@b11dK5cwJ9zdDj_bk@OgpW00Cy4&79UFXn?N4qu+RiKO{~wxED;B|A+a0I^Wa3xFGrL|G78;%>=_%wsV03 zv6s^-y>5X5!%D-F@67j3jWp@vOq%Y!(@1vQWXTXHEVT6Sk3FBSh$%A0;6Sun9v;rd+t-}}Th|kwde;FK zmKGRSh>kZO-7 z{K6w$2<53n(? z6-VvTzQxe>pCOjJo_s`-uZRRzz=$oezSftq**>Dor-ez40ltHPremR?+YeI`a2Tj| z2B3p0?gg`J8A?$uK#lj@Qj7X3sZaZ|JSLzIvmL;<{Ji`}T7ZIzrg-UP`T{WW61;&} z8tg9%Aqynd4a-SoMVZR~V}M_8{i zXvLv$1o0+9j0A3+z_qPHLZ)!p1~`z<&tC#Kb_IeB0>zuauukCk5Cm3<9YzQeT?BT@NA`ot1 zl!5RBV?H!|j4==vVO#`Z3dTba9L76bf;RpDVO1Is%%TQhBwG9eAfI975^Vo3ayHMi z=%*XhNsX0ZE2&G4i~wEoeQWAQ)s|*NrDHH7TrYK(y48rWmZTg9bho1Tu|RM{Xjo7z zJJg7>sVO0WsJ3g&^hQ$aLtV{w7iF`NhN|+P&VrM_zP&vQ_p5M5OWi2c$O?JrIX{kN zY0TQ%gd-_rJok_zzGOFeM2pC4VZ@5K?MyU#e>qPV>qLR9b2=zt*npPSB9Acf&8GI> zV{OcmKHnepBvO( z=Tr+fv))K4Ky;iFW5gHb!OJMYW;=uzGx1b1V>Xc`9hMgrks2=lq-33Ox^itTn3+FL z@wXkGmQ@Jz5@B9%Gj=U4Pkh(CiOn>fk2nIZ=H8c=_I!)DfxO){zy*mAS4dn}yUvit z@|eV$86#_G`FY$NfRtw`fzn2>bKaS0mb?`0sCVwFuhLYZ9ugplWEw}Do7YB`*P+1? z9Cd4Hg?k3&a=$!r#oHRf5rtG)=D5=@&(MUf^ml?17NkZQ8jtRDVr8XnNDHIXx4hIY z)tz|z)-{QozKF(K32t&ZeX3Cy?iuN~N~{)9WZ1$ZuUdo;3yqOVg!JX0CL*k&iKEKw z^LczwV%-9*(jee9SsA376FkMyCk_r!m_Ws&f+$kJN`~fcUIx8pfhRjPr=Y{H%sl7% zwm4ckr7|a2arvwY7MU2o9K4*1OVxShqqGKM|$wAM~{zpvQSv3GCX2)P=L&%nepz2);# zc(`A-%*_bU1!a}y23v0ZW5&~^yJw{t(IP}Ds_~Hlg&LuT%l@!nK}$nUWg^OqYb?zX_N+#iuuTsShkp|u%zeAh-qD4HW#@b1Y?p+r^1}T z^dgO%*|CdnE#YGbl4#NnarW#3%>L{W3hZ7BskpZz zkop5|>Zw&4#Z81Tk(MC-ccqEF8SAY*^z%gDYq#Bh&VTPOHEzql-hby`rCpt%x!P84Xz$;xk0g2Kho$>;bu4B4nZAYI}AIcfeAa zpi==>09Vj8hNMSM%EC0@gaN7$|rS@46O;(xLZ1B>7`}Xd=f8V#e_N>^yyS~2( z>SOBuTHK=__1y2H3yL)o-;ZXspyI8yWLZj14VT@Gr|@SeSrg}WjB$Owi)tA~tA5#C zLnKK~Pmff*=L3Y)Q8jGH9ciTt0{%vFk z^t)W{^lIl7Mie2ZakpZ(VEr^WrntQ+xq79Y&eAuqf$Gq5xY}3(s9(R7OT`^!9bo|` zvb0vgGIB>$J^T z!ErROV*r=H{;sVTuujSnSmprb)x2CHbbcjgWm(frx8XWH4QL(G(zNl0*L+KyGxn~I zXU64jId#syq^sE2hyo~OuXA_@`T?-jsydi z+0RCNh2PvlMm~Q(R{nrgl^nr;<7hgyl+T$N{~0-JV(3U#wE_p4j_DxlrtaMM(dA1M zJzc#23nN@fy?ihNb9aN1m$!*hZHTi(!l`^ww#wEC}L?^W4eZ+Mr0W3o15ZCRjKwo+9o141mW>ZrHlsN!2 z&dZ4ULX9V*kseVo98T4@pj*04Ax<>8_9Hw`!9rByS{6#F>-@G7$T*W6v~N?3nyc#d z&a$oja+K~;@rGiZ<6jkmcFwE zqbBW2=h!*X?=Ox|0nYTbE>?3fw%|(k`tPuj$Ggb35b-l?3B3xqtMp!P*4C;XdnzO) z@b+ja{|~gX(ED9_)lrk$PBx7VV#Yc2yNm8Sxw7gZ&CS51qz8Uh)6(9pe%F>_5X~}~ zPEj`S?<421kIja3k?UdMKVE7Kal;(kQ>`^u#ow9IslB0>o`5f{f{(3w{IM5{9hzk^ z@#`-|*@`g+8l{(?PzFnAa0x0dDHQt(ux}~sURv5r4gM8?RusE1rnLZI`LUpaCpTElP?+KvR!cbG#VclozL13(u{W8oZzdA)9WlSbJe+n=0`DsF zPEH;ECHPEFc%lb$fhUBgRG4#A1a`OZA+78wu78==yElBySgP*hC@R2K#mLwNFiyD2no1{7uLtP}6lpi!Ra>kC&m zxEhKh{r%Af4>_$(4-UqUIsjUG%sVK@W{kOUy*Frcxmn$!=t?Ff9{ipj+Z&RoMNr9K3SIXjGF)DeK$#r;Z38M751!yc39%!l#($s(KPppDi z)C?iETK2z$2wj8<4>E?Rjq$P#C{>*r*DM*GdQjheGk_eo*fDII z^9!ss3efJ}0&By&-}g?`bV{gM+>CaI=Fwl{{39rFS)`og3Ezc8nvmdnGE)fz4}*Yq zfB>ODMp2&yQ6WX)*dSUQR2do&4VGMa(C2JrB?bqpuD(7$Js7WVKMDc&y3eVTsq#-K zaG&}7rN~KMO%jf=mJp;WaMerJvV740eR-th^80!Nqh-NbaDZuNmGxI5Pc5It^Xx%lIQ~{T^@RL41 zXUs_OY}TA_Z*3)H;k@b#LA2k><4{{Pm-QuMYeQ)xHY4qq*2U%NZ-*Bztj+yAQirBB zi270p3jfbDIWHm~UDe^E>)Rh+V`=52T**aeiCY8{bru`-whco)dgb;H#!GX(cF8Hl zwSacHS<;YPHJ1Ur^Hvr(VG$iPluUDOlAx9Vy{GSgIGBUN#9kB!|FE3YB=r8ixOV-1 zz49qE%HhV1KJ1jm9(gWS+JdLmV7~pA`C-cLEBRIKw}>7P*Vr~~tm37Wl?#niQK3`j z8Uuc}?c<=z`I$sg(-K*PmtGw`L$h!vj;GTSgP8J{wghSm-ITj;JC3M7>f}pPQ`1N< zOibV?JwL*|SxvstAiQkCXY>lqk$wbDYigqK6d67?g_WaHflj$$u}2=cv9fjX*=L>7 z?;Qk_!nrzr(&@GZd!t{4cVsY8<4Lhuxudun6-}0rZ6=tO);faf3j5tm z7X}F0#<7JN81eYh989>qJdgI8D14hLoIa_)3D9mvvq-PaE&Vp4&e|7#pxryPm>Q!s zfPY`>Ds1pGx8<=~$x^Zmgd{kB!`0ROOtWvRZ z9y;}_{*2OyDiv>jv{gNejrELclIJ#|eCt#YY+-Jr|C~z%s$~^5wi!CQ5*-}fK6UmY zi)+HTAY>dji#-k?V-}2k*2f{?__|O{I19EK6V+SuE zod*^bUhU&~mc4PyDTA@DNE`1=@obR-vKq3k)2!R)5~?#+iwBvsK_4U%aP>d! znE!?`M?kTDE-w)x2(MAfOSMgT(*LDp1tMD>d_CTy@y(5O8gonIpSIfVd8TpafOK|t zjH|ygg3n{XOE8uNZ4tRYkN^+Ma@xEVWRwSlcb_9rs#E}!;O3QwC$qY%Zp~##p+fD# z-hUY*pX(4S({+hR2~d}slxZNINNWi%rUR{YEG1HouHg2{HdyZ~*^##ozPTi!bNGkTPW! z5~d6HFJcR~2M4OUoIAQGd+Sea1d~0@mjvjx(3-0P-QfJWi6o7nOzv6B?xsGJkju#8 z<(&wY=>YhCYY6kW;mrcrGQLEJVzDkhD54dNM7)E3b`l7 z%;h$98aHOjlEGle$KaAI9Lq7J+q`kendpkswKAJUacNLkeb0xz5{X#oFY_W5iJoD2 z*NmHFTb4MO=ilNAbwuS<3_60pIid351ZbbeSP(e!M8?N^v#Kf6u^(F>53XL&CKqA!7I#^O+r)4{$%1oteNzyZ|oE_sQ zV%}Zp&@7Vy`#gtO73g%OYplk&7)UGHt5eEDe+hIQTlkDZ;lr{&u$%b_X<&={S|}^f zPD3kNR&!VI6#uD(RE%qQ2JR|8XO2JtGw@@gqf5rz_~`PqjxJzfmupn__~XEGySBIN zTkrP9I9dVv*|WMK9G;RY)! zITD>Vd_mYk0W)(n>WROz3o}8D%kbI^d=SQ}B0!2$3#!Z`$E^Iqli%sU+)GQZEv*{| z7=wG!>yK;mPF<8W3BBIH6GO)GFXwK&ST$7@c7lrc;+4uYfB8{|*t9Fguj9Ot0-q!_ z3SWpK2>!ram2RApUS;4_WHm7mhou#-V)f&wTWpxzVAeCPGMoCm#I6CD6x(b$p$iGh znBQqKE43%=TUWi?8Um8~96Q zVg!-dsTC-xXjo7vv7_0)tOE6$xD80?lN41f1Xy;|tG)7CKLKM|{y|=8V!iKJp$i8R z`gUWVN{^JvggY#MrsaAq>_*FCO!y6%bw?qno|eU3YS7^YQl#t!4xY8f5AT0gW*@lN z<8>(4r|~=1cf5z;-p#|8Z+-vfy;`?|hHl?{idt0!yB|73I8XNYC@ga3AR3ZCRjZBy zAt?dM+3*&&@~=qsUU18U=B|~3(u8qO-@Vb%&A!sf7{bGZ2(@xhQq{`ZjRNa~`PI@` zzODZE@i#Pq|FiFy=sH*Uqd6<2lXDqeetnV-m^ynE+Vac@Wk#|aHbF~=I}Xys{l6G6 zqPqlNSWf1g%vMw>O`{eAQYquaXf?q4?D* z)sUoS&E|Wi>b{mxSy9+c^^_P`((yhUoN>upRpAfCT~M_JRgru_ftkxIlH4drTz(Tv zvg%c2kD-NFhb;khAiQ%AjyRg|^Z13L-qIdke z?AlPZffqrHc`lq0T=)eQr}@B&vy?^YQ2wi4a`0 z_`WU4x}>)$iYgNZpe07n1}K99)0Rb4UE#?aJP9?2){Hzeh()&5W!Mdeoe0imRi}at zvBeW^FJ)3TJVFA|=IkWl_@F&%3;NzWu>W^h1A$+Y9RNHxfa3BY_mUcN7 zI4HhwR%2G%>)WrlU!MOs_t@-*&qfYI068W_z{lX&Y5}ceAUIgn>s8@Kf2x-Oek?| zN3IgWr_sHzCINQh%`=U-pNQP9RA)IiVOiSA*Ju^&=7dc)9>o%uy)LayuwayqYy736 zA%B5z@THc>5ImjDrQi$Eqk|Xot|pzud}`1>S`v9c`xUQ0XScEg44Q`v(Frb6^s3zv zM|J(Z&Ri>XCsk9r2d>7J5SSMbkQ=0@PYKA&4af%NDyg6bc>i`nlId1K(uLK`(6$O# zTUfvNQByc894W52lOZP^0v%4eG3UvAljEh5PB5~RU8R_^OGjq%wS4s~U34_jFOYSQ zPdr3RoiW5~>z!i9zcSzsePbqvp~^jC+>J}((w36v#0tLqvj_1z{ZTMa|HGwek}zJI zBiryCXYtazYvzY1Ep-DIZ}V{!M|jf4ZyjIZ$hdP;3-4!;iMkwSfpmQAns~0Wd$NSWmMuF+bbG|N|YR@dXy9&Du)<~Aa zsQ#W-=|N}`-@wQ-yqXUC>NLL1q{;{+lHuNC2?bLH>V#vN>H_>)Xx{hIpsL^2r4oUvbM|^orGJaEH~ZOkyh8P;8_SO ziLSOn^u-dR zvD-~Kb*#Q#Z?CJZ9bgRr9%M9@_$10DsZt`rs#Ief3nI}ruR)XnRx$xX7^tl^+w1D; z23Up66WIl22_D`iUr|Hwq-||IQ3Eq~3dU`4c(7QX{j^}@=?SU#8C z@Wl&XKQ6-BhA5($6)QHGl;rg~u{77~4VM}1+OE$#;QEgUZo zd)5RC>)!Ks)n<1ivx{j=Qc#zX>AtxNCZidcIOPT9xasYZTuPK_HkvZau=Z`LE7 z8`u;wFpx61;Cv^%^Zbe#AX0e1ZOwPa;~ODy!}d83T>vr5H&b9HolOnei1Rv9Q#aGxLBb{3ymyYq4L!{(i9&lK;B;$s0Ao-Q~62Qh7g9T*U9 zIPgve^GLkllM%8eX5l^qcvu*Bkj}kUpZEV|&u;}U7yZBn!u#Mm;Un-dr(@F_@Zh&~ zn>)5GK)&R<4;r?*bLVnBwgQQVp8DK7A9UuRG2f3Hqo4fg3@*UbHX z!wb7waNPTw@i&kR62a-SqT^vh`e*j*b}fBA9OLGFa0`&afJ)RLVgc4JUJ1h$*sIC^ zVSw_DOKj)Pp{OWxs;1v4d&4Yrp1Ul}y`4TmZ_lcD=ff<_VcRgp5!+Uw{Me@nhUVO|KHfd2KHPL&5vz}YlxliKL`NMFqMl=sdE{@ac~wctMtuU z^=1$E$Vhh&l}AswM|xP4d=B&Rm0HyL2`ltO#JYChv^3Av^&12py5^tSp;{v#CtZ44-82xQeChpC8M?zd^;9SO9S=6#_P%2XRo-zj zbugHr&zL>>ERp=@31cE;x8*)@v5kM~_T69fM=sf=a5;t|8k&Us*X z!8h>2)RJUp@YZ_@BSewO*h%Un)+A8Jeo_n8%lQ;5P@DAn$)ZSMgx=HOon+V2HPQ=3 zBdn~O*)Y(+l(~!bdU4VOHr-`;^;7>Wr83K3=HDNlrL3Cy0H(TC)f$UzOSzMhc-0Mg zuuiu~zzfo=;sxP6U0X`!A?`{sSH%^tGz@=Jz<{abDKc4#e1uv(q8d?1Q&Oa*6U;h_ z5iWg;VGEsWJ4J>qDvR%P!X}rcq{x6fOvTDv7nT<)VnkfDmXCL~ZLT7+dbDf&$IoT5 zobBK16Se;%(2iZr2!1}_WYSfm9UAbcd`WU)8L1$b?$}ZN{IoCaz_r5ih5RRR6M_X@ z(ZX=ovU02cDtnhl0*%WB}QOdmn*3 zF2-rMK6NK+d67|#g)z>Qi)7-l$PkU^$)x2f;}y%JFQX@DVcbU1Y$lG>NJI%CQDdhT z@)n&s8OTSI8c8^&EQT?|@_9znW)_A>$2 zT`nPEC$S@({C9YQH)G)Ne_A&rxde{+iM;{evj6BC!qteYgyP~Gu{|&y%oo@8jMVux zo-l%*H6YZcoa1CV^5|!ifgPu3FHwh7!u4%XC*w{IY2v4Z)1+gXw9F+Oc1=jNdJb%+ zeLyjLiGdlbXZG&yuhad*iV!T(v*QfxCP6C8$0sh%(8HzC0{ zH(|N}Usk&R#&EVU+dIa~E85#Ff*&`ez|YOwD>~Y1lW}H(P|w$C+;66c3nJXjWr@E! znI?5}5ed;tbRH7YbF#Eu@8a zdZ#*1sW2$8AV07%fE(VYOVh;vE~Q5bLxKtmgJuKBgb3f|OaDz1OAZBoD!e{W;H;n9 z#`Iigy&&)tI$Yx$!37fe)%4zTw0z|3s_jOL5pj!Z@|sBm&qomgcZ_W)qrg# z+Z{h&Y6n(MigXmr2SL8{Le@B|8%a6KKObNxG2NI;P(fjQy$o|PS{NBt?AJB-8dkY5 z<_NGutiad0Z2jb)C8loliAY3?4-Z5 zeg@^)6*?9|`q90;=zie0r4>>nO_n4jNm69v&{TP%q;N0vqC7>KwAXq8ma0gUSocB- z$Uh&EA(e8AEGsoAJ>(&+$TQ!gbC38Qw}fNe*mWbUf>uQPb(z@eqJ^ZB9{v2~@Y1CV zXbZaZ5_a$SvZWsL{}SVSu^_~D;0fyfgwj_gNsQpsu{z|b>Ix-f={>Z#(YAUmzH?-8 znEm$?%wu1@JO|v;-u3<1O`s3wV?2+s7Jj?;b`d@q|GZC*ecg=J8Xoux`IBW!{ELm^><2Y;Qj4HdAB4+nY`eadg^0U{gSqT zOuuCRUDv9s$UQg86K`aUC42w}a(v!JC{|ys9DA%ik|j8mrP_1&+PSj!q`1*pD?C@O zOcbwNG{TN9fXxLV7e;%{PoL*?PjbYNq1jj{bAeeaTVpldClCx-CiV z#UZImAitrq+l?v|6cVNF{>(JHan5OOg0tNP)EPmtG?j@Ql4f$43C#oOm7}+j%Cx(O zwp$AOS!0GZ^JobPwBj^_=i$DaNn;1&7f_9!N%AC5BXvRi!Lhnee{S5&d^wW?VLP@a z3axE|!s6Atb-oyKd56x4jE+dKE{+)>JLv`=o9SkttdT%o7tCFr2uXrKlRj^L!-R|t zf2+|6yg0h`?NlVl z129NG;ue5tffxn@Zwic~iQ}e3`Io(~+z@-B##G@zd9^2G1YiLlj+2Aalz(o6?<0U-}JY%du+lRetuyt?w#=|PG8 z2rjSkZQ)67M)zr${_|yk)A6r}P)U`l-Y%0!ENKRZ@CfI1ki7&g#Jreyi&x(55q(A1 zxHTLba+0le$uMhV&h?d^u*JF{GAnU9333S(mrO8rTaVM>)UyT^--) zZd^<#DQZdkJp3>}ch&yCQhWQp1yQo)C0aI=-84~1g*?GVLUB=-ZrWHdC~ghGDGw6q zl4Y*!44qTyDX;0J+icY>t3msr%&-kAO`EN}y+x%yo|%WbZqI6nn^j*wJ2r|Jmj)*9 zBGvHWz^;omo3S4QF51m$Fl*dLJVydYJp%0a1?;m2vQ>g&)w59eGJec+er&*+)l@7- z?C2K$^BDdz9`1#k6QhCAh^5o`8N7_`lp+N$<8pCD__#0Sgs7bB3)vTUI+LDx8b@J$ zOWh#2rgAA}9CJHw*@(7YIrBdLI1I89eF<|N{l@9K(GSr#EE$l|o+N8qQ=u)~_5dwK zPmi~@hv>yoghIl}p|gaE*Vm}*9Qcn+G@Mi4ZP3kzT%Wy!=2Uv4mNQ4?#`iXQdYUpk zjXoyBQoKAZ4I%z5s=w818GS%$r%45tV=0NofrU#g2=J85C zp41vAG5b>p$0Ha*WrkdlTB=C1Keyv zdp@{5;P>IpyIhhmYD|*LW0wLKb@)&r7r}$64txhuED?KEBVv8rMBI9W+HRfQ0F@8n z$st@hL8c|>5+Is*Xl@)V4jE%UW<5zkg$j2e=i61fm0Shm8!SmlStIt${@s*UZ8|vBgC- zG0R|{{~aalTVC^YIdRd2>9Kid~q)%KWPq04+3W&RDZh}d41EH9TTtKCOuw+3ZfpEJ zHyrlXY;&7ExI`|=nYLnzZ|9*H*M-UNJl-V>aVhOtOB9+CZHfE>&!9ZSQt3a;9RW7d zUB!7?PM$%5RZ_~xPt_dv*#>Ec%N%ES0Xqg+(8%AaM3ZSA(#5AcLW_tc#E${u`*nnz z4jP?uS8=^%?mjZ|w5(R#Y-mcs1q{yqiH|(sJyY6a9j_kPo6C=As!Q5sEj^At(U0koO#wAMMF`!UcwWP3aFBM>=pb$f}5F zsSTu&QJpF_C! zsX{n;9xAUMc!2P0^vZyO)8xcw>H2=k^Zip)Aie$F-+i7O`Cx-cymo$aA}$?-tN zi;a-c^IfZdRVtFXJ_)N;!5Q@H{2?`l`nzOb zJB^Fuk6u~oT+_&v1QRvdO{0gX^q`aeJ>^t=jGUj?By)uw{@z-v2`ch3nY@bYbdD*y z$fE%`<@qIfKKo2XUQb`BLpKj3X6a87h1>Kk0HE)-!(IMg^E2-fecJ<1*|d z?v&7dG|HgoR&Sr5q$1~EqVX}O!!gw3mLh8BHL=RcEhKqux(&iT>IlHkKNwcOBL5;k zE3?T<@AhFC6YFe}-KGV*gm7-SggFKCUbyuA;hpQALEp_TXY6JbvIE>gHrPM3zr+{y z`J!*BDz@(*kLsG^*eVE6Y@Wl~WPgX?eEt#!mW__~qhxs{rZZD{V)G@Q%|*{tI7TQ| zHFi^lUGs{D<~Xm=tw8BfD>#5KO)m&d2#y)B=Hdz*@?COh8*5575z>h{&e?xOSc*a# z@SBpI1~biRP~%X_oj$w5e0pqHbV%M_Tt2SoGD*#%bPQSvDanMb^C-~VE9x*xJr}at zhgZjgyz^7D@*;EPY@rY%9)z0PhJBl9nKnUjeV)p{~Q!t}pHWDSCg?);!DGnb<>+vP+Jma4@pM`?D&Q*zR|6Kt(n*G*qNT7U{XeLv#uCxg>@oSS&1*I1_GAmn<484mcv{+SYuGi=45@u^)J zUMnk-X&+HKyMJs>e!wAViSwP1b`BoS(H0tcd%|MpBx>Nz`Hs3-J7xtt9$;qE%?K0C z(+-hMrb%f#uIj5yTiulv`u9T`TgIUQSQBqx3w0Y0D9}s+kpO zImhoWxZj_iQ!eA!N2N5V*&5FJc(kclsSWlKit7LZAtjV_MDe2qsp&ByldxnlA*BsT z>vUb#Z`gdLnj*y%KcDrWjme(>4%k2p3< zQW2Bm{{LwQ&9)W41Q`*QPh`4ODx*{W%S*9a5&jp>A;!Oky2Pyu{IOL(`HmT#%rrir z|8EI%e#kb7Jea2GhZ?Vr$&OR&12b}Cs-t5uF%6=;a`(J?ce_LE8hfJZPoMD6e{NdE z`4#ae2}#ukTT>peMy@9>w<&wwCjPsO8C*!Hz50S;bCDD@6@tkZ(!^b#4>?A1t9^J3uEOeSQ=Tn?0JrifFyK=#hQd@S5Y=S zQ$f(|7BSmaX4g3%yzBLB zvI;13SA`qVZK-foA4Tbs9*L@|ndNMUNKzvY)^1Ms|IKQrwKKYHI9i;kTmt3{-d1cLS z!8xn~^oz4?^)pEh6o9HY9z@Scgu1X!2C@;x2^IH%2glc8a*AgUBPW`v1YBL@G03Ik z@*Vl)38%Ow&%6LgV{9GE#-W`dDULNkfUhAi)rXx4ftC+P+zonExZqVrjCRr(x&G}Z zcRXB8T^EQNOK?;nrfiqWkHO5?GpR>8tA)FP!|Oq8|1I8+w640LgkC48tu@Cqi}Qu9g5m57WhmS zIWP2FM*4F7R~mb=e;&=9mNYEZQme1}%sfVO7f%*Dk!o|j*};qXU>=1SAxUFTqWP7) zn5pF950M{Q!xgVj_qE%vF^_66n&igtK0q7NP!?NDJtM|ARWg62 zrpLpe`F8avSP{eTUR4>wiX!O;xZ(o%TxfL!@Utyez~!*e`Wy>|$>I3&0vr)H#sdpr zI{eoqmC`nZDG;WE`DeEfXhO6}msGM*01i{xmG8}!JpnbFW?BGp= zUs869qQkGNshVHG>+kS3`=sTU1z41sd0GtBj_Oh%fTND15&0bvC<3}QmsZ#`)9ILY z@8oA=UW2|k^Psv0csj6Gh%l$gyTjW&#bTXaC>EAh_WV|v7OxkIbYk~9z9uU+x)$U+ zShPwzWMLy*4}HcA+;n8vlYZsHOP(5m`n0uI0D+u1G3koGZF;+{q@byx^Q$NEF8&Om zVqz_|r8-_jeL$_nBgWxlE6-QCA4U+I=_tRmLTcG~r{SMM&7xVYl%n4)+bwJ1l2osK`T@y_u>?DExO5LuT5w{Qv0K&q+@P3RGg`nblIg;klW z!piQ@KBJ=E(1dhqay3_n8VwW8h&GpcHx(%ZoCaZ|C>b}U8kA0hUW7Rx1&AcD%e)H& zJsH9j49w8slTK_YL?1_`q6g47XJlK^CAfnUI7*kPa~t(?`|Rd=%}wW_-M_j7F|o$C z_H(9n-UAZOKB5#ko`3fv-4E{YIGd9P3lvvAOik8M6rmxDixL%UvufT@Ks!LV#`j@g z<^b+(4&Yp7;aje8@hZ3=Tmcusu=C&gXf6ZlLH<_xLVc>Ch^l1Q)g zEt+)eA@;u6zN%e4(T=V2|I=?{o1{L{V?BnurOVGn(Njw-hgD-6rp8nZ05r4Dr}9jWb(#~z;u#(4D$pl#>|R7 z5yMIXJW{k2Q_U2Yh_ynYHUZ%NggWF}|D(ljan16amX;hj@?W>&qprn*mmgP4i#(?) zuGrP`GD~F~;d+K!G4<6Un~|%a6m;HOHOWzFwQBCefZlTkD#c)-M34>VdIKg^9IckC zez@{}X93kfu|!bF1#{l6dc-9jr{ccvp@0oY{3d>{;+~>|t$yW-9L3%boyuA&Cva(vZ*Nqc(We>`OfNb)|D1rzp#yKjVp4HTWDw!e2W=!vd6 z;6v2Z|AnP-C=Z>YUw$^#aob0jQ}v@m<9^D_Ol75j`z!0^cmKPrJJmuh+uH&5mN~|y zmaW&+Rj|lRGOMEQ-fW9nf+RiD>1iB_a5bE(isqsx5#gfMa1Ko&e?~$|ENZqLKabty zBjL-&&g0vGd++ewvNLyFUL7`<{le(x^t<)T%A3k?_(j^ouNeKLi{ zrRiEREHhV$_PjLq}RUZn3}USJS~5dj<4p;Xf|cDx6wGoJmW{cnE%npEA~}~I~!&d zHS|mcT*d~uT=sudtyvK*S0rahD+Vwx2ey)XCf=76FAnaipa4w#27$zF78jY7XJ&cK zTA+q+b5I?`S6nfFiEq25@GxG6gjwgHH8E44ZxfE2XB7$B3OUQsln%{&$ohrWz25P- zA$IcImDTDaIzk*d!QP0~@Soi-_bDON6r;57Thsr>_a(KjR8?)Spz9T8`Uu^u(9;16 zW^RI%hn56|6sgKUQ-0k3_IwYjgaA~uCdvlbea-2 zPZif6HcA1V1Y-LYkT^USGfjT4Wt({v_PlB9wmNZdC}kXY`qs>GO+LfJa6)x+7~ zIqRX829sW-l#ljZhm;IpQ@Qr=n*B0X)8cz%6_&ILY%EPb?S2|_Q z)T7oQGX>3Z3jT}Hp+{YZ2m9{sJijHv=6 zeE~MLW#@{%{&Q8=4>nWpS?uV&b8eXQ3MYDhoZIvog=^U?_yy^;fd)9lXDG?`tZRn+ zZojZhp|-5}ht>;I;e0qPZurEG79ybEmdhA9 zFRu+Wz#%@x)l0HH*BwezvS8KF7y&*gzGRkO*j`{hG zvG|0aRw3r!>Y=H>+1z19W>$8NB{vWF^is;Uzl);NTP_)D#f{_K;}~SFbiyOeq?x^< zWya}JX~JZh$UKyapxRi3fg5E|I~b(s;>b7ew08z?e87%>0~dN@gTqX4u@l=%gALx* zh#n6#D~SaAN{xS@PpwYM#l1wDt6D>% zW|yLrIZEl~<8B_Dgw6OWowz~01MjU(+=8cdl(gU3!l#B!)mnE2+r)w{doFsdv!rED zz}V@qC);#!X2fHw%}#xeafR-eyQgNI-#)S_8DqnX+5{0Iu-eK%nRa6NoG^~No0V%_ zo=Qu?zU7=@4Af7A&yoXqi3{G)qKnT=eI9E(?&Q?_Yx6ni&>u$IdoA>bp0S0-N diff --git a/web/src/components/UserOrder.vue b/web/src/components/UserOrder.vue index dd8a6c59..13432f27 100644 --- a/web/src/components/UserOrder.vue +++ b/web/src/components/UserOrder.vue @@ -1,5 +1,5 @@ From 9edb3d0a82e7610688235681f351678608eaeb4e Mon Sep 17 00:00:00 2001 From: RockYang Date: Fri, 27 Sep 2024 18:28:54 +0800 Subject: [PATCH 28/41] sd websocket refactor is finished --- api/core/types/task.go | 2 + api/handler/sd_handler.go | 14 +++---- api/service/sd/service.go | 19 ++++------ api/service/types.go | 7 ++-- web/src/App.vue | 10 ++--- web/src/views/ImageSd.vue | 67 ++++++++++------------------------ web/src/views/admin/ApiKey.vue | 2 +- 7 files changed, 45 insertions(+), 76 deletions(-) diff --git a/api/core/types/task.go b/api/core/types/task.go index d41e592a..6ec62167 100644 --- a/api/core/types/task.go +++ b/api/core/types/task.go @@ -43,12 +43,14 @@ type MjTask struct { type SdTask struct { Id int `json:"id"` // job 数据库ID Type TaskType `json:"type"` + ClientId string `json:"client_id"` UserId int `json:"user_id"` Params SdTaskParams `json:"params"` RetryCount int `json:"retry_count"` } type SdTaskParams struct { + ClientId string `json:"client_id"` // 客户端ID TaskId string `json:"task_id"` Prompt string `json:"prompt"` // 提示词 NegPrompt string `json:"neg_prompt"` // 反向提示词 diff --git a/api/handler/sd_handler.go b/api/handler/sd_handler.go index 2f658e73..0d2ba6ea 100644 --- a/api/handler/sd_handler.go +++ b/api/handler/sd_handler.go @@ -144,17 +144,13 @@ func (h *SdJobHandler) Image(c *gin.Context) { } h.sdService.PushTask(types.SdTask{ - Id: int(job.Id), - Type: types.TaskImage, - Params: params, - UserId: userId, + Id: int(job.Id), + ClientId: data.ClientId, + Type: types.TaskImage, + Params: params, + UserId: userId, }) - client := h.sdService.Clients.Get(uint(job.UserId)) - if client != nil { - _ = client.Send([]byte("Task Updated")) - } - // update user's power err = h.userService.DecreasePower(job.UserId, job.Power, model.PowerLog{ Type: types.PowerConsume, diff --git a/api/service/sd/service.go b/api/service/sd/service.go index a6c9a856..9aa25c2a 100644 --- a/api/service/sd/service.go +++ b/api/service/sd/service.go @@ -34,17 +34,17 @@ type Service struct { db *gorm.DB uploadManager *oss.UploaderManager leveldb *store.LevelDB - Clients *types.LMap[uint, *types.WsClient] // UserId => Client + wsService *service.WebsocketService } -func NewService(db *gorm.DB, manager *oss.UploaderManager, levelDB *store.LevelDB, redisCli *redis.Client) *Service { +func NewService(db *gorm.DB, manager *oss.UploaderManager, levelDB *store.LevelDB, redisCli *redis.Client, wsService *service.WebsocketService) *Service { return &Service{ httpClient: req.C(), taskQueue: store.NewRedisQueue("StableDiffusion_Task_Queue", redisCli), notifyQueue: store.NewRedisQueue("StableDiffusion_Queue", redisCli), db: db, leveldb: levelDB, - Clients: types.NewLMap[uint, *types.WsClient](), + wsService: wsService, uploadManager: manager, } } @@ -90,7 +90,7 @@ func (s *Service) Run() { "err_msg": err.Error(), }) // 通知前端,任务失败 - s.notifyQueue.RPush(service.NotifyMessage{UserId: task.UserId, JobId: task.Id, Message: service.TaskStatusFailed}) + s.notifyQueue.RPush(service.NotifyMessage{ClientId: task.ClientId, UserId: task.UserId, JobId: task.Id, Message: service.TaskStatusFailed}) continue } } @@ -213,7 +213,7 @@ func (s *Service) Txt2Img(task types.SdTask) error { // task finished s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumn("progress", 100) - s.notifyQueue.RPush(service.NotifyMessage{UserId: task.UserId, JobId: task.Id, Message: service.TaskStatusFinished}) + s.notifyQueue.RPush(service.NotifyMessage{ClientId: task.ClientId, UserId: task.UserId, JobId: task.Id, Message: service.TaskStatusFinished}) // 从 leveldb 中删除预览图片数据 _ = s.leveldb.Delete(task.Params.TaskId) return nil @@ -223,7 +223,7 @@ func (s *Service) Txt2Img(task types.SdTask) error { if err == nil && resp.Progress > 0 { s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumn("progress", int(resp.Progress*100)) // 发送更新状态信号 - s.notifyQueue.RPush(service.NotifyMessage{UserId: task.UserId, JobId: task.Id, Message: service.TaskStatusRunning}) + s.notifyQueue.RPush(service.NotifyMessage{ClientId: task.ClientId, UserId: task.UserId, JobId: task.Id, Message: service.TaskStatusRunning}) // 保存预览图片数据 if resp.CurrentImage != "" { _ = s.leveldb.Put(task.Params.TaskId, resp.CurrentImage) @@ -267,14 +267,11 @@ func (s *Service) CheckTaskNotify() { if err != nil { continue } - client := s.Clients.Get(uint(message.UserId)) + client := s.wsService.Clients.Get(message.ClientId) if client == nil { continue } - err = client.Send([]byte(message.Message)) - if err != nil { - continue - } + utils.SendChannelMsg(client, types.ChSd, message.Message) } }() } diff --git a/api/service/types.go b/api/service/types.go index 70f8eb92..1c5c601e 100644 --- a/api/service/types.go +++ b/api/service/types.go @@ -8,9 +8,10 @@ const ( ) type NotifyMessage struct { - UserId int `json:"user_id"` - JobId int `json:"job_id"` - Message string `json:"message"` + UserId int `json:"user_id"` + ClientId string `json:"client_id"` + JobId int `json:"job_id"` + Message string `json:"message"` } const RewritePromptTemplate = "Please rewrite the following text into AI painting prompt words, and please try to add detailed description of the picture, painting style, scene, rendering effect, picture light and other creative elements. Just output the final prompt word directly. Do not output any explanation lines. The text to be rewritten is: [%s]" diff --git a/web/src/App.vue b/web/src/App.vue index d999a96e..bfb50e0c 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -71,12 +71,12 @@ const connect = () => { handler.value = setInterval(() => { _socket.send(JSON.stringify({"type":"ping"})) },5000) + }) - for (const key in store.messageHandlers) { - console.log(key, store.messageHandlers[key]) - store.setMessageHandler(store.messageHandlers[key]) - } - }); + for (const key in store.messageHandlers) { + console.log(key, store.messageHandlers[key]) + store.setMessageHandler(store.messageHandlers[key]) + } _socket.addEventListener('close', () => { store.setSocket(null) diff --git a/web/src/views/ImageSd.vue b/web/src/views/ImageSd.vue index 2b377022..a9309c93 100644 --- a/web/src/views/ImageSd.vue +++ b/web/src/views/ImageSd.vue @@ -487,11 +487,11 @@ diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index 979939c9..ff2e7fa4 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -125,15 +125,14 @@ diff --git a/web/src/views/mobile/Profile.vue b/web/src/views/mobile/Profile.vue index 630516b5..40650fbe 100644 --- a/web/src/views/mobile/Profile.vue +++ b/web/src/views/mobile/Profile.vue @@ -125,7 +125,13 @@ + + + + @@ -158,9 +164,7 @@ import {ElMessage} from "element-plus"; import {checkSession, getSystemInfo} from "@/store/cache"; import {useRouter} from "vue-router"; import {removeUserToken} from "@/store/session"; -import bus from '@/store/eventbus' -import {getMobileTheme} from "@/store/system"; -import QRCode from "qrcode"; +import {useSharedStore} from "@/store/sharedata"; const form = ref({ username: 'GeekMaster', @@ -183,6 +187,9 @@ const router = useRouter() const userId = ref(0) const isLogin = ref(false) const showSettings = ref(false) +const store = useSharedStore() +const stream = ref(store.chatStream) +const dark = ref(store.mobileTheme === 'dark') onMounted(() => { checkSession().then(user => { @@ -322,12 +329,6 @@ const logout = function () { }) } -const dark = ref(getMobileTheme() === 'dark') - -const changeTheme = () => { - bus.emit('changeTheme', dark.value ? 'dark' : 'light') -} -