SpringSunYY 5 месяцев назад
Родитель
Сommit
6f151d25b6

+ 53 - 40
ruoyi-ui/src/views/login.vue

@@ -9,7 +9,7 @@
           auto-complete="off"
           placeholder="账号"
         >
-          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
+          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/>
         </el-input>
       </el-form-item>
       <el-form-item prop="password">
@@ -20,7 +20,7 @@
           placeholder="密码"
           @keyup.enter.native="handleLogin"
         >
-          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
+          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
         </el-input>
       </el-form-item>
       <el-form-item prop="code" v-if="captchaOnOff">
@@ -31,7 +31,7 @@
           style="width: 63%"
           @keyup.enter.native="handleLogin"
         >
-          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
+          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
         </el-input>
         <div class="login-code">
           <img :src="codeUrl" @click="getCode" class="login-code-img"/>
@@ -62,97 +62,101 @@
 </template>
 
 <script>
-import { getCodeImg } from "@/api/login";
-import Cookies from "js-cookie";
+import { getCodeImg } from '@/api/login'
+import Cookies from 'js-cookie'
 import { encrypt, decrypt } from '@/utils/jsencrypt'
 
 export default {
-  name: "Login",
+  name: 'Login',
   data() {
     return {
-      codeUrl: "",
+      codeUrl: '',
       loginForm: {
-        username: "admin",
-        password: "admin123",
+        username: 'admin',
+        password: 'admin123',
         rememberMe: false,
-        code: "",
-        uuid: ""
+        code: '',
+        uuid: ''
       },
       loginRules: {
         username: [
-          { required: true, trigger: "blur", message: "请输入您的账号" }
+          { required: true, trigger: 'blur', message: '请输入您的账号' }
         ],
         password: [
-          { required: true, trigger: "blur", message: "请输入您的密码" }
+          { required: true, trigger: 'blur', message: '请输入您的密码' }
         ],
-        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
+        code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
       },
       loading: false,
       // 验证码开关
-      captchaOnOff: true,
+      captchaOnOff: false,
       // 注册开关
       register: false,
       redirect: undefined
-    };
+    }
   },
   watch: {
     $route: {
       handler: function(route) {
-        this.redirect = route.query && route.query.redirect;
+        this.redirect = route.query && route.query.redirect
       },
       immediate: true
     }
   },
   created() {
-    this.getCode();
-    this.getCookie();
+    this.getCode()
+    this.getCookie()
   },
   methods: {
     getCode() {
+      if (!this.captchaOnOff) {
+        return
+      }
       getCodeImg().then(res => {
-        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
+        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff
         if (this.captchaOnOff) {
-          this.codeUrl = "data:image/gif;base64," + res.img;
-          this.loginForm.uuid = res.uuid;
+          this.codeUrl = 'data:image/gif;base64,' + res.img
+          this.loginForm.uuid = res.uuid
         }
-      });
+      })
     },
     getCookie() {
-      const username = Cookies.get("username");
-      const password = Cookies.get("password");
+      const username = Cookies.get('username')
+      const password = Cookies.get('password')
       const rememberMe = Cookies.get('rememberMe')
       this.loginForm = {
         username: username === undefined ? this.loginForm.username : username,
         password: password === undefined ? this.loginForm.password : decrypt(password),
         rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
-      };
+      }
     },
     handleLogin() {
       this.$refs.loginForm.validate(valid => {
         if (valid) {
-          this.loading = true;
+          this.loading = true
           if (this.loginForm.rememberMe) {
-            Cookies.set("username", this.loginForm.username, { expires: 30 });
-            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
-            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
+            Cookies.set('username', this.loginForm.username, { expires: 30 })
+            Cookies.set('password', encrypt(this.loginForm.password), { expires: 30 })
+            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 })
           } else {
-            Cookies.remove("username");
-            Cookies.remove("password");
-            Cookies.remove('rememberMe');
+            Cookies.remove('username')
+            Cookies.remove('password')
+            Cookies.remove('rememberMe')
           }
-          this.$store.dispatch("Login", this.loginForm).then(() => {
-            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
+          this.$store.dispatch('Login', this.loginForm).then(() => {
+            this.$router.push({ path: this.redirect || '/' }).catch(() => {
+            })
           }).catch(() => {
-            this.loading = false;
+            this.loading = false
             if (this.captchaOnOff) {
-              this.getCode();
+              this.getCode()
             }
-          });
+          })
         }
-      });
+      })
     }
   }
-};
+}
 </script>
 
 <style rel="stylesheet/scss" lang="scss">
@@ -164,6 +168,7 @@ export default {
   background-image: url("../assets/images/login-background.jpg");
   background-size: cover;
 }
+
 .title {
   margin: 0px auto 30px auto;
   text-align: center;
@@ -175,32 +180,39 @@ export default {
   background: #ffffff;
   width: 400px;
   padding: 25px 25px 5px 25px;
+
   .el-input {
     height: 38px;
+
     input {
       height: 38px;
     }
   }
+
   .input-icon {
     height: 39px;
     width: 14px;
     margin-left: 2px;
   }
 }
+
 .login-tip {
   font-size: 13px;
   text-align: center;
   color: #bfbfbf;
 }
+
 .login-code {
   width: 33%;
   height: 38px;
   float: right;
+
   img {
     cursor: pointer;
     vertical-align: middle;
   }
 }
+
 .el-login-footer {
   height: 40px;
   line-height: 40px;
@@ -213,6 +225,7 @@ export default {
   font-size: 12px;
   letter-spacing: 1px;
 }
+
 .login-code-img {
   height: 38px;
 }

+ 10 - 7
ruoyi_admin/controller/index/captcha.py

@@ -22,15 +22,18 @@ def index_captcha_image():
     生成验证码图片
     :return:
     """
+    captchaOnOff = SysConfigService.select_captcha_on_off()
+    if not captchaOnOff:
+        return AjaxResponse.from_success()
     ImageCaptcha.character_rotate = (-15, 15)
     ImageCaptcha.character_warp_dx = (0.1, 0.1)
     ImageCaptcha.character_warp_dy = (0.1, 0.1)
     ImageCaptcha.word_offset_dx = 0.1
     ImageCaptcha.word_space_probability = 0
     image = ImageCaptcha(
-        width=160, 
-        height=60, 
-        font_sizes=[42,45,48]
+        width=160,
+        height=60,
+        font_sizes=[42, 45, 48]
     )
     wait_letters = string.ascii_letters + string.digits
     exclude_letters = "oO0iIl1"
@@ -38,8 +41,8 @@ def index_captcha_image():
     code = ''.join(random.sample(sample_letters, 4))
     uuid_str = uuid.uuid4().hex
     verifyKey = Constants.CAPTCHA_CODE_KEY + uuid_str
-    redis_cache.set(verifyKey, code, ex=Constants.CAPTCHA_EXPIRATION*60)
-    
+    redis_cache.set(verifyKey, code, ex=Constants.CAPTCHA_EXPIRATION * 60)
+
     byte_buffer = BytesIO()
     try:
         image.write(code, byte_buffer)
@@ -48,6 +51,6 @@ def index_captcha_image():
     byte_image = byte_buffer.getvalue()
     ajax_response = AjaxResponse.from_success()
     ajax_response.uuid = uuid_str
-    ajax_response.img = str(base64.b64encode(byte_image),encoding="utf-8")
-    ajax_response.captchaOnOff = SysConfigService.select_captcha_on_off()
+    ajax_response.img = str(base64.b64encode(byte_image), encoding="utf-8")
+    ajax_response.captchaOnOff = captchaOnOff
     return ajax_response

+ 10 - 0
ruoyi_framework/config.py

@@ -25,6 +25,16 @@ class TokenConfig:
         """
         return timedelta(minutes=int(cls._expire_time))
     
+    @classmethod
+    def expire_seconds(cls) -> int:
+        """
+        获取过期秒数
+
+        Returns:
+            int: 过期秒数
+        """
+        return int(cls.expire_time().total_seconds())
+    
 
 class ThreadPoolConfig:
     

+ 14 - 0
ruoyi_framework/service/sys_login.py

@@ -15,6 +15,7 @@ from ruoyi_framework.asyncsched.manager import TaskManager
 from ruoyi_framework.asyncsched.task import record_logininfor
 from ruoyi_system.domain.entity import SysLogininfor
 from ruoyi_system.service import SysConfigService,SysUserService
+from ruoyi_system.service.sys_post import SysPostService
 from ruoyi_admin.ext import lm,redis_cache
 from .token import TokenService
 from .sys_permission import SysPermissionService
@@ -86,6 +87,19 @@ class LoginService:
             raise ServiceException(f"对不起,您的账号:{user.user_name} 已删除")
         if user.status == UserStatus.DISABLE.value:
             raise ServiceException(f"对不起,您的账号:{user.user_name} 已停用")
+        if not user.role_ids:
+            dedup_role_ids = []
+            seen_role_ids = set()
+            for role in user.roles:
+                role_id = getattr(role, "role_id", None)
+                if role_id and role_id not in seen_role_ids:
+                    seen_role_ids.add(role_id)
+                    dedup_role_ids.append(role_id)
+            user.role_ids = dedup_role_ids
+        if not user.role_id and user.role_ids:
+            user.role_id = user.role_ids[0]
+        if not user.post_ids:
+            user.post_ids = SysPostService.select_post_list_by_user_id(user.user_id)
         login_user = LoginUser(
             user_id=user.user_id,
             dept_id=user.dept_id,

+ 20 - 4
ruoyi_framework/service/token.py

@@ -6,6 +6,7 @@ from flask import Request, request
 
 from ruoyi_common.exception import ServiceException
 from ruoyi_common.utils import AddressUtil, IpUtil, TokenUtil
+from ruoyi_common.utils.base import UserAgentUtil
 from ruoyi_common.constant import Constants
 from ruoyi_common.domain.entity import LoginUser
 from ruoyi_framework.config import TokenConfig
@@ -72,11 +73,13 @@ class TokenService:
             user(LoginUser): 登录用户
         '''
         user.login_time = datetime.now()
-        user.expire_time = user.login_time + TokenConfig.expire_time()
+        expire_delta = TokenConfig.expire_time()
+        user.expire_time = user.login_time + expire_delta
+        expire_seconds = TokenConfig.expire_seconds()
         usertoken_key = cls.get_token_key(user.token.hex)
         user_json = user.model_dump_json()
         if redis_cache:
-            redis_cache.set(usertoken_key, user_json, TokenConfig.expire_time() * 60)
+            redis_cache.set(usertoken_key, user_json, ex=expire_seconds)
         
     @classmethod
     def set_useragent(cls, user:LoginUser):
@@ -86,10 +89,23 @@ class TokenService:
         Args:
             user(LoginUser): 登录用户
         '''
+        agent = request.user_agent
         user.ip_addr = IpUtil.get_ip()
         user.login_location = AddressUtil.get_address(user.ip_addr)
-        user.browser = request.user_agent.browser
-        user.os = request.user_agent.platform
+
+        browser = getattr(agent, "browser", None)
+        if not browser:
+            browser = UserAgentUtil.browser()
+        if not browser and agent:
+            browser = agent.string
+        user.browser = browser
+
+        platform = getattr(agent, "platform", None)
+        if not platform:
+            platform = UserAgentUtil.os()
+        if not platform and agent:
+            platform = agent.string
+        user.os = platform
     
     @classmethod
     def parse_token(cls, token:str) -> dict:

+ 3 - 3
ruoyi_system/service/sys_config.py

@@ -140,9 +140,9 @@ class SysConfigService:
         for id in ids:
             config = cls.select_config_by_id(id)
             if not config:
-                return 
+                return
             if UserConstants.YES == config.config_type:
-                raise ServiceException(f"Built-in parameter【{config.config_key}】cannot be deleted")      
+                raise ServiceException(f"Built-in parameter【{config.config_key}】cannot be deleted")
             redis_cache.delete(cls.get_cache_key(config.config_key))
             deleting_ids.append(id)
         flag = SysConfigMapper.delete_configs_by_ids(deleting_ids)
@@ -207,7 +207,7 @@ class SysConfigService:
 def init(sender:Flask):
     '''
     初始化操作
-    
+
     Args:
         sender (Flask): 消息发送者
     '''