+
@@ -58,11 +64,12 @@ import { API_BASE_URL, toast } from '../main'
import { getToken, fetchCurrentUser, setToken } from '../utils/auth'
import BaseInput from '../components/BaseInput.vue'
import Dropdown from '../components/Dropdown.vue'
+import AvatarCropper from '../components/AvatarCropper.vue'
import { hatch } from 'ldrs'
hatch.register()
export default {
name: 'SettingsPageView',
- components: { BaseInput, Dropdown },
+ components: { BaseInput, Dropdown, AvatarCropper },
data() {
return {
username: '',
@@ -70,6 +77,8 @@ export default {
usernameError: '',
avatar: '',
avatarFile: null,
+ tempAvatar: '',
+ showCropper: false,
role: '',
publishMode: 'DIRECT',
passwordStrength: 'LOW',
@@ -100,15 +109,19 @@ export default {
methods: {
onAvatarChange(e) {
const file = e.target.files[0]
- this.avatarFile = file
if (file) {
const reader = new FileReader()
reader.onload = () => {
- this.avatar = reader.result
+ this.tempAvatar = reader.result
+ this.showCropper = true
}
reader.readAsDataURL(file)
}
},
+ onCropped({ file, url }) {
+ this.avatarFile = file
+ this.avatar = url
+ },
fetchPublishModes() {
return Promise.resolve([
{ id: 'DIRECT', name: '直接发布', icon: 'fas fa-bolt' },
From 9f1080eeb012483c9a0b16034861897b515ac4d6 Mon Sep 17 00:00:00 2001
From: Tim
Date: Mon, 4 Aug 2025 13:11:55 +0800
Subject: [PATCH 2/8] feat: cropper
---
frontend/src/components/AvatarCropper.vue | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/frontend/src/components/AvatarCropper.vue b/frontend/src/components/AvatarCropper.vue
index 96755cd5f..2cb0a1d62 100644
--- a/frontend/src/components/AvatarCropper.vue
+++ b/frontend/src/components/AvatarCropper.vue
@@ -83,7 +83,8 @@ export default {
left: 0;
right: 0;
bottom: 0;
- background: rgba(0, 0, 0, 0.8);
+ background-color: rgba(0, 0, 0, 0.8);
+ opacity: 1.0;
display: flex;
align-items: center;
justify-content: center;
@@ -91,7 +92,7 @@ export default {
}
.cropper-body {
- background: #fff;
+ background: var(--background-color);
padding: 10px;
border-radius: 6px;
display: flex;
@@ -118,15 +119,16 @@ export default {
.cropper-btn {
padding: 6px 12px;
- border: 1px solid #ccc;
- background: #fff;
border-radius: 4px;
+ color: var(--primary-color);
+ border: none;
+ background: transparent;
cursor: pointer;
}
.cropper-btn.primary {
background: var(--primary-color);
- color: #fff;
+ color: var(--text-color);
border-color: var(--primary-color);
}
From b30939dd7d7055d2fd0855d6cabd82c9be46610b Mon Sep 17 00:00:00 2001
From: Tim <135014430+nagisa77@users.noreply.github.com>
Date: Mon, 4 Aug 2025 13:15:01 +0800
Subject: [PATCH 3/8] chore: add hourly log rotation
---
backend/src/main/resources/logback-spring.xml | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 backend/src/main/resources/logback-spring.xml
diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml
new file mode 100644
index 000000000..f634b7664
--- /dev/null
+++ b/backend/src/main/resources/logback-spring.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
+
+
+
+
+ ${LOG_PATH}/app.log
+
+ ${LOG_PATH}/app-%d{yyyy-MM-dd-HH}.log
+ 72
+
+
+ %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
From a5899565b14a62a6b201e8b4f96283da960be2c8 Mon Sep 17 00:00:00 2001
From: Tim
Date: Mon, 4 Aug 2025 13:18:55 +0800
Subject: [PATCH 4/8] feat: add log save
---
.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index d87d478a5..979fc00df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ target
openisle.iml
node_modules
dist
-open-isle.env
\ No newline at end of file
+open-isle.env
+logs
\ No newline at end of file
From d50e8c0863db1688c1fcb15e4a06c4c01be9e808 Mon Sep 17 00:00:00 2001
From: Tim <135014430+nagisa77@users.noreply.github.com>
Date: Mon, 4 Aug 2025 14:22:55 +0800
Subject: [PATCH 5/8] fix: guard against null exception messages
---
.../controller/GlobalExceptionHandler.java | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java b/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
index 7af18210b..cc1106df0 100644
--- a/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
+++ b/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
@@ -7,30 +7,40 @@ import com.openisle.exception.FieldException;
import com.openisle.exception.NotFoundException;
import com.openisle.exception.RateLimitException;
+import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(FieldException.class)
public ResponseEntity> handleFieldException(FieldException ex) {
- return ResponseEntity.badRequest()
- .body(Map.of("error", ex.getMessage(), "field", ex.getField()));
+ Map body = new HashMap<>();
+ body.put("error", Objects.toString(ex.getMessage(), null));
+ body.put("field", ex.getField());
+ return ResponseEntity.badRequest().body(body);
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity> handleNotFoundException(NotFoundException ex) {
- return ResponseEntity.status(404).body(Map.of("error", ex.getMessage()));
+ Map body = new HashMap<>();
+ body.put("error", Objects.toString(ex.getMessage(), null));
+ return ResponseEntity.status(404).body(body);
}
@ExceptionHandler(RateLimitException.class)
public ResponseEntity> handleRateLimitException(RateLimitException ex) {
- return ResponseEntity.status(429).body(Map.of("error", ex.getMessage()));
+ Map body = new HashMap<>();
+ body.put("error", Objects.toString(ex.getMessage(), null));
+ return ResponseEntity.status(429).body(body);
}
@ExceptionHandler(Exception.class)
public ResponseEntity> handleException(Exception ex) {
- return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage()));
+ Map body = new HashMap<>();
+ body.put("error", Objects.toString(ex.getMessage(), null));
+ return ResponseEntity.badRequest().body(body);
}
}
From 4da16cd0717694f41f7c9680c0617dc72e92c3b4 Mon Sep 17 00:00:00 2001
From: tim
Date: Mon, 4 Aug 2025 14:25:43 +0800
Subject: [PATCH 6/8] Revert "fix: guard against null exception messages"
This reverts commit d50e8c0863db1688c1fcb15e4a06c4c01be9e808.
---
.../controller/GlobalExceptionHandler.java | 20 +++++--------------
1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java b/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
index cc1106df0..7af18210b 100644
--- a/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
+++ b/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
@@ -7,40 +7,30 @@ import com.openisle.exception.FieldException;
import com.openisle.exception.NotFoundException;
import com.openisle.exception.RateLimitException;
-import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(FieldException.class)
public ResponseEntity> handleFieldException(FieldException ex) {
- Map body = new HashMap<>();
- body.put("error", Objects.toString(ex.getMessage(), null));
- body.put("field", ex.getField());
- return ResponseEntity.badRequest().body(body);
+ return ResponseEntity.badRequest()
+ .body(Map.of("error", ex.getMessage(), "field", ex.getField()));
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity> handleNotFoundException(NotFoundException ex) {
- Map body = new HashMap<>();
- body.put("error", Objects.toString(ex.getMessage(), null));
- return ResponseEntity.status(404).body(body);
+ return ResponseEntity.status(404).body(Map.of("error", ex.getMessage()));
}
@ExceptionHandler(RateLimitException.class)
public ResponseEntity> handleRateLimitException(RateLimitException ex) {
- Map body = new HashMap<>();
- body.put("error", Objects.toString(ex.getMessage(), null));
- return ResponseEntity.status(429).body(body);
+ return ResponseEntity.status(429).body(Map.of("error", ex.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity> handleException(Exception ex) {
- Map body = new HashMap<>();
- body.put("error", Objects.toString(ex.getMessage(), null));
- return ResponseEntity.badRequest().body(body);
+ return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage()));
}
}
From 3ae27e6216ada4cb59ab2a344d3c199be9e00f21 Mon Sep 17 00:00:00 2001
From: Tim <135014430+nagisa77@users.noreply.github.com>
Date: Mon, 4 Aug 2025 14:26:12 +0800
Subject: [PATCH 7/8] Handle missing exception messages in global handler
---
.../com/openisle/controller/GlobalExceptionHandler.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java b/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
index 7af18210b..461486827 100644
--- a/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
+++ b/backend/src/main/java/com/openisle/controller/GlobalExceptionHandler.java
@@ -30,7 +30,11 @@ public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity> handleException(Exception ex) {
- return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage()));
+ String message = ex.getMessage();
+ if (message == null) {
+ message = ex.getClass().getSimpleName();
+ }
+ return ResponseEntity.badRequest().body(Map.of("error", message));
}
}
From f6d7020165f0786c5f8e4f6b95754b78559b62d6 Mon Sep 17 00:00:00 2001
From: WilliamColton
Date: Mon, 4 Aug 2025 16:29:21 +0800
Subject: [PATCH 8/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=98=BE=E7=A4=BA?=
=?UTF-8?q?=E6=9C=80=E5=90=8E=E8=AF=84=E8=AE=BA=E6=97=B6=E9=97=B4=E7=9A=84?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/java/com/openisle/controller/UserController.java | 4 +++-
.../main/java/com/openisle/repository/CommentRepository.java | 4 ++++
.../src/main/java/com/openisle/service/CommentService.java | 4 ++++
frontend/src/views/ProfileView.vue | 4 ++++
4 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/backend/src/main/java/com/openisle/controller/UserController.java b/backend/src/main/java/com/openisle/controller/UserController.java
index 1aef3a650..0501c07b1 100644
--- a/backend/src/main/java/com/openisle/controller/UserController.java
+++ b/backend/src/main/java/com/openisle/controller/UserController.java
@@ -75,7 +75,7 @@ public class UserController {
@PutMapping("/me")
public ResponseEntity> updateProfile(@RequestBody UpdateProfileDto dto,
- Authentication auth) {
+ Authentication auth) {
User user = userService.updateProfile(auth.getName(), dto.getUsername(), dto.getIntroduction());
return ResponseEntity.ok(Map.of(
"token", jwtService.generateToken(user.getUsername()),
@@ -239,6 +239,7 @@ public class UserController {
dto.setFollowing(subscriptionService.countSubscribed(user.getUsername()));
dto.setCreatedAt(user.getCreatedAt());
dto.setLastPostTime(postService.getLastPostTime(user.getUsername()));
+ dto.setLastCommentTime(commentService.getLastCommentTimeOfUserByUserId(user.getId()));
dto.setTotalViews(postService.getTotalViews(user.getUsername()));
dto.setVisitedDays(userVisitService.countVisits(user.getUsername()));
dto.setReadPosts(postReadService.countReads(user.getUsername()));
@@ -306,6 +307,7 @@ public class UserController {
private long following;
private java.time.LocalDateTime createdAt;
private java.time.LocalDateTime lastPostTime;
+ private java.time.LocalDateTime lastCommentTime;
private long totalViews;
private long visitedDays;
private long readPosts;
diff --git a/backend/src/main/java/com/openisle/repository/CommentRepository.java b/backend/src/main/java/com/openisle/repository/CommentRepository.java
index 1b4b8e092..12f20fecd 100644
--- a/backend/src/main/java/com/openisle/repository/CommentRepository.java
+++ b/backend/src/main/java/com/openisle/repository/CommentRepository.java
@@ -23,4 +23,8 @@ public interface CommentRepository extends JpaRepository {
@org.springframework.data.jpa.repository.Query("SELECT COUNT(c) FROM Comment c WHERE c.author.username = :username AND c.createdAt >= :start")
long countByAuthorAfter(@org.springframework.data.repository.query.Param("username") String username,
@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start);
+
+ @org.springframework.data.jpa.repository.Query("SELECT MAX(c.createdAt) FROM Comment c WHERE c.author.id = :userId")
+ java.time.LocalDateTime findLastCommentTimeOfUserByUserId(@org.springframework.data.repository.query.Param("userId") Long userId);
+
}
diff --git a/backend/src/main/java/com/openisle/service/CommentService.java b/backend/src/main/java/com/openisle/service/CommentService.java
index ddae361f9..96ed10f4e 100644
--- a/backend/src/main/java/com/openisle/service/CommentService.java
+++ b/backend/src/main/java/com/openisle/service/CommentService.java
@@ -76,6 +76,10 @@ public class CommentService {
return comment;
}
+ public java.time.LocalDateTime getLastCommentTimeOfUserByUserId(Long userId) { // 根据用户id查询该用户最后回复时间
+ return commentRepository.findLastCommentTimeOfUserByUserId(userId);
+ }
+
@Transactional
public Comment addReply(String username, Long parentId, String content) {
log.debug("addReply called by user {} for parent comment {}", username, parentId);
diff --git a/frontend/src/views/ProfileView.vue b/frontend/src/views/ProfileView.vue
index 0a9308cc3..538830a6c 100644
--- a/frontend/src/views/ProfileView.vue
+++ b/frontend/src/views/ProfileView.vue
@@ -44,6 +44,10 @@
最后发帖时间:
{{ formatDate(user.lastPostTime) }}
+