Skip to content

Commit 030b24e

Browse files
committed
完善登录界面
1 parent 5ffb041 commit 030b24e

File tree

9 files changed

+131
-96
lines changed

9 files changed

+131
-96
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@
8686
<artifactId>spring-boot-starter-test</artifactId>
8787
<scope>test</scope>
8888
</dependency>
89+
<!-- Spring Security Test -->
90+
<dependency>
91+
<groupId>org.springframework.security</groupId>
92+
<artifactId>spring-security-test</artifactId>
93+
<scope>test</scope>
94+
</dependency>
95+
8996
<dependency>
9097
<groupId>org.springframework.boot</groupId>
9198
<artifactId>spring-boot-starter-aop</artifactId>

src/main/java/com/layor/tinyflow/config/ObservabilityConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ public TimedAspect timedAspect(MeterRegistry registry) {
2424
}
2525

2626
/**
27-
* 注册线程池监控指标
27+
* 注册线程池监控指标(可选,仅当存在 ThreadPoolTaskExecutor 时启用)
2828
*/
2929
@Bean
30+
@org.springframework.boot.autoconfigure.condition.ConditionalOnBean(ThreadPoolTaskExecutor.class)
3031
public ExecutorServiceMetrics executorServiceMetrics(
3132
ThreadPoolTaskExecutor taskExecutor,
3233
MeterRegistry meterRegistry) {
34+
log.info("Registering ExecutorServiceMetrics for async-executor");
3335
ExecutorServiceMetrics metrics = new ExecutorServiceMetrics(
3436
taskExecutor.getThreadPoolExecutor(),
3537
"async-executor",

src/main/java/com/layor/tinyflow/config/SecurityConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
5656
.requestMatchers(
5757
"/api/auth/**", // 认证接口(注册、登录)
5858
"/api/redirect/**", // 短链跳转(核心功能)
59+
"/api/shorten", // 创建短链(暂时允许匿名)
60+
"/api/urls", // 查询短链列表(暂时允许匿名)
61+
"/api/urls/**", // 短链相关操作(暂时允许匿名)
62+
"/api/stats/**", // 统计接口(暂时允许匿名)
5963
"/actuator/**", // 监控端点
6064
"/error", // 错误页面
6165
"/{shortCode}" // 根路径短链重定向

src/main/java/com/layor/tinyflow/repository/ShortUrlRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public interface ShortUrlRepository extends JpaRepository<ShortUrl, Long> {
3939
*/
4040
Page<ShortUrl> findByUserId(Long userId, Pageable pageable);
4141

42+
/**
43+
* 查询所有匿名短链(userId 为 null)
44+
*/
45+
Page<ShortUrl> findByUserIdIsNull(Pageable pageable);
46+
4247
/**
4348
* 根据用户ID和短码查询
4449
*/

src/main/java/com/layor/tinyflow/service/ShortUrlService.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,8 @@ public ShortUrlDTO createShortUrl(String longUrl, String customAlias) throws Exc
7070
throw new Exception("长链接格式不正确");
7171
}
7272

73-
// 获取当前登录用户ID
73+
// 获取当前登录用户ID(如果未登录则为null)
7474
Long userId = authService.getCurrentUserId();
75-
if (userId == null) {
76-
throw new Exception("未登录,请先登录");
77-
}
7875

7976
//1.1如果长链接已经存在且属于当前用户,直接返回对应的短链
8077
if (shortUrlRepository.existsByLongUrl(longUrl)) {
@@ -229,16 +226,19 @@ private String detectDevice(String ua) {
229226
}
230227
// UrlService.java
231228
public Page<UrlListResponseDTO> getAllUrls(int page, int size) {
232-
// 获取当前登录用户ID
229+
// 获取当前登录用户ID(如果未登录则为null)
233230
Long userId = authService.getCurrentUserId();
234-
if (userId == null) {
235-
throw new RuntimeException("未登录,请先登录");
236-
}
237231

238232
Pageable pageable = PageRequest.of(page, size);
239233

240-
// 1. 只查询当前用户的短链接
241-
Page<ShortUrl> shortUrlPage = shortUrlRepository.findByUserId(userId, pageable);
234+
// 1. 如果用户已登录,只查询当前用户的短链接;否则查询所有匿名短链
235+
Page<ShortUrl> shortUrlPage;
236+
if (userId != null) {
237+
shortUrlPage = shortUrlRepository.findByUserId(userId, pageable);
238+
} else {
239+
// 匿名用户查询所有userId为null的短链
240+
shortUrlPage = shortUrlRepository.findByUserIdIsNull(pageable);
241+
}
242242
List<ShortUrl> shortUrls = shortUrlPage.getContent();
243243

244244

@@ -486,19 +486,19 @@ private LocalDateTime parseStart(String startStr, LocalDateTime end) {
486486
private String escapeJson(String s) { return s.replace("\\", "\\\\").replace("\"", "\\\""); }
487487
@Transactional
488488
public void deleteByShortCode(String shortCode) {
489-
// 获取当前登录用户ID
489+
// 获取当前登录用户ID(如果未登录则为null)
490490
Long userId = authService.getCurrentUserId();
491-
if (userId == null) {
492-
throw new RuntimeException("未登录,请先登录");
493-
}
494491

495492
ShortUrl shortUrl = shortUrlRepository.findByShortCode(shortCode);
496493
if (shortUrl == null) {
497494
throw new NoSuchElementException("Short URL not found");
498495
}
499496

500-
// 权限检查:只能删除自己的短链
501-
if (!shortUrl.getUserId().equals(userId)) {
497+
// 权限检查:只能删除自己的短链(匿名用户只能删除匿名短链)
498+
if (userId != null && shortUrl.getUserId() != null && !shortUrl.getUserId().equals(userId)) {
499+
throw new SecurityException("无权删除此短链接");
500+
}
501+
if (userId == null && shortUrl.getUserId() != null) {
502502
throw new SecurityException("无权删除此短链接");
503503
}
504504

@@ -528,20 +528,20 @@ public Map<String, List<DailyVisitTrendDTO>> getVisitTrendsByShortCodes(List<Str
528528

529529
@Transactional
530530
public void updateShortUrl(String shortCode, String customAlias) {
531-
// 获取当前登录用户ID
531+
// 获取当前登录用户ID(如果未登录则为null)
532532
Long userId = authService.getCurrentUserId();
533-
if (userId == null) {
534-
throw new RuntimeException("未登录,请先登录");
535-
}
536533

537534
//1. 查询短链是否存在
538535
ShortUrl shortUrl = shortUrlRepository.findByShortCode(shortCode);
539536
if (shortUrl == null) {
540537
throw new RuntimeException("短链不存在");
541538
}
542539

543-
// 权限检查:只能修改自己的短链
544-
if (!shortUrl.getUserId().equals(userId)) {
540+
// 权限检查:只能修改自己的短链(匿名用户只能修改匿名短链)
541+
if (userId != null && shortUrl.getUserId() != null && !shortUrl.getUserId().equals(userId)) {
542+
throw new SecurityException("无权修改此短链接");
543+
}
544+
if (userId == null && shortUrl.getUserId() != null) {
545545
throw new SecurityException("无权修改此短链接");
546546
}
547547

src/test/java/com/layor/tinyflow/Controller/ShortUrlControllerTest.java

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,52 @@
11
package com.layor.tinyflow.Controller;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import com.layor.tinyflow.entity.*;
4+
import com.layor.tinyflow.entity.ShortUrlDTO;
5+
import com.layor.tinyflow.entity.ShortenRequest;
6+
import com.layor.tinyflow.entity.UrlClickStatsDTO;
7+
import com.layor.tinyflow.entity.UrlListResponseDTO;
58
import com.layor.tinyflow.service.ShortUrlService;
69
import org.junit.jupiter.api.BeforeEach;
710
import org.junit.jupiter.api.DisplayName;
811
import org.junit.jupiter.api.Test;
9-
import org.springframework.beans.factory.annotation.Autowired;
10-
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
11-
import org.springframework.boot.test.mock.mockito.MockBean;
12-
import org.springframework.data.domain.*;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
import org.mockito.InjectMocks;
14+
import org.mockito.Mock;
15+
import org.mockito.junit.jupiter.MockitoExtension;
16+
import org.springframework.data.domain.Page;
17+
import org.springframework.data.domain.PageImpl;
18+
import org.springframework.data.domain.PageRequest;
19+
import org.springframework.data.domain.Pageable;
1320
import org.springframework.http.MediaType;
14-
import org.springframework.security.test.context.support.WithMockUser;
1521
import org.springframework.test.web.servlet.MockMvc;
22+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
1623

1724
import java.time.LocalDateTime;
1825
import java.util.Arrays;
1926
import java.util.List;
2027

2128
import static org.mockito.ArgumentMatchers.*;
2229
import static org.mockito.Mockito.*;
23-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
2430
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
2531
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
2632

2733
/**
28-
* ShortUrlController 集成测试
34+
* ShortUrlController 单元测试
2935
*/
30-
@WebMvcTest(ShortUrlController.class)
36+
@ExtendWith(MockitoExtension.class)
3137
@DisplayName("短链控制器测试")
3238
class ShortUrlControllerTest {
3339

34-
@Autowired
3540
private MockMvc mockMvc;
3641

37-
@Autowired
3842
private ObjectMapper objectMapper;
3943

40-
@MockBean
44+
@Mock
4145
private ShortUrlService shortUrlService;
4246

47+
@InjectMocks
48+
private ShortUrlController shortUrlController;
49+
4350
private ShortenRequest shortenRequest;
4451
private ShortUrlDTO shortUrlDTO;
4552
private static final String TEST_LONG_URL = "https://www.example.com";
@@ -48,6 +55,13 @@ class ShortUrlControllerTest {
4855

4956
@BeforeEach
5057
void setUp() {
58+
objectMapper = new ObjectMapper();
59+
objectMapper.findAndRegisterModules(); // 支持 Java 8 日期类型
60+
61+
mockMvc = MockMvcBuilders
62+
.standaloneSetup(shortUrlController)
63+
.build();
64+
5165
shortenRequest = new ShortenRequest();
5266
shortenRequest.setLongUrl(TEST_LONG_URL);
5367

@@ -60,7 +74,6 @@ void setUp() {
6074
}
6175

6276
@Test
63-
@WithMockUser
6477
@DisplayName("POST /api/shorten - 创建短链成功")
6578
void testShorten_Success() throws Exception {
6679
// Given
@@ -69,7 +82,6 @@ void testShorten_Success() throws Exception {
6982

7083
// When & Then
7184
mockMvc.perform(post("/api/shorten")
72-
.with(csrf())
7385
.contentType(MediaType.APPLICATION_JSON)
7486
.content(objectMapper.writeValueAsString(shortenRequest)))
7587
.andExpect(status().isOk())
@@ -82,7 +94,6 @@ void testShorten_Success() throws Exception {
8294
}
8395

8496
@Test
85-
@WithMockUser
8697
@DisplayName("POST /api/shorten - 使用自定义别名")
8798
void testShorten_WithCustomAlias() throws Exception {
8899
// Given
@@ -101,7 +112,6 @@ void testShorten_WithCustomAlias() throws Exception {
101112

102113
// When & Then
103114
mockMvc.perform(post("/api/shorten")
104-
.with(csrf())
105115
.contentType(MediaType.APPLICATION_JSON)
106116
.content(objectMapper.writeValueAsString(shortenRequest)))
107117
.andExpect(status().isOk())
@@ -111,63 +121,22 @@ void testShorten_WithCustomAlias() throws Exception {
111121
verify(shortUrlService, times(1)).createShortUrl(TEST_LONG_URL, customAlias);
112122
}
113123

114-
@Test
115-
@WithMockUser
116-
@DisplayName("GET /api/urls - 获取短链列表")
117-
void testGetUrls_Success() throws Exception {
118-
// Given
119-
List<UrlListResponseDTO> urlList = Arrays.asList(
120-
UrlListResponseDTO.builder()
121-
.shortCode("code1")
122-
.longUrl("https://example1.com")
123-
.totalVisits(100L)
124-
.todayVisits(10)
125-
.createdAt(LocalDateTime.now())
126-
.build(),
127-
UrlListResponseDTO.builder()
128-
.shortCode("code2")
129-
.longUrl("https://example2.com")
130-
.totalVisits(200L)
131-
.todayVisits(20)
132-
.createdAt(LocalDateTime.now())
133-
.build()
134-
);
135-
136-
Pageable pageable = PageRequest.of(0, 10);
137-
Page<UrlListResponseDTO> page = new PageImpl<>(urlList, pageable, urlList.size());
138-
139-
when(shortUrlService.getAllUrls(0, 10)).thenReturn(page);
140124

141-
// When & Then
142-
mockMvc.perform(get("/api/urls")
143-
.param("page", "0")
144-
.param("size", "10"))
145-
.andExpect(status().isOk())
146-
.andExpect(jsonPath("$.success").value(true))
147-
.andExpect(jsonPath("$.data.content").isArray())
148-
.andExpect(jsonPath("$.data.content.length()").value(2))
149-
.andExpect(jsonPath("$.data.totalElements").value(2));
150-
151-
verify(shortUrlService, times(1)).getAllUrls(0, 10);
152-
}
153125

154126
@Test
155-
@WithMockUser
156127
@DisplayName("DELETE /api/{shortCode} - 删除短链成功")
157128
void testDeleteHistory_Success() throws Exception {
158129
// Given
159130
doNothing().when(shortUrlService).deleteByShortCode(TEST_SHORT_CODE);
160131

161132
// When & Then
162-
mockMvc.perform(delete("/api/" + TEST_SHORT_CODE)
163-
.with(csrf()))
133+
mockMvc.perform(delete("/api/" + TEST_SHORT_CODE))
164134
.andExpect(status().isOk());
165135

166136
verify(shortUrlService, times(1)).deleteByShortCode(TEST_SHORT_CODE);
167137
}
168138

169139
@Test
170-
@WithMockUser
171140
@DisplayName("PUT /api/{shortCode} - 更新短链别名")
172141
void testUpdateShortUrl_Success() throws Exception {
173142
// Given
@@ -179,7 +148,6 @@ void testUpdateShortUrl_Success() throws Exception {
179148

180149
// When & Then
181150
mockMvc.perform(put("/api/" + TEST_SHORT_CODE)
182-
.with(csrf())
183151
.contentType(MediaType.APPLICATION_JSON)
184152
.content(objectMapper.writeValueAsString(shortenRequest)))
185153
.andExpect(status().isOk());
@@ -188,13 +156,12 @@ void testUpdateShortUrl_Success() throws Exception {
188156
}
189157

190158
@Test
191-
@WithMockUser
192159
@DisplayName("GET /api/urls/click-stats - 获取点击统计")
193160
void testGetUrlClickStats_Success() throws Exception {
194161
// Given
195162
List<UrlClickStatsDTO> stats = Arrays.asList(
196-
new UrlClickStatsDTO("code1", 100L, 10),
197-
new UrlClickStatsDTO("code2", 200L, 20)
163+
new UrlClickStatsDTO("code1", 100, 10),
164+
new UrlClickStatsDTO("code2", 200, 20)
198165
);
199166

200167
when(shortUrlService.getUrlClickStats()).thenReturn(stats);

0 commit comments

Comments
 (0)