diff --git a/api/core/types/config.go b/api/core/types/config.go index 0d2b0073..10edddbb 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -94,7 +94,6 @@ type BaseConfig struct { DefaultNickname string `json:"default_nickname"` // 默认昵称 ICP string `json:"icp"` // ICP 备案号 - EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码 EmailWhiteList []string `json:"email_white_list"` // 邮箱白名单列表 AssistantModelId int `json:"assistant_model_id"` // 用来做提示词,翻译的AI模型 id MaxFileSize int `json:"max_file_size"` // 最大文件大小,单位:MB @@ -118,6 +117,7 @@ const ( ConfigKeyNotice = "notice" ConfigKeyAgreement = "agreement" ConfigKeyPrivacy = "privacy" + ConfigKeyMarkMap = "mark_map" ConfigKeyCaptcha = "captcha" ConfigKeyWxLogin = "wx_login" ConfigKeyLicense = "license" diff --git a/api/handler/admin/admin_handler.go b/api/handler/admin/admin_handler.go index 51dcdec9..6cc60e9d 100644 --- a/api/handler/admin/admin_handler.go +++ b/api/handler/admin/admin_handler.go @@ -81,18 +81,18 @@ func (h *ManagerHandler) Login(c *gin.Context) { return } - if h.App.SysConfig.Base.EnabledVerify { - var check bool - if data.X != 0 { - check = h.captcha.SlideCheck(data) - } else { - check = h.captcha.Check(data) - } - if !check { - resp.ERROR(c, "请先完人机验证") - return - } - } + // if h.App.SysConfig.Base.EnabledVerify { + // var check bool + // if data.X != 0 { + // check = h.captcha.SlideCheck(data) + // } else { + // check = h.captcha.Check(data) + // } + // if !check { + // resp.ERROR(c, "请先完人机验证") + // return + // } + // } var manager model.AdminUser res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager) diff --git a/api/handler/admin/config_handler.go b/api/handler/admin/config_handler.go index 158cdd5f..baa6daab 100644 --- a/api/handler/admin/config_handler.go +++ b/api/handler/admin/config_handler.go @@ -92,6 +92,9 @@ func (h *ConfigHandler) RegisterRoutes() { rg.POST("update/base", h.UpdateBase) rg.POST("update/power", h.UpdatePower) rg.POST("update/notice", h.UpdateNotice) + rg.POST("update/agreement", h.UpdateAgreement) + rg.POST("update/privacy", h.UpdatePrivacy) + rg.POST("update/mark_map", h.UpdateMarkMap) rg.POST("update/captcha", h.UpdateCaptcha) rg.POST("update/wx_login", h.UpdateWxLogin) rg.POST("update/payment", h.UpdatePayment) @@ -193,6 +196,64 @@ func (h *ConfigHandler) UpdateNotice(c *gin.Context) { resp.SUCCESS(c, data) } +// UpdateAgreement 更新用户协议配置 +func (h *ConfigHandler) UpdateAgreement(c *gin.Context) { + var data struct { + Content string `json:"content"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + err := h.Update(types.ConfigKeyAgreement, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + resp.SUCCESS(c, data) +} + +// UpdatePrivacy 更新隐私政策配置 +func (h *ConfigHandler) UpdatePrivacy(c *gin.Context) { + var data struct { + Content string `json:"content"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + err := h.Update(types.ConfigKeyPrivacy, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + resp.SUCCESS(c, data) +} + +// UpdateMarkMap 更新思维导图配置 +func (h *ConfigHandler) UpdateMarkMap(c *gin.Context) { + var data struct { + Content string `json:"content"` + } + + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + err := h.Update(types.ConfigKeyMarkMap, data) + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + resp.SUCCESS(c, data) +} + // UpdateCaptcha 更新行为验证码配置 func (h *ConfigHandler) UpdateCaptcha(c *gin.Context) { var data types.CaptchaConfig @@ -206,10 +267,7 @@ func (h *ConfigHandler) UpdateCaptcha(c *gin.Context) { resp.ERROR(c, err.Error()) return } - if data.Enabled { - h.captchaService.UpdateConfig(data) - } - h.sysConfig.Captcha = data + h.captchaService.UpdateConfig(data) resp.SUCCESS(c, data) } diff --git a/api/handler/captcha_handler.go b/api/handler/captcha_handler.go index be4952d4..b63dca79 100644 --- a/api/handler/captcha_handler.go +++ b/api/handler/captcha_handler.go @@ -16,16 +16,13 @@ import ( "github.com/gin-gonic/gin" ) -// 今日头条函数实现 - type CaptchaHandler struct { App *core.AppServer service *service.CaptchaService - config types.CaptchaConfig } func NewCaptchaHandler(app *core.AppServer, s *service.CaptchaService, sysConfig *types.SystemConfig) *CaptchaHandler { - return &CaptchaHandler{App: app, service: s, config: sysConfig.Captcha} + return &CaptchaHandler{App: app, service: s} } // RegisterRoutes 注册路由 @@ -37,10 +34,15 @@ func (h *CaptchaHandler) RegisterRoutes() { group.POST("check", h.Check) group.GET("slide/get", h.SlideGet) group.POST("slide/check", h.SlideCheck) + group.GET("config", h.GetConfig) +} + +func (h *CaptchaHandler) GetConfig(c *gin.Context) { + resp.SUCCESS(c, gin.H{"enabled": h.service.GetConfig().Enabled, "type": h.service.GetConfig().Type}) } func (h *CaptchaHandler) Get(c *gin.Context) { - if !h.config.Enabled { + if !h.service.GetConfig().Enabled { resp.ERROR(c, "验证码服务未启用") return } @@ -56,7 +58,7 @@ func (h *CaptchaHandler) Get(c *gin.Context) { // Check verify the captcha data func (h *CaptchaHandler) Check(c *gin.Context) { - if !h.config.Enabled { + if !h.service.GetConfig().Enabled { resp.ERROR(c, "验证码服务未启用") return } @@ -80,7 +82,7 @@ func (h *CaptchaHandler) Check(c *gin.Context) { // SlideGet 获取滑动验证图片 func (h *CaptchaHandler) SlideGet(c *gin.Context) { - if !h.config.Enabled { + if !h.service.GetConfig().Enabled { resp.ERROR(c, "验证码服务未启用") return } @@ -96,7 +98,7 @@ func (h *CaptchaHandler) SlideGet(c *gin.Context) { // SlideCheck 滑动验证结果校验 func (h *CaptchaHandler) SlideCheck(c *gin.Context) { - if !h.config.Enabled { + if !h.service.GetConfig().Enabled { resp.ERROR(c, "验证码服务未启用") return } diff --git a/api/handler/chat_model_handler.go b/api/handler/chat_model_handler.go index 465c687e..ff9ca79b 100644 --- a/api/handler/chat_model_handler.go +++ b/api/handler/chat_model_handler.go @@ -9,7 +9,6 @@ package handler import ( "geekai/core" - "geekai/core/middleware" "geekai/store/model" "geekai/store/vo" "geekai/utils" @@ -30,12 +29,7 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler { // RegisterRoutes 注册路由 func (h *ChatModelHandler) RegisterRoutes() { group := h.App.Engine.Group("/api/model/") - - // 需要用户授权的接口 - group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis)) - { - group.GET("list", h.List) - } + group.GET("list", h.List) } // List 模型列表 diff --git a/api/handler/sms_handler.go b/api/handler/sms_handler.go index 1075a276..ae4fb3d3 100644 --- a/api/handler/sms_handler.go +++ b/api/handler/sms_handler.go @@ -24,10 +24,10 @@ const CodeStorePrefix = "/verify/codes/" type SmsHandler struct { BaseHandler - redis *redis.Client - sms *sms.SmsManager - smtp *service.SmtpService - captcha *service.CaptchaService + redis *redis.Client + sms *sms.SmsManager + smtp *service.SmtpService + captchaService *service.CaptchaService } func NewSmsHandler( @@ -37,11 +37,11 @@ func NewSmsHandler( smtp *service.SmtpService, captcha *service.CaptchaService) *SmsHandler { return &SmsHandler{ - redis: client, - sms: sms, - captcha: captcha, - smtp: smtp, - BaseHandler: BaseHandler{App: app}} + redis: client, + sms: sms, + captchaService: captcha, + smtp: smtp, + BaseHandler: BaseHandler{App: app}} } // RegisterRoutes 注册路由 @@ -63,12 +63,12 @@ func (h *SmsHandler) SendCode(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - if h.App.SysConfig.Base.EnabledVerify { + if h.captchaService.GetConfig().Enabled { var check bool if data.X != 0 { - check = h.captcha.SlideCheck(data) + check = h.captchaService.SlideCheck(data) } else { - check = h.captcha.Check(data) + check = h.captchaService.Check(data) } if !check { resp.ERROR(c, "请先完人机验证") diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 0997ccc7..33e34397 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -35,7 +35,7 @@ type UserHandler struct { redis *redis.Client levelDB *store.LevelDB licenseService *service.LicenseService - captcha *service.CaptchaService + captchaService *service.CaptchaService userService *service.UserService } @@ -53,7 +53,7 @@ func NewUserHandler( searcher: searcher, redis: client, levelDB: levelDB, - captcha: captcha, + captchaService: captcha, licenseService: licenseService, userService: userService, } @@ -104,12 +104,13 @@ func (h *UserHandler) Register(c *gin.Context) { return } - if h.App.SysConfig.Base.EnabledVerify && data.RegWay == "username" { + // 如果注册方式不是账号密码,则需要验证码 + if h.captchaService.GetConfig().Enabled && data.RegWay != "username" { var check bool if data.X != 0 { - check = h.captcha.SlideCheck(data) + check = h.captchaService.SlideCheck(data) } else { - check = h.captcha.Check(data) + check = h.captchaService.Check(data) } if !check { resp.ERROR(c, "请先完人机验证") @@ -279,15 +280,12 @@ 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.Base.EnabledVerify && needVerify { + if h.captchaService.GetConfig().Enabled { var check bool if data.X != 0 { - check = h.captcha.SlideCheck(data) + check = h.captchaService.SlideCheck(data) } else { - check = h.captcha.Check(data) + check = h.captchaService.Check(data) } if !check { resp.ERROR(c, "请先完人机验证") @@ -298,19 +296,17 @@ 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 } - if user.Status == false { + if !user.Status { resp.ERROR(c, "该用户已被禁止登录,请联系管理员") return } @@ -343,8 +339,6 @@ func (h *UserHandler) Login(c *gin.Context) { 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/api/service/captcha_service.go b/api/service/captcha_service.go index 0cc5e503..998bcac3 100644 --- a/api/service/captcha_service.go +++ b/api/service/captcha_service.go @@ -31,6 +31,10 @@ func (s *CaptchaService) UpdateConfig(config types.CaptchaConfig) { s.config = config } +func (s *CaptchaService) GetConfig() types.CaptchaConfig { + return s.config +} + func (s *CaptchaService) Get() (interface{}, error) { url := fmt.Sprintf("%s/api/captcha/get", types.GeekAPIURL) var res types.BizVo @@ -48,7 +52,7 @@ func (s *CaptchaService) Get() (interface{}, error) { return res.Data, nil } -func (s *CaptchaService) Check(data interface{}) bool { +func (s *CaptchaService) Check(data any) bool { url := fmt.Sprintf("%s/api/captcha/check", types.GeekAPIURL) var res types.BizVo r, err := s.client.R(). @@ -66,7 +70,7 @@ func (s *CaptchaService) Check(data interface{}) bool { return true } -func (s *CaptchaService) SlideGet() (interface{}, error) { +func (s *CaptchaService) SlideGet() (any, error) { url := fmt.Sprintf("%s/api/captcha/slide/get", types.GeekAPIURL) var res types.BizVo r, err := s.client.R(). @@ -83,7 +87,7 @@ func (s *CaptchaService) SlideGet() (interface{}, error) { return res.Data, nil } -func (s *CaptchaService) SlideCheck(data interface{}) bool { +func (s *CaptchaService) SlideCheck(data any) bool { url := fmt.Sprintf("%s/api/captcha/slide/check", types.GeekAPIURL) var res types.BizVo r, err := s.client.R(). diff --git a/web/src/components/Captcha.vue b/web/src/components/Captcha.vue index c96ef862..aeb59438 100644 --- a/web/src/components/Captcha.vue +++ b/web/src/components/Captcha.vue @@ -4,19 +4,16 @@ v-model="show" :close-on-click-modal="true" :show-close="isMobileInternal" + :append-to-body="true" style="width: 360px; --el-dialog-padding-primary: 5px 15px 15px 15px" > - + 人机验证 { const loadCaptcha = () => { show.value = true // 手机用滑动验证码 - if (isMobile()) { + if (props.type === 'slide') { getSlideCaptcha() } else { handleRequestCaptCode() diff --git a/web/src/components/CaptchaPlus.vue b/web/src/components/CaptchaPlus.vue index 92a919a2..9058a48c 100644 --- a/web/src/components/CaptchaPlus.vue +++ b/web/src/components/CaptchaPlus.vue @@ -226,6 +226,7 @@ export default { -moz-user-select: none; -ms-user-select: none; user-select: none; + height: 360px; .wg-cap-wrap__header { height: 50px; diff --git a/web/src/components/LoginDialog.vue b/web/src/components/LoginDialog.vue index ab97cf85..67e073d2 100644 --- a/web/src/components/LoginDialog.vue +++ b/web/src/components/LoginDialog.vue @@ -207,6 +207,17 @@ + + + 我已阅读并同意 + 《用户协议》 + 和 + 《隐私政策》 + + + - + + + + + + + + + @@ -265,12 +284,13 @@ import SendMsg from '@/components/SendMsg.vue' import { getSystemInfo } from '@/store/cache' import { setUserToken } from '@/store/session' import { useSharedStore } from '@/store/sharedata' -import { httpPost } from '@/utils/http' +import { httpGet, httpPost } from '@/utils/http' import { arrayContains } from '@/utils/libs' import { validateEmail, validateMobile } from '@/utils/validate' import { Checked, Iphone, Lock, Message } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' import { onMounted, ref, watch } from 'vue' +import { marked } from 'marked' // eslint-disable-next-line no-undef const props = defineProps({ @@ -313,10 +333,16 @@ const captchaRef = ref(null) // eslint-disable-next-line no-undef const emits = defineEmits(['hide', 'success']) const action = ref('login') -const enableVerify = ref(false) +const enableCaptcha = ref(false) +const captchaType = ref('') const showResetPass = ref(false) const store = useSharedStore() const loading = ref(false) +const agreeChecked = ref(false) +const showAgreement = ref(false) +const showPrivacy = ref(false) +const agreementHtml = ref('') +const privacyHtml = ref('') onMounted(() => { getSystemInfo() @@ -341,12 +367,16 @@ onMounted(() => { if (res.data['wechat_card_url'] !== '') { wxImg.value = res.data['wechat_card_url'] } - enableVerify.value = res.data['enabled_verify'] } }) .catch((e) => { ElMessage.error('获取系统配置失败:' + e.message) }) + + httpGet('/api/captcha/config').then((res) => { + enableCaptcha.value = res.data['enabled'] + captchaType.value = res.data['type'] + }) }) const submit = (verifyData) => { @@ -365,7 +395,7 @@ const submitLogin = () => { if (!data.value.password) { return ElMessage.error('请输入密码') } - if (enableVerify.value) { + if (enableCaptcha.value) { captchaRef.value.loadCaptcha() action.value = 'login' } else { @@ -418,7 +448,10 @@ const submitRegister = () => { if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') { return ElMessage.error('请输入验证码') } - if (enableVerify.value && activeName.value === 'username') { + if (!agreeChecked.value) { + return ElMessage.error('请先阅读并同意《用户协议》和《隐私政策》') + } + if (enableCaptcha.value && activeName.value === 'username') { captchaRef.value.loadCaptcha() action.value = 'register' } else { @@ -451,6 +484,34 @@ const doRegister = (verifyData) => { loading.value = false }) } + +// 打开并加载协议 +const openAgreement = () => { + if (!agreementHtml.value) { + httpGet('/api/config/get?key=agreement') + .then((res) => { + agreementHtml.value = marked.parse(res.data?.content || '') + showAgreement.value = true + }) + .catch((e) => ElMessage.error('加载用户协议失败:' + e.message)) + } else { + showAgreement.value = true + } +} + +// 打开并加载隐私政策 +const openPrivacy = () => { + if (!privacyHtml.value) { + httpGet('/api/config/get?key=privacy') + .then((res) => { + privacyHtml.value = marked.parse(res.data?.content || '') + showPrivacy.value = true + }) + .catch((e) => ElMessage.error('加载隐私政策失败:' + e.message)) + } else { + showPrivacy.value = true + } +} diff --git a/web/src/views/admin/settings/BasicConfig.vue b/web/src/views/admin/settings/BasicConfig.vue index 5ebaf6c0..1508c9c3 100644 --- a/web/src/views/admin/settings/BasicConfig.vue +++ b/web/src/views/admin/settings/BasicConfig.vue @@ -90,18 +90,6 @@ - - - - 启用验证码 - (启用验证码之后,注册登录都会加载行为验证码,增加安全性。) - - - - - 手机注册 diff --git a/web/src/views/admin/settings/MarkMapConfig.vue b/web/src/views/admin/settings/MarkMapConfig.vue index e4b1d61d..f2a9d2d3 100644 --- a/web/src/views/admin/settings/MarkMapConfig.vue +++ b/web/src/views/admin/settings/MarkMapConfig.vue @@ -1,45 +1,14 @@ - - - 思维导图配置 - - - - - 思维导图默认文本 - - - - - - - - - + + - - - 保存 - - - + + 保存 @@ -47,26 +16,25 @@