From bddd611cc1e068fe8ddd79c96aa0238cb1cf318a Mon Sep 17 00:00:00 2001 From: RockYang Date: Thu, 4 Jul 2024 15:34:32 +0800 Subject: [PATCH] wechat login is ready --- CHANGELOG.md | 2 +- api/handler/mj_handler.go | 11 +-- api/handler/user_handler.go | 94 ++++++++++++++++++++++- database/update-v4.1.0.sql | 5 +- web/src/assets/css/login.styl | 115 +++++++++++++++++++++++++++ web/src/components/ChatReply.vue | 1 + web/src/components/TaskList.vue | 58 ++++++++++++++ web/src/router.js | 6 ++ web/src/views/ChatPlus.vue | 4 +- web/src/views/Dalle.vue | 39 +--------- web/src/views/Home.vue | 9 ++- web/src/views/ImageMj.vue | 41 +--------- web/src/views/ImageSd.vue | 36 +-------- web/src/views/Login.vue | 128 ++++--------------------------- web/src/views/LoginCallback.vue | 105 +++++++++++++++++++++++++ 15 files changed, 416 insertions(+), 238 deletions(-) create mode 100644 web/src/assets/css/login.styl create mode 100644 web/src/components/TaskList.vue create mode 100644 web/src/views/LoginCallback.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index d6ba6a56..20da1320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * 功能新增:**支持AI解读 PDF, Word, Excel等文件** * 功能优化:优化聊天界面的用户上传文件的列表样式 * 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换 -* 功能新增:支持微信等社交媒体登录 +* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。 ## v4.0.9 diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 7b8d7ca0..7f6f6b7d 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -446,14 +446,9 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize } if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" { - // discord 服务器图片需要使用代理转发图片数据流 - if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") { - image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL) - if err == nil { - job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image) - } - } else { - job.ImgURL = job.OrgURL + image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL) + if err == nil { + job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image) } } diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index deae433d..c765a100 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -285,9 +285,99 @@ func (h *UserHandler) CLoginRequest(c *gin.Context) { // CLoginCallback 第三方登录回调 func (h *UserHandler) CLoginCallback(c *gin.Context) { - //platform := h.GetTrim(c, "type") - //code := h.GetTrim(c, "code") + loginType := h.GetTrim(c, "login_type") + code := h.GetTrim(c, "code") + var res types.BizVo + apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL) + r, err := req.C().R().SetBody(gin.H{"login_type": loginType, "code": code}). + SetHeader("AppId", h.App.Config.ApiConfig.AppId). + SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)). + SetSuccessResult(&res). + Post(apiURL) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + if r.IsErrorState() { + resp.ERROR(c, "error with login http status: "+r.Status) + return + } + + if res.Code != types.Success { + resp.ERROR(c, "error with http response: "+res.Message) + return + } + + // login successfully + data := res.Data.(map[string]interface{}) + session := gin.H{} + var user model.User + tx := h.DB.Debug().Where("openid", data["openid"]).First(&user) + if tx.Error != nil { // user not exist, create new user + // 检测最大注册人数 + var totalUser int64 + h.DB.Model(&model.User{}).Count(&totalUser) + if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum { + resp.ERROR(c, "当前注册用户数已达上限,请请升级 License") + return + } + + salt := utils.RandString(8) + password := fmt.Sprintf("%d", utils.RandomNumber(8)) + user = model.User{ + Username: fmt.Sprintf("%s@%d", loginType, utils.RandomNumber(10)), + Password: utils.GenPassword(password, salt), + Avatar: fmt.Sprintf("%s", data["avatar"]), + Salt: salt, + Status: true, + ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色 + ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型 + Power: h.App.SysConfig.InitPower, + OpenId: fmt.Sprintf("%s", data["openid"]), + Nickname: fmt.Sprintf("%s", data["nickname"]), + } + + tx = h.DB.Create(&user) + if tx.Error != nil { + resp.ERROR(c, "保存数据失败") + logger.Error(tx.Error) + return + } + session["username"] = user.Username + session["password"] = password + } else { // login directly + // 更新最后登录时间和IP + user.LastLoginIp = c.ClientIP() + user.LastLoginAt = time.Now().Unix() + h.DB.Model(&user).Updates(user) + + h.DB.Create(&model.UserLoginLog{ + UserId: user.Id, + Username: user.Username, + LoginIp: c.ClientIP(), + LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()), + }) + } + + // 创建 token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "user_id": user.Id, + "expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(), + }) + tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey)) + if err != nil { + resp.ERROR(c, "Failed to generate token, "+err.Error()) + return + } + // 保存到 redis + key := fmt.Sprintf("users/%d", user.Id) + if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil { + resp.ERROR(c, "error with save token: "+err.Error()) + return + } + session["token"] = tokenString + resp.SUCCESS(c, session) } // Session 获取/验证会话 diff --git a/database/update-v4.1.0.sql b/database/update-v4.1.0.sql index 61ed1a8a..37d36e92 100644 --- a/database/update-v4.1.0.sql +++ b/database/update-v4.1.0.sql @@ -1,3 +1,6 @@ ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数'; ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`; -ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`; \ No newline at end of file +ALTER TABLE `chatgpt_users` ADD UNIQUE(`openid`); +ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`; +ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像'; +ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标'; \ No newline at end of file diff --git a/web/src/assets/css/login.styl b/web/src/assets/css/login.styl new file mode 100644 index 00000000..bf8b6c49 --- /dev/null +++ b/web/src/assets/css/login.styl @@ -0,0 +1,115 @@ +.bg { + position fixed + left 0 + right 0 + top 0 + bottom 0 + background-color #313237 + background-image url("~@/assets/img/login-bg.jpg") + background-size cover + background-position center + background-repeat repeat-y + //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */ +} + +.main { + .contain { + position fixed + left 50% + top 40% + width 90% + max-width 400px; + transform translate(-50%, -50%) + padding 20px 10px; + color #ffffff + border-radius 10px; + + .logo { + text-align center + + .el-image { + width 120px; + cursor pointer + } + } + + .header { + width 100% + margin-bottom 24px + font-size 24px + color $white_v1 + letter-space 2px + text-align center + padding-top 10px + } + + .content { + width 100% + height: auto + border-radius 3px + + .block { + margin-bottom 16px + + .el-input__inner { + border 1px solid $gray-v6 !important + + .el-icon-user, .el-icon-lock { + font-size 20px + } + } + } + + .btn-row { + padding-top 10px; + + .login-btn { + width 100% + font-size 16px + letter-spacing 2px + } + } + + .text-line { + justify-content center + padding-top 10px; + font-size 14px; + } + + .opt { + padding 15px + .el-col { + text-align center + } + } + + .divider { + border-top: 2px solid #c1c1c1; + } + + .clogin { + padding 15px + display flex + justify-content center + + .iconfont { + font-size 20px + background: #E9F1F6; + padding: 8px; + border-radius: 50%; + } + .iconfont.icon-wechat { + color #0bc15f + } + } + } + } + + .footer { + color #ffffff; + + .container { + padding 20px; + } + } +} \ No newline at end of file diff --git a/web/src/components/ChatReply.vue b/web/src/components/ChatReply.vue index 033fefe5..6e64dbb0 100644 --- a/web/src/components/ChatReply.vue +++ b/web/src/components/ChatReply.vue @@ -155,6 +155,7 @@ const synthesis = (text) => { // 重新生成 const reGenerate = (prompt) => { + console.log(prompt) emits('regen', prompt) } diff --git a/web/src/components/TaskList.vue b/web/src/components/TaskList.vue new file mode 100644 index 00000000..d2e63e0d --- /dev/null +++ b/web/src/components/TaskList.vue @@ -0,0 +1,58 @@ + + + + + \ No newline at end of file diff --git a/web/src/router.js b/web/src/router.js index de19cfff..4b534968 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -101,6 +101,12 @@ const routes = [ meta: {title: '用户登录'}, component: () => import('@/views/Login.vue'), }, + { + name: 'login-callback', + path: '/login/callback', + meta: {title: '用户登录'}, + component: () => import('@/views/LoginCallback.vue'), + }, { name: 'register', path: '/register', diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index c334922c..d452ffc5 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -626,10 +626,12 @@ const connect = function (chat_id, role_id) { reader.onload = () => { const data = JSON.parse(String(reader.result)); if (data.type === 'start') { + const prePrompt = chatData.value[chatData.value.length-1].content chatData.value.push({ type: "reply", id: randString(32), icon: _role['icon'], + prompt:prePrompt, content: "" }); } else if (data.type === 'end') { // 消息接收完毕 @@ -864,7 +866,7 @@ const reGenerate = function (prompt) { type: "prompt", id: randString(32), icon: loginUser.value.avatar, - content: md.render(text) + content: text }); socket.value.send(JSON.stringify({type: "chat", content: prompt})); } diff --git a/web/src/views/Dalle.vue b/web/src/views/Dalle.vue index 1e151b7d..a89053c4 100644 --- a/web/src/views/Dalle.vue +++ b/web/src/views/Dalle.vue @@ -86,43 +86,7 @@

任务列表

-
-
-
-
- - - - - - -
- -
-
- - - -
-
- -
+

创作记录

@@ -228,6 +192,7 @@ import {ElMessage, ElMessageBox, ElNotification} from "element-plus"; import Clipboard from "clipboard"; import {checkSession} from "@/action/session"; import {useSharedStore} from "@/store/sharedata"; +import TaskList from "@/components/TaskList.vue"; const listBoxHeight = ref(0) // const paramBoxHeight = ref(0) diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index f90ebaef..eef14830 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -215,10 +215,11 @@ const init = () => { const logout = function () { httpGet('/api/user/logout').then(() => { removeUserToken() - store.setShowLoginDialog(true) - loginUser.value = {} - // 刷新组件 - routerViewKey.value += 1 + router.push("/login") + // store.setShowLoginDialog(true) + // loginUser.value = {} + // // 刷新组件 + // routerViewKey.value += 1 }).catch(() => { ElMessage.error('注销失败!'); }) diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index 2a5555b3..78d9f3c1 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -163,7 +163,7 @@
-
+
@@ -450,43 +450,7 @@

任务列表

-
-
-
-
- - - - - - -
- -
-
- - - -
-
- -
+

创作记录

@@ -617,6 +581,7 @@ import {useRouter} from "vue-router"; import {getSessionId} from "@/store/session"; import {copyObj, removeArrayItem} from "@/utils/libs"; import {useSharedStore} from "@/store/sharedata"; +import TaskList from "@/components/TaskList.vue"; const listBoxHeight = ref(0) const paramBoxHeight = ref(0) diff --git a/web/src/views/ImageSd.vue b/web/src/views/ImageSd.vue index 2706b7cf..d1a75891 100644 --- a/web/src/views/ImageSd.vue +++ b/web/src/views/ImageSd.vue @@ -296,39 +296,8 @@

任务列表

-
-
-
-
- - - - - - -
- -
-
- - - -
-
- -
+ +

创作记录

@@ -506,6 +475,7 @@ import {checkSession} from "@/action/session"; import {useRouter} from "vue-router"; import {getSessionId} from "@/store/session"; import {useSharedStore} from "@/store/sharedata"; +import TaskList from "@/components/TaskList.vue"; const listBoxHeight = ref(0) // const paramBoxHeight = ref(0) diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index ee8a0332..893c57a5 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -35,19 +35,22 @@ - - - - - - 注册 - + 注册 + 重置密码 - + 首页 + +
+ 其他登录方式 + +
+ +
+
@@ -80,8 +83,7 @@ const password = ref(process.env.VUE_APP_PASS); const showResetPass = ref(false) const logo = ref("/images/logo.png") const licenseConfig = ref({}) -const cLoginURL = ref('') -const span = ref(8) +const wechatLoginURL = ref('') onMounted(() => { // 获取系统配置 @@ -94,7 +96,6 @@ onMounted(() => { httpGet("/api/config/license").then(res => { licenseConfig.value = res.data - span.value = 6 }).catch(e => { showMessageError("获取 License 配置:" + e.message) }) @@ -108,10 +109,9 @@ onMounted(() => { }).catch(() => { }) - // const returnURL = `${location.protocol}//${location.host}/user/api/clogin/callback` - const returnURL = `https://ai.r9it.com/user/api/clogin/callback` + const returnURL = `${location.protocol}//${location.host}/login/callback` httpGet("/api/user/clogin/request?return_url="+returnURL).then(res => { - cLoginURL.value = res.data.url + wechatLoginURL.value = res.data.url }).catch(e => { console.error(e) }) @@ -147,103 +147,5 @@ const login = function () { \ No newline at end of file diff --git a/web/src/views/LoginCallback.vue b/web/src/views/LoginCallback.vue new file mode 100644 index 00000000..1b9110f8 --- /dev/null +++ b/web/src/views/LoginCallback.vue @@ -0,0 +1,105 @@ + + + + +