fzzj 8 kuukautta sitten
vanhempi
commit
beb096779e

+ 26
- 18
RuoYi-App/pages/novel/list.vue Näytä tiedosto

@@ -167,11 +167,11 @@ export default {
167 167
         // 加载分类
168 168
     async loadCategories() {
169 169
       try {
170
-        // 公开API,不需要认证
171
-              const res = await this.$http.get('/category/tree', {}, {
172
-                header: { isToken: false } // 明确不携带token
170
+    // 使用正确的参数格式
171
+    const res = await this.$http.get('/category/tree', {
172
+      header: { isToken: false }
173 173
               });
174
-        this.categories = res?.data ? [{ id: 0, name: '全部' }, ...res.data] : [];
174
+        this.categories = res && res.data ? [{ id: 0, name: '全部' }, ...res.data] : [];
175 175
       } catch (e) {
176 176
         console.error('加载分类失败', e);
177 177
         this.categories = [{ id: 0, name: '全部' }];
@@ -181,21 +181,21 @@ export default {
181 181
     async loadHotNovels() {
182 182
       try {
183 183
       // 公开API,不需要认证
184
-      const res = await this.$http.get('/novel/hot', {}, {
185
-        header: { isToken: false } // 明确不携带token
184
+    const res = await this.$http.get('/novel/hot', {
185
+      header: { isToken: false }
186 186
       });
187 187
         // 处理响应
188
-        if (res?.data?.rows) {
188
+        if (res && res.data && res.data.rows) {
189 189
           this.hotNovels = res.data.rows;
190
-        } else if (Array.isArray(res?.data)) {
190
+        } else if (res && res.data && Array.isArray(res.data)) {
191 191
           this.hotNovels = res.data;
192 192
         } else {
193 193
           console.warn('热门小说API返回格式未知', res);
194
-          this.hotNovels = this.mockHotNovels; // 使用模拟数据
194
+          this.hotNovels = this.mockHotNovels;
195 195
         }
196 196
       } catch (e) {
197 197
         console.error('加载热门小说失败', e);
198
-        this.hotNovels = this.mockHotNovels; // 使用模拟数据
198
+        this.hotNovels = this.mockHotNovels;
199 199
       }
200 200
     },
201 201
     // 加载小说列表
@@ -205,11 +205,14 @@ export default {
205 205
       try {
206 206
         // 使用正确的请求方式
207 207
         //const res = await this.$http.get('/novel/list');
208
-         const res = await this.$http.get('/novel/list', {}, { header: { isToken: false } });
209
-        // 处理响应
210
-        if (res?.data?.rows) {
208
+    const res = await this.$http.get('/novel/list', {
209
+      header: { isToken: false }
210
+        });
211
+        
212
+        // 修复可选链操作符问题
213
+        if (res && res.data && res.data.rows) {
211 214
           this.novels = res.data.rows;
212
-        } else if (Array.isArray(res?.data)) {
215
+        } else if (res && res.data && Array.isArray(res.data)) {
213 216
           this.novels = res.data;
214 217
         } else {
215 218
           console.warn('小说列表API返回格式未知', res);
@@ -228,11 +231,16 @@ export default {
228 231
     async loadAds() {
229 232
       try {
230 233
         const [topRes, bottomRes] = await Promise.all([
231
-          this.$http.get('/ad/position?code=TOP_BANNER', {}, { header: { isToken: false } }),
232
-          this.$http.get('/ad/position?code=BOTTOM_BANNER', {}, { header: { isToken: false } })
234
+      this.$http.get('/ad/position?code=TOP_BANNER', {
235
+        header: { isToken: false }
236
+      }),
237
+      this.$http.get('/ad/position?code=BOTTOM_BANNER', {
238
+        header: { isToken: false }
239
+      })
233 240
         ]);
234
-        this.topAds = topRes?.data || topRes || [];
235
-        this.bottomAds = bottomRes?.data || bottomRes || [];
241
+        
242
+        this.topAds = topRes && topRes.data ? topRes.data : [];
243
+        this.bottomAds = bottomRes && bottomRes.data ? bottomRes.data : [];
236 244
       } catch (e) {
237 245
         console.error('加载广告失败', e);
238 246
       }

+ 5
- 2
RuoYi-App/utils/auth.js Näytä tiedosto

@@ -10,9 +10,12 @@ export function setToken(token) {
10 10
 	  // 同时设置到uni和localStorage确保兼容性
11 11
 	  uni.setStorageSync('token', token);
12 12
 	  localStorage.setItem('token', token);
13
-  return uni.setStorageSync('token', token)
13
+  //return uni.setStorageSync('token', token)
14 14
 }
15 15
 
16 16
 export function removeToken() {
17
-  return uni.removeStorageSync(TokenKey)
17
+  // 同时清除 uni-app 存储和 localStorage
18
+  uni.removeStorageSync('token');
19
+  localStorage.removeItem('token');
20
+  //return uni.removeStorageSync(TokenKey)
18 21
 }

+ 36
- 39
RuoYi-App/utils/request.js Näytä tiedosto

@@ -1,69 +1,66 @@
1 1
 // src/utils/request.js
2 2
 import config from '@/config'
3
-import { getToken } from '@/utils/auth'
3
+import { getToken, removeToken } from '@/utils/auth'
4 4
 import { toast, showConfirm, tansParams } from '@/utils/common'
5 5
 
6
-const baseUrl = config.baseUrl
7
-let timeout = 10000
8
-
9
-const request = (configObj) => {  // 重命名参数为 configObj
10
-  // 确保传入的配置对象有效
11
-  configObj = configObj || {}
6
+// 移除对 store 的依赖
7
+const request = (configObj = {}) => {
8
+  // 确保配置对象有效
12 9
   configObj.header = configObj.header || {}
13 10
   
14
-  // 处理 token
15
-  const token = getToken()
16
-  // 只有存在token且未明确禁止携带token时才添加
11
+  // 获取token - 只从本地存储获取
12
+  const token = getToken() || ''
13
+  
14
+  // 仅当token存在且未明确禁止时才添加
17 15
   if (token && configObj.header.isToken !== false) {
18 16
     configObj.header['Authorization'] = 'Bearer ' + token
19 17
   } else if (!token) {
20
-    console.warn('请求未携带Token:', configObj.url)
18
+    console.log('请求未携带Token: 未登录状态访问公开API', configObj.url)
21 19
   }
22
-
23 20
   
24
-  // GET 请求参数处理
25
-  if (configObj.params) {
26
-    let url = configObj.url + '?' + tansParams(configObj.params)
27
-    url = url.slice(0, -1)
28
-    configObj.url = url
21
+  // 完整URL处理
22
+  let fullUrl = configObj.url
23
+  if (!fullUrl.startsWith('http')) {
24
+    fullUrl = (configObj.baseUrl || config.baseUrl || '') + fullUrl
29 25
   }
30 26
   
31 27
   return new Promise((resolve, reject) => {
32 28
     uni.request({
33 29
       method: configObj.method || 'get',
34
-      timeout: configObj.timeout || timeout,
35
-      url: (configObj.baseUrl || baseUrl) + configObj.url,
30
+      url: fullUrl,
36 31
       data: configObj.data,
37 32
       header: configObj.header,
38
-      dataType: 'json',
39 33
       success: (res) => {
40 34
         // 处理401认证失败
41 35
         if (res.statusCode === 401) {
42
-          // 更友好的错误处理
43
-          const errorMsg = res.data?.msg || '认证失败,请重新登录';
44
-          console.warn('认证失败:', configObj.url, errorMsg);
45
-          
46
-          // 显示提示但不强制跳转
47
-          toast(errorMsg);
36
+          const errorMsg = res.data?.msg || '认证失败,请重新登录'
37
+          console.warn('认证失败:', configObj.url, errorMsg)
48 38
           
49 39
           // 清除无效token
50
-          if (token) {
51
-            uni.removeStorageSync('token');
52
-            localStorage.removeItem('token');
53
-          }
40
+          removeToken()
54 41
           
55
-          reject(new Error(errorMsg));
56
-        } else if (res.statusCode === 200) {
57
-          resolve(res.data);
58
-        } else {
59
-          reject(res.data);
42
+          // 显示登录提示
43
+          toast(errorMsg)
44
+          
45
+          // 跳转到登录页面
46
+          uni.navigateTo({ url: '/pages/login' })
47
+          
48
+          reject(new Error(errorMsg))
49
+        } 
50
+        // 处理其他成功响应
51
+        else if (res.statusCode >= 200 && res.statusCode < 300) {
52
+          resolve(res.data)
53
+        } 
54
+        // 处理其他错误
55
+        else {
56
+          const errorMsg = res.data?.msg || `请求失败 (${res.statusCode})`
57
+          toast(errorMsg)
58
+          reject(new Error(errorMsg))
60 59
         }
61 60
       },
62 61
       fail: (err) => {
63
-        let message = '后端接口连接异常'
64
-        if (err.errMsg.includes('timeout')) {
65
-          message = '请求超时'
66
-        }
62
+        let message = '网络连接异常'
63
+        if (err.errMsg.includes('timeout')) message = '请求超时'
67 64
         toast(message)
68 65
         reject(err)
69 66
       }

+ 1
- 4
RuoYi-App/vue.config.js Näytä tiedosto

@@ -8,10 +8,7 @@ module.exports = {
8 8
       '/': {
9 9
         target: 'http://localhost:8080',
10 10
         changeOrigin: true,
11
-        pathRewrite: {
12
-          '^/api': '', // 移除多余的/api前缀
13
-          '^/': '' 
14
-        }
11
+		logLevel: 'debug' // 添加调试日志
15 12
       }
16 13
     }
17 14
   }

+ 6
- 0
RuoYi-Vue/ruoyi-admin/src/main/resources/application.yml Näytä tiedosto

@@ -200,3 +200,9 @@ app:
200 200
     secret: your-very-strong-secret-key-here # 至少32位随机字符串
201 201
     expiration: 86400000 # 令牌过期时间(毫秒),默认24小时
202 202
     header: Authorization # 请求头名称
203
+permit-all:
204
+  urls:
205
+    - /druid/**
206
+    - /swagger-ui/**
207
+    - /v3/api-docs/**
208
+    - /webjars/**

+ 19
- 0
RuoYi-Vue/ruoyi-framework/src/main/java/com/ruoyi/framework/config/CorsConfig.java Näytä tiedosto

@@ -0,0 +1,19 @@
1
+package com.ruoyi.framework.config;
2
+
3
+import org.springframework.context.annotation.Configuration;
4
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
5
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6
+
7
+@Configuration
8
+public class CorsConfig implements WebMvcConfigurer {
9
+    @Override
10
+    public void addCorsMappings(CorsRegistry registry) {
11
+        registry.addMapping("/**")
12
+                // 使用 allowedOriginPatterns 替代 allowedOrigins
13
+                .allowedOriginPatterns("http://localhost:9090")
14
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
15
+                .allowedHeaders("*")
16
+                .allowCredentials(true)
17
+                .maxAge(3600);
18
+    }
19
+}

+ 69
- 48
RuoYi-Vue/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java Näytä tiedosto

@@ -15,6 +15,9 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
15 15
 import org.springframework.security.web.SecurityFilterChain;
16 16
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
17 17
 import org.springframework.security.web.authentication.logout.LogoutFilter;
18
+import org.springframework.web.cors.CorsConfiguration;
19
+import org.springframework.web.cors.CorsConfigurationSource;
20
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
18 21
 import org.springframework.web.filter.CorsFilter;
19 22
 import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
20 23
 import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
@@ -23,7 +26,7 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
23 26
 
24 27
 /**
25 28
  * spring security配置
26
- * 
29
+ *
27 30
  * @author ruoyi
28 31
  */
29 32
 @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@@ -35,7 +38,7 @@ public class SecurityConfig
35 38
      */
36 39
     @Autowired
37 40
     private UserDetailsService userDetailsService;
38
-    
41
+
39 42
     /**
40 43
      * 认证失败处理类
41 44
      */
@@ -53,7 +56,7 @@ public class SecurityConfig
53 56
      */
54 57
     @Autowired
55 58
     private JwtAuthenticationTokenFilter authenticationTokenFilter;
56
-    
59
+
57 60
     /**
58 61
      * 跨域过滤器
59 62
      */
@@ -94,54 +97,72 @@ public class SecurityConfig
94 97
      * authenticated       |   用户登录后可访问
95 98
      */
96 99
     @Bean
97
-    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
98
-    {
99
-        return httpSecurity
100
-            // CSRF禁用,因为不使用session
101
-            .csrf(csrf -> csrf.disable())
102
-            // 禁用HTTP响应标头
103
-            .headers((headersCustomizer) -> {
104
-                headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
105
-            })
106
-            // 认证失败处理类
107
-            .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
108
-            // 基于token,所以不需要session
109
-            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
110
-            // 注解标记允许匿名访问的url
111
-            .authorizeHttpRequests((requests) -> {
112
-                permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
113
-                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
114
-                requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
115
-                    // 静态资源,可匿名访问
116
-                    .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
117
-                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
118
-                    // 除上面外的所有请求全部需要鉴权认证
119
-                    .anyRequest().authenticated();
120
-            })
121
-            // 添加Logout filter
122
-            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
123
-            // 添加JWT filter
124
-            .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
125
-            // 添加CORS filter
126
-            .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
127
-            .addFilterBefore(corsFilter, LogoutFilter.class)
128
-            .build();
100
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
101
+        // 添加公开API到白名单
102
+        permitAllUrl.getUrls().add("/category/**");
103
+        permitAllUrl.getUrls().add("/novel/**");
104
+        permitAllUrl.getUrls().add("/ad/**");
105
+        permitAllUrl.getUrls().add("/swagger-ui/**");
106
+        permitAllUrl.getUrls().add("/v3/api-docs/**");
107
+
108
+        http
109
+                // 禁用CSRF
110
+                .csrf(csrf -> csrf.disable())
111
+                // 设置CORS配置
112
+                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
113
+                // 异常处理
114
+                .exceptionHandling(exception ->
115
+                        exception.authenticationEntryPoint(unauthorizedHandler)
116
+                )
117
+                // 无状态会话
118
+                .sessionManagement(session ->
119
+                        session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
120
+                )
121
+                // 权限配置
122
+                .authorizeHttpRequests(auth -> auth
123
+                        .antMatchers(permitAllUrl.getUrls().toArray(new String[0])).permitAll()
124
+                        .antMatchers("/login", "/register", "/captchaImage").permitAll()
125
+                        .antMatchers(HttpMethod.GET,
126
+                                "/",
127
+                                "/*.html",
128
+                                "/**/*.html",
129
+                                "/**/*.css",
130
+                                "/**/*.js",
131
+                                "/profile/**"
132
+                        ).permitAll()
133
+                        .antMatchers(
134
+                                "/swagger-ui.html",
135
+                                "/swagger-resources/**",
136
+                                "/webjars/**",
137
+                                "/*/api-docs",
138
+                                "/druid/**"
139
+                        ).permitAll()
140
+                        .anyRequest().authenticated()
141
+                )
142
+                // 登出配置
143
+                .logout(logout ->
144
+                        logout.logoutUrl("/logout")
145
+                                .logoutSuccessHandler(logoutSuccessHandler)
146
+                )
147
+                // 添加JWT过滤器
148
+                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
149
+                // 添加CORS过滤器
150
+                .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
151
+
152
+        return http.build();
129 153
     }
130
-    // SecurityConfig.java
131 154
 
155
+    // CORS配置
156
+    private CorsConfigurationSource corsConfigurationSource() {
157
+        CorsConfiguration configuration = new CorsConfiguration();
158
+        configuration.addAllowedOrigin("*"); // 允许所有来源
159
+        configuration.addAllowedMethod("*"); // 允许所有方法
160
+        configuration.addAllowedHeader("*"); // 允许所有头部
161
+        configuration.setAllowCredentials(true); // 允许凭证
132 162
 
133
-    protected void configure(HttpSecurity http) throws Exception {
134
-        http
135
-                .authorizeRequests()
136
-                // 放行公共API
137
-                .antMatchers("/novel/list").permitAll()
138
-                .antMatchers("/category/tree").permitAll()
139
-                .antMatchers("/novel/hot").permitAll()
140
-                .antMatchers("/ad/position").permitAll()
141
-                // 其他请求需要认证
142
-                .anyRequest().authenticated()
143
-                .and()
144
-                .csrf().disable();
163
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
164
+        source.registerCorsConfiguration("/**", configuration);
165
+        return source;
145 166
     }
146 167
     /**
147 168
      * 强散列哈希加密实现

+ 2
- 0
RuoYi-Vue/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java Näytä tiedosto

@@ -9,6 +9,7 @@ import java.util.regex.Pattern;
9 9
 import org.apache.commons.lang3.RegExUtils;
10 10
 import org.springframework.beans.BeansException;
11 11
 import org.springframework.beans.factory.InitializingBean;
12
+import org.springframework.boot.context.properties.ConfigurationProperties;
12 13
 import org.springframework.context.ApplicationContext;
13 14
 import org.springframework.context.ApplicationContextAware;
14 15
 import org.springframework.context.annotation.Configuration;
@@ -24,6 +25,7 @@ import com.ruoyi.common.annotation.Anonymous;
24 25
  * @author ruoyi
25 26
  */
26 27
 @Configuration
28
+@ConfigurationProperties(prefix = "permit-all")
27 29
 public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
28 30
 {
29 31
     private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

+ 2
- 0
RuoYi-Vue/ruoyi-system/src/main/java/com/ruoyi/novel/domain/NovelCategory.java Näytä tiedosto

@@ -3,6 +3,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
3 3
 import com.baomidou.mybatisplus.annotation.IdType;
4 4
 import com.baomidou.mybatisplus.annotation.TableId;
5 5
 import com.baomidou.mybatisplus.annotation.TableName;
6
+import com.fasterxml.jackson.annotation.JsonInclude;
6 7
 import lombok.Data;
7 8
 
8 9
 import java.text.SimpleDateFormat;
@@ -33,6 +34,7 @@ public class NovelCategory {
33 34
     private String templateFilter;
34 35
     private String link;
35 36
     @TableField("create_time")
37
+    @JsonInclude(JsonInclude.Include.NON_NULL) // 忽略null字段
36 38
     private Integer createTime;
37 39
     @TableField("update_time")
38 40
     private Integer updateTime;

Loading…
Peruuta
Tallenna