Просмотр исходного кода

fix:完善API的权限校验;使用具体对象类型代替Map作为参数的传递和响应;修复用户管理模块api的用户新增、用户修改中字段未全覆盖、密码未进行加密的逻辑错误;

yangyi 2 дней назад
Родитель
Сommit
33c8da2efe
34 измененных файлов с 608 добавлено и 180 удалено
  1. 4 0
      AGENTS.md
  2. 3 2
      src/main/java/space/anyi/serve/config/SecurityConfig.java
  3. 4 5
      src/main/java/space/anyi/serve/controller/AttachmentController.java
  4. 19 2
      src/main/java/space/anyi/serve/controller/MetaController.java
  5. 4 5
      src/main/java/space/anyi/serve/controller/NotificationController.java
  6. 10 14
      src/main/java/space/anyi/serve/controller/PostController.java
  7. 7 12
      src/main/java/space/anyi/serve/controller/RealnameAuthController.java
  8. 0 38
      src/main/java/space/anyi/serve/controller/SystemConfigController.java
  9. 6 14
      src/main/java/space/anyi/serve/controller/TipOrderController.java
  10. 26 21
      src/main/java/space/anyi/serve/controller/UserController.java
  11. 25 42
      src/main/java/space/anyi/serve/controller/WalletController.java
  12. 18 0
      src/main/java/space/anyi/serve/entity/attachment/UploadVo.java
  13. 1 0
      src/main/java/space/anyi/serve/entity/meta/Meta.java
  14. 72 0
      src/main/java/space/anyi/serve/entity/meta/OssConfigDto.java
  15. 18 0
      src/main/java/space/anyi/serve/entity/notification/UnreadCountVo.java
  16. 14 0
      src/main/java/space/anyi/serve/entity/order/CreateTipOrderDto.java
  17. 31 0
      src/main/java/space/anyi/serve/entity/order/CreateTipOrderVo.java
  18. 18 0
      src/main/java/space/anyi/serve/entity/post/CreatePostVo.java
  19. 14 0
      src/main/java/space/anyi/serve/entity/post/UpdateHitStatusDto.java
  20. 14 0
      src/main/java/space/anyi/serve/entity/post/UpdateViewCountDto.java
  21. 19 0
      src/main/java/space/anyi/serve/entity/realname/RealnameReviewDto.java
  22. 30 0
      src/main/java/space/anyi/serve/entity/realname/RealnameSubmitDto.java
  23. 53 0
      src/main/java/space/anyi/serve/entity/user/ProfileVo.java
  24. 14 0
      src/main/java/space/anyi/serve/entity/user/UpdateRoleDto.java
  25. 19 2
      src/main/java/space/anyi/serve/entity/user/UserDto.java
  26. 10 0
      src/main/java/space/anyi/serve/entity/user/UserVo.java
  27. 26 0
      src/main/java/space/anyi/serve/entity/wallet/AdminRechargeDto.java
  28. 15 0
      src/main/java/space/anyi/serve/entity/wallet/RechargeDto.java
  29. 19 0
      src/main/java/space/anyi/serve/entity/wallet/WalletVo.java
  30. 31 0
      src/main/java/space/anyi/serve/entity/wallet/WithdrawApplyVo.java
  31. 15 0
      src/main/java/space/anyi/serve/entity/wallet/WithdrawDto.java
  32. 19 0
      src/main/java/space/anyi/serve/entity/wallet/WithdrawReviewDto.java
  33. 15 11
      src/main/java/space/anyi/serve/filter/JwtAuthenticationFilter.java
  34. 15 12
      src/main/java/space/anyi/serve/service/impl/UserServiceImpl.java

+ 4 - 0
AGENTS.md

@@ -36,4 +36,8 @@ export databaseHost=localhost databaseUsername=postgres databasePassword=postgre
 - `application.yaml` uses `${placeholder}` for secrets; never hardcode credentials.
 - SQL logging to stdout via MyBatis-Plus `log-impl: StdOutImpl`.
 - Actuator endpoints fully exposed (`management.endpoints.web.exposure.include: "*"`).
+- **No `/api` prefix**: Controller `@RequestMapping` paths must NOT use `/api/` prefix (e.g., `"posts"`, `"wallet"`, not `"api/posts"`).
+- **API contracts**: Request bodies must be DTO classes (in `entity/*/`), not `Map`. Responses must be VO classes (in `entity/*/`), not `Map`.
+- **OpenAPI**: Every DTO and VO class must have `@Schema(description = "...")`. Every DTO field must have `@Schema` and `jakarta.validation` annotations (`@NotBlank`, `@NotNull`, `@Size`, etc.) with Chinese `message`.
+- **Controller return types**: Always use `Response<T>` with a concrete generic type (DTO or VO). Never raw `Response` or `Response<Map<...>>`.
 - No Dockerfile, no CI, no Makefile.

+ 3 - 2
src/main/java/space/anyi/serve/config/SecurityConfig.java

@@ -53,8 +53,9 @@ public class SecurityConfig {
                 .authorizeHttpRequests(authorize -> authorize
                         .requestMatchers("/auth/login", "/auth/register").permitAll()
                         .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
-                        .requestMatchers(HttpMethod.GET,"/meta/**").permitAll()
-                        .requestMatchers(HttpMethod.POST,"/meta/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/meta/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/posts/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/config/**").permitAll()
                         .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                         .anyRequest().authenticated()
                 )

+ 4 - 5
src/main/java/space/anyi/serve/controller/AttachmentController.java

@@ -7,14 +7,13 @@ import org.springframework.security.core.Authentication;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import space.anyi.serve.entity.Response;
+import space.anyi.serve.entity.attachment.UploadVo;
 import space.anyi.serve.entity.auth.JwtUserDetails;
 import space.anyi.serve.service.AttachmentService;
 
-import java.util.Map;
-
 @Tag(name = "AttachmentController", description = "附件上传")
 @RestController
-@RequestMapping("api/attachments")
+@RequestMapping("attachments")
 public class AttachmentController {
 
     private final AttachmentService attachmentService;
@@ -26,7 +25,7 @@ public class AttachmentController {
     @Operation(summary = "上传附件到OSS(需对接OSS配置)")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @PostMapping("upload")
-    public Response<Map<String, String>> upload(
+    public Response<UploadVo> upload(
             @RequestParam("file") MultipartFile file,
             @RequestParam(defaultValue = "post_image") String type,
             Authentication authentication) {
@@ -36,6 +35,6 @@ public class AttachmentController {
         // TODO: 对接 OSS 上传,当前返回占位 URL
         String url = "https://oss.example.com/" + System.currentTimeMillis() + "_" + file.getOriginalFilename();
         attachmentService.upload(userId, url, type);
-        return Response.ok(Map.of("url", url));
+        return Response.ok(new UploadVo(url));
     }
 }

+ 19 - 2
src/main/java/space/anyi/serve/controller/MetaController.java

@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import space.anyi.serve.entity.Response;
 import space.anyi.serve.entity.meta.Meta;
+import space.anyi.serve.entity.meta.OssConfigDto;
 import space.anyi.serve.entity.meta.WebsiteMetaDto;
 import space.anyi.serve.service.MetaService;
 
@@ -35,13 +36,13 @@ public class MetaController {
 
 
     @GetMapping("getWebsiteMeta")
-    public Response getWebsiteMeta() throws JsonProcessingException {
+    public Response<Object> getWebsiteMeta() throws JsonProcessingException {
         Meta meta = metaService.getMeta(Meta.WEBSITE_META_KEY);
         return Response.ok(meta.getValue());
     }
     @PreAuthorize("hasAnyRole('ROLE_admin')")
     @PostMapping("updateWebsiteMeta")
-    public Response updateWebsiteMeta(@Valid @RequestBody WebsiteMetaDto websiteMetaDto){
+    public Response<Void> updateWebsiteMeta(@Valid @RequestBody WebsiteMetaDto websiteMetaDto){
         Meta meta = new Meta();
         meta.setKey(Meta.WEBSITE_META_KEY);
         meta.setValue(websiteMetaDto);
@@ -49,4 +50,20 @@ public class MetaController {
         return Response.ok();
     }
 
+    @GetMapping("getOssConfig")
+    public Response<Object> getOssConfig() {
+        Meta meta = metaService.getMeta(Meta.OSS_CONFIG_KEY);
+        return Response.ok(meta != null ? meta.getValue() : null);
+    }
+
+    @PreAuthorize("hasAnyRole('ROLE_admin')")
+    @PostMapping("updateOssConfig")
+    public Response<Void> updateOssConfig(@Valid @RequestBody OssConfigDto ossConfigDto){
+        Meta meta = new Meta();
+        meta.setKey(Meta.OSS_CONFIG_KEY);
+        meta.setValue(ossConfigDto);
+        metaService.updateMeta(Meta.OSS_CONFIG_KEY, meta);
+        return Response.ok();
+    }
+
 }

+ 4 - 5
src/main/java/space/anyi/serve/controller/NotificationController.java

@@ -8,15 +8,14 @@ import org.springframework.web.bind.annotation.*;
 import space.anyi.serve.entity.Response;
 import space.anyi.serve.entity.auth.JwtUserDetails;
 import space.anyi.serve.entity.notification.NotificationVo;
+import space.anyi.serve.entity.notification.UnreadCountVo;
 import space.anyi.serve.service.NotificationService;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 @Tag(name = "NotificationController", description = "系统通知")
 @RestController
-@RequestMapping("api/notifications")
+@RequestMapping("notifications")
 public class NotificationController {
 
     private final NotificationService notificationService;
@@ -37,11 +36,11 @@ public class NotificationController {
     @Operation(summary = "获取未读数")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @GetMapping("unread-count")
-    public Response<Map<String, Long>> getUnreadCount(Authentication authentication) {
+    public Response<UnreadCountVo> getUnreadCount(Authentication authentication) {
         JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
         Long userId = details.getUser().getId();
         long count = notificationService.getUnreadCount(userId);
-        return Response.ok(Map.of("count", count));
+        return Response.ok(new UnreadCountVo(count));
     }
 
     @Operation(summary = "标记已读")

+ 10 - 14
src/main/java/space/anyi/serve/controller/PostController.java

@@ -10,19 +10,16 @@ import org.springframework.web.bind.annotation.*;
 import space.anyi.serve.entity.PageVo;
 import space.anyi.serve.entity.Response;
 import space.anyi.serve.entity.auth.JwtUserDetails;
-import space.anyi.serve.entity.post.Post;
-import space.anyi.serve.entity.post.PostDto;
-import space.anyi.serve.entity.post.PostVo;
+import space.anyi.serve.entity.post.*;
 import space.anyi.serve.entity.user.User;
 import space.anyi.serve.service.PostService;
 import space.anyi.serve.service.UserService;
 
 import java.util.List;
-import java.util.Map;
 
 @Tag(name = "PostController", description = "帖子管理")
 @RestController
-@RequestMapping("api/posts")
+@RequestMapping("posts")
 public class PostController {
 
     private final PostService postService;
@@ -66,7 +63,7 @@ public class PostController {
     @Operation(summary = "创建帖子(仅专家/管理员)")
     @PreAuthorize("hasAnyRole('ROLE_expert', 'ROLE_admin')")
     @PostMapping
-    public Response<Map<String, Object>> createPost(@Valid @RequestBody PostDto dto, Authentication authentication) {
+    public Response<CreatePostVo> createPost(@Valid @RequestBody PostDto dto, Authentication authentication) {
         JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
         User currentUser = userService.queryById(details.getUser().getId());
 
@@ -78,15 +75,15 @@ public class PostController {
         post.setPrice(dto.getPrice());
         post.setExpireTime(dto.getExpireTime());
         Long postId = postService.createPost(post);
-        return Response.ok(Map.of("id", postId.toString()));
+        return Response.ok(new CreatePostVo(postId.toString()));
     }
 
     @Operation(summary = "设置命中状态(管理员)")
     @PreAuthorize("hasRole('ROLE_admin')")
     @PutMapping("{id}/hit-status")
-    public Response<Void> updateHitStatus(@PathVariable Long id, @RequestBody Map<String, String> body) {
-        String hitStatus = body.get("hitStatus");
-        if (hitStatus == null || (!hitStatus.equals("pending") && !hitStatus.equals("hit") && !hitStatus.equals("miss"))) {
+    public Response<Void> updateHitStatus(@PathVariable Long id, @Valid @RequestBody UpdateHitStatusDto dto) {
+        String hitStatus = dto.getHitStatus();
+        if (!hitStatus.equals("pending") && !hitStatus.equals("hit") && !hitStatus.equals("miss")) {
             return Response.error("无效的命中状态");
         }
         postService.updateHitStatus(id, hitStatus);
@@ -96,10 +93,9 @@ public class PostController {
     @Operation(summary = "修改查看人数(管理员)")
     @PreAuthorize("hasRole('ROLE_admin')")
     @PutMapping("{id}/view-count")
-    public Response<Void> updateViewCount(@PathVariable Long id, @RequestBody Map<String, Integer> body) {
-        Integer viewCount = body.get("viewCount");
-        if (viewCount == null || viewCount < 0) return Response.error("无效的查看人数");
-        postService.updateViewCount(id, viewCount);
+    public Response<Void> updateViewCount(@PathVariable Long id, @Valid @RequestBody UpdateViewCountDto dto) {
+        if (dto.getViewCount() < 0) return Response.error("无效的查看人数");
+        postService.updateViewCount(id, dto.getViewCount());
         return Response.ok();
     }
 

+ 7 - 12
src/main/java/space/anyi/serve/controller/RealnameAuthController.java

@@ -8,16 +8,14 @@ import org.springframework.security.core.Authentication;
 import org.springframework.web.bind.annotation.*;
 import space.anyi.serve.entity.Response;
 import space.anyi.serve.entity.auth.JwtUserDetails;
-import space.anyi.serve.entity.realname.RealnameAuth;
-import space.anyi.serve.entity.realname.RealnameAuthVo;
+import space.anyi.serve.entity.realname.*;
 import space.anyi.serve.service.RealnameAuthService;
 
 import java.util.List;
-import java.util.Map;
 
 @Tag(name = "RealnameAuthController", description = "实名认证")
 @RestController
-@RequestMapping("api/realname")
+@RequestMapping("realname")
 public class RealnameAuthController {
 
     private final RealnameAuthService realnameAuthService;
@@ -29,11 +27,11 @@ public class RealnameAuthController {
     @Operation(summary = "提交实名认证")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @PostMapping
-    public Response<Void> submit(@Valid @RequestBody Map<String, String> body, Authentication authentication) {
+    public Response<Void> submit(@Valid @RequestBody RealnameSubmitDto dto, Authentication authentication) {
         JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
         Long userId = details.getUser().getId();
-        realnameAuthService.submit(userId, body.get("realName"), body.get("idCard"),
-                body.get("idCardFront"), body.get("idCardBack"));
+        realnameAuthService.submit(userId, dto.getRealName(), dto.getIdCard(),
+                dto.getIdCardFront(), dto.getIdCardBack());
         return Response.ok();
     }
 
@@ -50,11 +48,8 @@ public class RealnameAuthController {
     @Operation(summary = "管理员审核实名认证")
     @PreAuthorize("hasRole('ROLE_admin')")
     @PutMapping("{id}/review")
-    public Response<Void> review(@PathVariable Long id, @RequestBody Map<String, Object> body) {
-        Boolean approved = (Boolean) body.get("approved");
-        String rejectReason = (String) body.get("rejectReason");
-        if (approved == null) return Response.error("请指定审核结果");
-        realnameAuthService.review(id, approved, rejectReason);
+    public Response<Void> review(@PathVariable Long id, @Valid @RequestBody RealnameReviewDto dto) {
+        realnameAuthService.review(id, dto.getApproved(), dto.getRejectReason());
         return Response.ok();
     }
 

+ 0 - 38
src/main/java/space/anyi/serve/controller/SystemConfigController.java

@@ -1,38 +0,0 @@
-package space.anyi.serve.controller;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-import space.anyi.serve.entity.Response;
-import space.anyi.serve.entity.meta.Meta;
-import space.anyi.serve.service.MetaService;
-
-import java.util.Map;
-
-@Tag(name = "SystemConfigController", description = "系统配置")
-@RestController
-@RequestMapping("api/config")
-public class SystemConfigController {
-
-    private final MetaService metaService;
-
-    public SystemConfigController(MetaService metaService) {
-        this.metaService = metaService;
-    }
-
-    @Operation(summary = "获取配置")
-    @GetMapping("{key}")
-    public Response<Object> getConfig(@PathVariable String key) {
-        Meta meta = metaService.getMeta(key);
-        return Response.ok(meta != null ? meta.getValue() : null);
-    }
-
-    @Operation(summary = "更新配置(管理员)")
-    @PreAuthorize("hasRole('ROLE_admin')")
-    @PutMapping("{key}")
-    public Response<Void> updateConfig(@PathVariable String key, @RequestBody Object value) {
-        metaService.updateMetaValue(key, value);
-        return Response.ok();
-    }
-}

+ 6 - 14
src/main/java/space/anyi/serve/controller/TipOrderController.java

@@ -10,16 +10,14 @@ import org.springframework.web.bind.annotation.*;
 import space.anyi.serve.entity.PageVo;
 import space.anyi.serve.entity.Response;
 import space.anyi.serve.entity.auth.JwtUserDetails;
-import space.anyi.serve.entity.order.OrderTip;
-import space.anyi.serve.entity.order.OrderTipVo;
+import space.anyi.serve.entity.order.*;
 import space.anyi.serve.service.TipOrderService;
 
 import java.util.List;
-import java.util.Map;
 
 @Tag(name = "TipOrderController", description = "打赏订单")
 @RestController
-@RequestMapping("api/orders")
+@RequestMapping("orders")
 public class TipOrderController {
 
     private final TipOrderService tipOrderService;
@@ -31,21 +29,15 @@ public class TipOrderController {
     @Operation(summary = "打赏并即时扣款")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @PostMapping("tip")
-    public Response<Map<String, Object>> createAndPay(
-            @Valid @RequestBody Map<String, Long> body,
+    public Response<CreateTipOrderVo> createAndPay(
+            @Valid @RequestBody CreateTipOrderDto dto,
             @RequestHeader("X-Request-Id") String requestId,
             Authentication authentication) {
         JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
         Long userId = details.getUser().getId();
-        Long postId = body.get("postId");
-        if (postId == null) return Response.error("postId不能为空");
 
-        OrderTip order = tipOrderService.createAndPay(userId, postId, requestId);
-        return Response.ok(Map.of(
-                "orderId", order.getId().toString(),
-                "status", order.getStatus(),
-                "amount", order.getAmount()
-        ));
+        OrderTip order = tipOrderService.createAndPay(userId, dto.getPostId(), requestId);
+        return Response.ok(new CreateTipOrderVo(order.getId().toString(), order.getStatus(), order.getAmount()));
     }
 
     @Operation(summary = "获取我的打赏订单列表")

+ 26 - 21
src/main/java/space/anyi/serve/controller/UserController.java

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import org.springframework.beans.BeanUtils;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 import space.anyi.serve.entity.PageVo;
 import space.anyi.serve.entity.Response;
@@ -18,9 +19,8 @@ import space.anyi.serve.service.UserService;
 import space.anyi.serve.service.WalletService;
 import space.anyi.serve.service.UserProfileService;
 
-import java.util.HashMap;
+import java.math.BigDecimal;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 
@@ -36,11 +36,13 @@ public class UserController {
     private final UserService userService;
     private final WalletService walletService;
     private final UserProfileService userProfileService;
+    private final PasswordEncoder passwordEncoder;
 
-    public UserController(UserService userService, WalletService walletService, UserProfileService userProfileService) {
+    public UserController(UserService userService, WalletService walletService, UserProfileService userProfileService, PasswordEncoder passwordEncoder) {
         this.userService = userService;
         this.walletService = walletService;
         this.userProfileService = userProfileService;
+        this.passwordEncoder = passwordEncoder;
     }
 
     /**
@@ -110,6 +112,9 @@ public class UserController {
         User user = new User();
         BeanUtils.copyProperties(userDto,user);
         user.setId(Long.valueOf(userDto.getId()));
+        if (userDto.getPassword() != null && !userDto.getPassword().isEmpty()) {
+            user.setPassword(passwordEncoder.encode(userDto.getPassword()));
+        }
         return Response.ok(this.userService.update(user));
     }
 
@@ -133,7 +138,7 @@ public class UserController {
      */
     @PreAuthorize("hasRole('ROLE_admin')")
     @PutMapping("/updateStatus")
-    public Response updateUserStatus(@Valid@RequestBody UpdateUserStatusDto dto){
+    public Response<Boolean> updateUserStatus(@Valid@RequestBody UpdateUserStatusDto dto){
         User user = new User();
         BeanUtils.copyProperties(dto,user);
         user.setId(Long.valueOf(dto.getId()));
@@ -147,7 +152,7 @@ public class UserController {
      */
     @PreAuthorize("hasAnyRole('ROLE_admin', 'ROLE_user')")
     @PutMapping("/updateAvatar")
-    public Response updateUserAvatar(@Valid@RequestBody UpdateUserAvatarDto dto){
+    public Response<Boolean> updateUserAvatar(@Valid@RequestBody UpdateUserAvatarDto dto){
         User user = new User();
         BeanUtils.copyProperties(dto,user);
         user.setId(Long.valueOf(dto.getId()));
@@ -161,7 +166,7 @@ public class UserController {
      */
     @PreAuthorize("hasAnyRole('ROLE_admin', 'ROLE_user')")
     @PutMapping("/updatePassword")
-    public Response updatePassword(@Valid @RequestBody UpdateUserPasswordDto dto){
+    public Response<Boolean> updatePassword(@Valid @RequestBody UpdateUserPasswordDto dto){
         User user = new User();
         BeanUtils.copyProperties(dto,user);
         user.setId(Long.valueOf(dto.getId()));
@@ -170,9 +175,9 @@ public class UserController {
 
     @PreAuthorize("hasRole('ROLE_admin')")
     @PutMapping("/{id}/role")
-    public Response<Void> updateRole(@PathVariable Long id, @RequestBody Map<String, String> body) {
-        String role = body.get("role");
-        if (role == null || (!role.equals("user") && !role.equals("expert") && !role.equals("admin"))) {
+    public Response<Void> updateRole(@PathVariable Long id, @Valid @RequestBody UpdateRoleDto dto) {
+        String role = dto.getRole();
+        if (!role.equals("user") && !role.equals("expert") && !role.equals("admin")) {
             return Response.error("无效的角色值");
         }
         User user = userService.queryById(id);
@@ -184,7 +189,7 @@ public class UserController {
 
     @GetMapping("/profile")
     @PreAuthorize("hasAnyRole('ROLE_admin', 'ROLE_user', 'ROLE_expert')")
-    public Response<Map<String, Object>> profile(org.springframework.security.core.Authentication authentication) {
+    public Response<ProfileVo> profile(org.springframework.security.core.Authentication authentication) {
         org.springframework.security.core.userdetails.UserDetails principal =
                 (org.springframework.security.core.userdetails.UserDetails) authentication.getPrincipal();
         space.anyi.serve.entity.auth.JwtUserDetails userDetails = (space.anyi.serve.entity.auth.JwtUserDetails) principal;
@@ -194,17 +199,17 @@ public class UserController {
         Wallet wallet = walletService.getByUserId(userId);
         UserProfile profile = userProfileService.getByUserId(userId);
 
-        Map<String, Object> result = new HashMap<>();
-        result.put("id", user.getId().toString());
-        result.put("account", user.getAccount());
-        result.put("username", user.getUsername());
-        result.put("avatar", user.getAvatar());
-        result.put("role", user.getRole());
-        result.put("phoneNumber", user.getPhoneNumber());
-        result.put("balance", wallet != null ? wallet.getBalance() : 0);
-        result.put("level", profile != null ? profile.getLevel() : "gold");
-        result.put("isRealname", profile != null ? profile.getIsRealname() : false);
-        return Response.ok(result);
+        ProfileVo vo = new ProfileVo();
+        vo.setId(user.getId().toString());
+        vo.setAccount(user.getAccount());
+        vo.setUsername(user.getUsername());
+        vo.setAvatar(user.getAvatar());
+        vo.setRole(user.getRole());
+        vo.setPhoneNumber(user.getPhoneNumber());
+        vo.setBalance(wallet != null ? wallet.getBalance() : BigDecimal.ZERO);
+        vo.setLevel(profile != null ? profile.getLevel() : "gold");
+        vo.setIsRealname(profile != null ? profile.getIsRealname() : false);
+        return Response.ok(vo);
     }
 }
 

+ 25 - 42
src/main/java/space/anyi/serve/controller/WalletController.java

@@ -8,19 +8,15 @@ import org.springframework.security.core.Authentication;
 import org.springframework.web.bind.annotation.*;
 import space.anyi.serve.entity.Response;
 import space.anyi.serve.entity.auth.JwtUserDetails;
-import space.anyi.serve.entity.wallet.Wallet;
-import space.anyi.serve.entity.wallet.WalletTransaction;
-import space.anyi.serve.entity.wallet.WalletTransactionVo;
+import space.anyi.serve.entity.wallet.*;
 import space.anyi.serve.service.WalletService;
 
 import java.math.BigDecimal;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 @Tag(name = "WalletController", description = "钱包管理")
 @RestController
-@RequestMapping("api/wallet")
+@RequestMapping("wallet")
 public class WalletController {
 
     private final WalletService walletService;
@@ -32,53 +28,47 @@ public class WalletController {
     @Operation(summary = "查询余额")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @GetMapping("balance")
-    public Response<Map<String, Object>> getBalance(Authentication authentication) {
+    public Response<WalletVo> getBalance(Authentication authentication) {
         JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
         Long userId = details.getUser().getId();
         Wallet wallet = walletService.getByUserId(userId);
-        Map<String, Object> result = new HashMap<>();
-        result.put("balance", wallet != null ? wallet.getBalance() : BigDecimal.ZERO);
-        return Response.ok(result);
+        return Response.ok(new WalletVo(wallet != null ? wallet.getBalance() : BigDecimal.ZERO));
     }
 
     @Operation(summary = "充值(预留接口,待对接支付平台)")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @PostMapping("recharge")
-    public Response<Map<String, Object>> recharge(@RequestBody Map<String, BigDecimal> body, Authentication authentication) {
-        JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
-        Long userId = details.getUser().getId();
-        BigDecimal amount = body.get("amount");
-        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+    public Response<String> recharge(@Valid @RequestBody RechargeDto dto, Authentication authentication) {
+        if (dto.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
             return Response.error("充值金额必须大于0");
         }
-        walletService.addBalance(userId, amount, "recharge", "用户充值");
-        return Response.ok(Map.of("message", "充值成功"));
+        JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
+        Long userId = details.getUser().getId();
+        walletService.addBalance(userId, dto.getAmount(), "recharge", "用户充值");
+        return Response.ok("充值成功");
     }
 
     @Operation(summary = "管理员代充值")
     @PreAuthorize("hasRole('ROLE_admin')")
     @PostMapping("admin-recharge")
-    public Response<Map<String, Object>> adminRecharge(@RequestBody Map<String, Object> body) {
-        Long userId = Long.valueOf(body.get("userId").toString());
-        BigDecimal amount = new BigDecimal(body.get("amount").toString());
-        String remark = (String) body.getOrDefault("remark", "管理员代充值");
-        if (amount.compareTo(BigDecimal.ZERO) <= 0) return Response.error("金额必须大于0");
-        walletService.addBalance(userId, amount, "admin_adjust", remark);
-        return Response.ok(Map.of("message", "充值成功"));
+    public Response<String> adminRecharge(@Valid @RequestBody AdminRechargeDto dto) {
+        if (dto.getAmount().compareTo(BigDecimal.ZERO) <= 0) return Response.error("金额必须大于0");
+        String remark = dto.getRemark() != null ? dto.getRemark() : "管理员代充值";
+        walletService.addBalance(dto.getUserId(), dto.getAmount(), "admin_adjust", remark);
+        return Response.ok("充值成功");
     }
 
     @Operation(summary = "提现申请")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @PostMapping("withdraw")
-    public Response<Map<String, Object>> applyWithdraw(@RequestBody Map<String, BigDecimal> body, Authentication authentication) {
-        JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
-        Long userId = details.getUser().getId();
-        BigDecimal amount = body.get("amount");
-        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+    public Response<WithdrawApplyVo> applyWithdraw(@Valid @RequestBody WithdrawDto dto, Authentication authentication) {
+        if (dto.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
             return Response.error("提现金额必须大于0");
         }
-        WalletTransaction tx = walletService.applyWithdraw(userId, amount);
-        return Response.ok(Map.of("transactionId", tx.getId().toString(), "status", tx.getStatus()));
+        JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
+        Long userId = details.getUser().getId();
+        WalletTransaction tx = walletService.applyWithdraw(userId, dto.getAmount());
+        return Response.ok(new WithdrawApplyVo(tx.getId().toString(), tx.getStatus(), tx.getAmount()));
     }
 
     @Operation(summary = "获取待审核提现列表(管理员)")
@@ -94,28 +84,21 @@ public class WalletController {
     @Operation(summary = "审核提现(管理员)")
     @PreAuthorize("hasRole('ROLE_admin')")
     @PutMapping("withdraw/{id}/review")
-    public Response<Void> reviewWithdraw(@PathVariable Long id, @RequestBody Map<String, Object> body) {
-        Boolean approved = (Boolean) body.get("approved");
-        String rejectReason = (String) body.get("rejectReason");
-        if (approved == null) return Response.error("请指定审核结果");
-        walletService.reviewWithdraw(id, approved, rejectReason);
+    public Response<Void> reviewWithdraw(@PathVariable Long id, @Valid @RequestBody WithdrawReviewDto dto) {
+        walletService.reviewWithdraw(id, dto.getApproved(), dto.getRejectReason());
         return Response.ok();
     }
 
     @Operation(summary = "资金明细")
     @PreAuthorize("hasAnyRole('ROLE_user', 'ROLE_expert', 'ROLE_admin')")
     @GetMapping("transactions")
-    public Response<Map<String, Object>> getTransactions(
+    public Response<List<WalletTransactionVo>> getTransactions(
             @RequestParam(defaultValue = "1") int pageNum,
             @RequestParam(defaultValue = "10") int pageSize,
             Authentication authentication) {
         JwtUserDetails details = (JwtUserDetails) authentication.getPrincipal();
         Long userId = details.getUser().getId();
         List<WalletTransaction> list = walletService.getTransactions(userId, pageNum, pageSize);
-        List<WalletTransactionVo> vos = WalletTransactionVo.from(list);
-        Map<String, Object> result = new HashMap<>();
-        result.put("list", vos);
-        result.put("total", vos.size());
-        return Response.ok(result);
+        return Response.ok(WalletTransactionVo.from(list));
     }
 }

+ 18 - 0
src/main/java/space/anyi/serve/entity/attachment/UploadVo.java

@@ -0,0 +1,18 @@
+package space.anyi.serve.entity.attachment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "附件上传结果视图")
+public class UploadVo {
+    @Schema(description = "文件URL")
+    private String url;
+
+    public UploadVo() {}
+
+    public UploadVo(String url) {
+        this.url = url;
+    }
+
+    public String getUrl() { return url; }
+    public void setUrl(String url) { this.url = url; }
+}

+ 1 - 0
src/main/java/space/anyi/serve/entity/meta/Meta.java

@@ -14,6 +14,7 @@ import jakarta.validation.constraints.Size;
 @Schema(description = "元数据实体,存储键值对形式的配置")
 public class Meta {
     public static final String WEBSITE_META_KEY = "website_config";
+    public static final String OSS_CONFIG_KEY = "oss";
     /**
      * 自增主键,唯一标识一条元数据记录
      */

+ 72 - 0
src/main/java/space/anyi/serve/entity/meta/OssConfigDto.java

@@ -0,0 +1,72 @@
+package space.anyi.serve.entity.meta;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "OSS配置DTO,用于更新OSS存储配置信息")
+public class OssConfigDto {
+    @Schema(description = "Endpoint")
+    String endpoint = "";
+
+    @Schema(description = "Region")
+    String region = "";
+
+    @Schema(description = "Bucket")
+    String bucket = "";
+
+    @Schema(description = "Access Key")
+    String accessKey = "";
+
+    @Schema(description = "Secret Key")
+    String secretKey = "";
+
+    @Schema(description = "公开访问域名")
+    String publicDomain = "";
+
+    public String getEndpoint() {
+        return endpoint;
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public String getRegion() {
+        return region;
+    }
+
+    public void setRegion(String region) {
+        this.region = region;
+    }
+
+    public String getBucket() {
+        return bucket;
+    }
+
+    public void setBucket(String bucket) {
+        this.bucket = bucket;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    public String getPublicDomain() {
+        return publicDomain;
+    }
+
+    public void setPublicDomain(String publicDomain) {
+        this.publicDomain = publicDomain;
+    }
+}

+ 18 - 0
src/main/java/space/anyi/serve/entity/notification/UnreadCountVo.java

@@ -0,0 +1,18 @@
+package space.anyi.serve.entity.notification;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "未读通知数量视图")
+public class UnreadCountVo {
+    @Schema(description = "未读数量")
+    private Long count;
+
+    public UnreadCountVo() {}
+
+    public UnreadCountVo(Long count) {
+        this.count = count;
+    }
+
+    public Long getCount() { return count; }
+    public void setCount(Long count) { this.count = count; }
+}

+ 14 - 0
src/main/java/space/anyi/serve/entity/order/CreateTipOrderDto.java

@@ -0,0 +1,14 @@
+package space.anyi.serve.entity.order;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "打赏请求参数")
+public class CreateTipOrderDto {
+    @NotNull(message = "帖子ID不能为空")
+    @Schema(description = "帖子ID")
+    private Long postId;
+
+    public Long getPostId() { return postId; }
+    public void setPostId(Long postId) { this.postId = postId; }
+}

+ 31 - 0
src/main/java/space/anyi/serve/entity/order/CreateTipOrderVo.java

@@ -0,0 +1,31 @@
+package space.anyi.serve.entity.order;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
+
+@Schema(description = "打赏结果视图")
+public class CreateTipOrderVo {
+    @Schema(description = "订单ID")
+    private String orderId;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "金额")
+    private BigDecimal amount;
+
+    public CreateTipOrderVo() {}
+
+    public CreateTipOrderVo(String orderId, String status, BigDecimal amount) {
+        this.orderId = orderId;
+        this.status = status;
+        this.amount = amount;
+    }
+
+    public String getOrderId() { return orderId; }
+    public void setOrderId(String orderId) { this.orderId = orderId; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public BigDecimal getAmount() { return amount; }
+    public void setAmount(BigDecimal amount) { this.amount = amount; }
+}

+ 18 - 0
src/main/java/space/anyi/serve/entity/post/CreatePostVo.java

@@ -0,0 +1,18 @@
+package space.anyi.serve.entity.post;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "创建帖子结果视图")
+public class CreatePostVo {
+    @Schema(description = "帖子ID")
+    private String id;
+
+    public CreatePostVo() {}
+
+    public CreatePostVo(String id) {
+        this.id = id;
+    }
+
+    public String getId() { return id; }
+    public void setId(String id) { this.id = id; }
+}

+ 14 - 0
src/main/java/space/anyi/serve/entity/post/UpdateHitStatusDto.java

@@ -0,0 +1,14 @@
+package space.anyi.serve.entity.post;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+
+@Schema(description = "更新帖子命中状态请求参数")
+public class UpdateHitStatusDto {
+    @NotBlank(message = "命中状态不能为空")
+    @Schema(description = "命中状态(pending/hit/miss)")
+    private String hitStatus;
+
+    public String getHitStatus() { return hitStatus; }
+    public void setHitStatus(String hitStatus) { this.hitStatus = hitStatus; }
+}

+ 14 - 0
src/main/java/space/anyi/serve/entity/post/UpdateViewCountDto.java

@@ -0,0 +1,14 @@
+package space.anyi.serve.entity.post;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "修改帖子查看人数请求参数")
+public class UpdateViewCountDto {
+    @NotNull(message = "查看人数不能为空")
+    @Schema(description = "新的查看人数")
+    private Integer viewCount;
+
+    public Integer getViewCount() { return viewCount; }
+    public void setViewCount(Integer viewCount) { this.viewCount = viewCount; }
+}

+ 19 - 0
src/main/java/space/anyi/serve/entity/realname/RealnameReviewDto.java

@@ -0,0 +1,19 @@
+package space.anyi.serve.entity.realname;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "实名认证审核请求参数")
+public class RealnameReviewDto {
+    @NotNull(message = "请指定审核结果")
+    @Schema(description = "是否通过")
+    private Boolean approved;
+
+    @Schema(description = "驳回原因")
+    private String rejectReason;
+
+    public Boolean getApproved() { return approved; }
+    public void setApproved(Boolean approved) { this.approved = approved; }
+    public String getRejectReason() { return rejectReason; }
+    public void setRejectReason(String rejectReason) { this.rejectReason = rejectReason; }
+}

+ 30 - 0
src/main/java/space/anyi/serve/entity/realname/RealnameSubmitDto.java

@@ -0,0 +1,30 @@
+package space.anyi.serve.entity.realname;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+
+@Schema(description = "实名认证提交请求参数")
+public class RealnameSubmitDto {
+    @NotBlank(message = "真实姓名不能为空")
+    @Schema(description = "真实姓名")
+    private String realName;
+
+    @NotBlank(message = "身份证号不能为空")
+    @Schema(description = "身份证号")
+    private String idCard;
+
+    @Schema(description = "身份证正面照URL")
+    private String idCardFront;
+
+    @Schema(description = "身份证反面照URL")
+    private String idCardBack;
+
+    public String getRealName() { return realName; }
+    public void setRealName(String realName) { this.realName = realName; }
+    public String getIdCard() { return idCard; }
+    public void setIdCard(String idCard) { this.idCard = idCard; }
+    public String getIdCardFront() { return idCardFront; }
+    public void setIdCardFront(String idCardFront) { this.idCardFront = idCardFront; }
+    public String getIdCardBack() { return idCardBack; }
+    public void setIdCardBack(String idCardBack) { this.idCardBack = idCardBack; }
+}

+ 53 - 0
src/main/java/space/anyi/serve/entity/user/ProfileVo.java

@@ -0,0 +1,53 @@
+package space.anyi.serve.entity.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
+
+@Schema(description = "用户个人信息视图")
+public class ProfileVo {
+    @Schema(description = "用户ID")
+    private String id;
+
+    @Schema(description = "账号")
+    private String account;
+
+    @Schema(description = "用户名")
+    private String username;
+
+    @Schema(description = "头像URL")
+    private String avatar;
+
+    @Schema(description = "角色")
+    private String role;
+
+    @Schema(description = "手机号")
+    private String phoneNumber;
+
+    @Schema(description = "余额")
+    private BigDecimal balance;
+
+    @Schema(description = "会员等级")
+    private String level;
+
+    @Schema(description = "是否实名认证")
+    private Boolean isRealname;
+
+    public String getId() { return id; }
+    public void setId(String id) { this.id = id; }
+    public String getAccount() { return account; }
+    public void setAccount(String account) { this.account = account; }
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public String getAvatar() { return avatar; }
+    public void setAvatar(String avatar) { this.avatar = avatar; }
+    public String getRole() { return role; }
+    public void setRole(String role) { this.role = role; }
+    public String getPhoneNumber() { return phoneNumber; }
+    public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
+    public BigDecimal getBalance() { return balance; }
+    public void setBalance(BigDecimal balance) { this.balance = balance; }
+    public String getLevel() { return level; }
+    public void setLevel(String level) { this.level = level; }
+    public Boolean getIsRealname() { return isRealname; }
+    public void setIsRealname(Boolean isRealname) { this.isRealname = isRealname; }
+}

+ 14 - 0
src/main/java/space/anyi/serve/entity/user/UpdateRoleDto.java

@@ -0,0 +1,14 @@
+package space.anyi.serve.entity.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+
+@Schema(description = "更新用户角色请求参数")
+public class UpdateRoleDto {
+    @NotBlank(message = "角色不能为空")
+    @Schema(description = "角色(user/expert/admin)")
+    private String role;
+
+    public String getRole() { return role; }
+    public void setRole(String role) { this.role = role; }
+}

+ 19 - 2
src/main/java/space/anyi/serve/entity/user/UserDto.java

@@ -40,10 +40,11 @@ public class UserDto {
     @Size(min = 4,max = 32,message = "用户角色长度不能小于4且不能大于32")
     @Schema(description = "用户角色",minLength = 4,maxLength = 32)
     private String role;
-    @NotBlank(message = "用户手机号不能为空")
     @Size(min = 11,max = 11,message = "手机号长度只能为11")
-    @Schema(description = "用户角色",minLength = 11,maxLength = 11)
+    @Schema(description = "用户手机号",minLength = 11,maxLength = 11)
     private String phoneNumber;
+    @Schema(description = "用户头像地址")
+    private String avatar;
 
 
     public String getId() {
@@ -85,4 +86,20 @@ public class UserDto {
     public void setRole(String role) {
         this.role = role;
     }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
 }

+ 10 - 0
src/main/java/space/anyi/serve/entity/user/UserVo.java

@@ -79,6 +79,15 @@ public class UserVo {
     public void setEnable(Integer enable) {
         this.enable = enable;
     }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+
     public static UserVo form(User user){
         UserVo vo = new UserVo();
         if (Objects.isNull(user)) {
@@ -90,6 +99,7 @@ public class UserVo {
         vo.setRole(user.getRole());
         vo.setAvatar(user.getAvatar());
         vo.setEnable(user.getEnable());
+        vo.setPhoneNumber(user.getPhoneNumber());
         return vo;
     }
     public static List<UserVo> from(List<User> list){

+ 26 - 0
src/main/java/space/anyi/serve/entity/wallet/AdminRechargeDto.java

@@ -0,0 +1,26 @@
+package space.anyi.serve.entity.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+@Schema(description = "管理员代充值请求参数")
+public class AdminRechargeDto {
+    @NotNull(message = "用户ID不能为空")
+    @Schema(description = "目标用户ID")
+    private Long userId;
+
+    @NotNull(message = "充值金额不能为空")
+    @Schema(description = "充值金额")
+    private BigDecimal amount;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    public Long getUserId() { return userId; }
+    public void setUserId(Long userId) { this.userId = userId; }
+    public BigDecimal getAmount() { return amount; }
+    public void setAmount(BigDecimal amount) { this.amount = amount; }
+    public String getRemark() { return remark; }
+    public void setRemark(String remark) { this.remark = remark; }
+}

+ 15 - 0
src/main/java/space/anyi/serve/entity/wallet/RechargeDto.java

@@ -0,0 +1,15 @@
+package space.anyi.serve.entity.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+@Schema(description = "充值请求参数")
+public class RechargeDto {
+    @NotNull(message = "充值金额不能为空")
+    @Schema(description = "充值金额")
+    private BigDecimal amount;
+
+    public BigDecimal getAmount() { return amount; }
+    public void setAmount(BigDecimal amount) { this.amount = amount; }
+}

+ 19 - 0
src/main/java/space/anyi/serve/entity/wallet/WalletVo.java

@@ -0,0 +1,19 @@
+package space.anyi.serve.entity.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
+
+@Schema(description = "钱包信息视图")
+public class WalletVo {
+    @Schema(description = "余额")
+    private BigDecimal balance;
+
+    public WalletVo() {}
+
+    public WalletVo(BigDecimal balance) {
+        this.balance = balance;
+    }
+
+    public BigDecimal getBalance() { return balance; }
+    public void setBalance(BigDecimal balance) { this.balance = balance; }
+}

+ 31 - 0
src/main/java/space/anyi/serve/entity/wallet/WithdrawApplyVo.java

@@ -0,0 +1,31 @@
+package space.anyi.serve.entity.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
+
+@Schema(description = "提现申请结果视图")
+public class WithdrawApplyVo {
+    @Schema(description = "交易ID")
+    private String transactionId;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "金额")
+    private BigDecimal amount;
+
+    public WithdrawApplyVo() {}
+
+    public WithdrawApplyVo(String transactionId, String status, BigDecimal amount) {
+        this.transactionId = transactionId;
+        this.status = status;
+        this.amount = amount;
+    }
+
+    public String getTransactionId() { return transactionId; }
+    public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public BigDecimal getAmount() { return amount; }
+    public void setAmount(BigDecimal amount) { this.amount = amount; }
+}

+ 15 - 0
src/main/java/space/anyi/serve/entity/wallet/WithdrawDto.java

@@ -0,0 +1,15 @@
+package space.anyi.serve.entity.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+@Schema(description = "提现请求参数")
+public class WithdrawDto {
+    @NotNull(message = "提现金额不能为空")
+    @Schema(description = "提现金额")
+    private BigDecimal amount;
+
+    public BigDecimal getAmount() { return amount; }
+    public void setAmount(BigDecimal amount) { this.amount = amount; }
+}

+ 19 - 0
src/main/java/space/anyi/serve/entity/wallet/WithdrawReviewDto.java

@@ -0,0 +1,19 @@
+package space.anyi.serve.entity.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "提现审核请求参数")
+public class WithdrawReviewDto {
+    @NotNull(message = "请指定审核结果")
+    @Schema(description = "是否通过")
+    private Boolean approved;
+
+    @Schema(description = "驳回原因")
+    private String rejectReason;
+
+    public Boolean getApproved() { return approved; }
+    public void setApproved(Boolean approved) { this.approved = approved; }
+    public String getRejectReason() { return rejectReason; }
+    public void setRejectReason(String rejectReason) { this.rejectReason = rejectReason; }
+}

+ 15 - 11
src/main/java/space/anyi/serve/filter/JwtAuthenticationFilter.java

@@ -6,23 +6,26 @@ import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
+import space.anyi.serve.entity.auth.JwtUserDetails;
+import space.anyi.serve.entity.user.User;
 import space.anyi.serve.handler.security.JwtTokenProvider;
+import space.anyi.serve.mapper.UserMapper;
 
 import java.io.IOException;
-import java.util.List;
 
 @Component
 public class JwtAuthenticationFilter extends OncePerRequestFilter {
 
     private final JwtTokenProvider jwtTokenProvider;
+    private final UserMapper userMapper;
 
-    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
+    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserMapper userMapper) {
         this.jwtTokenProvider = jwtTokenProvider;
+        this.userMapper = userMapper;
     }
 
     @Override
@@ -33,14 +36,15 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
 
         if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
             Claims claims = jwtTokenProvider.parseToken(token);
-            String account = claims.getSubject();
-            String role = claims.get("role", String.class);
-
-            List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_" + role));
-
-            UsernamePasswordAuthenticationToken authentication =
-                    new UsernamePasswordAuthenticationToken(account, null, authorities);
-            SecurityContextHolder.getContext().setAuthentication(authentication);
+            Long userId = claims.get("userId", Long.class);
+
+            User user = userMapper.selectById(userId);
+            if (user != null) {
+                JwtUserDetails userDetails = new JwtUserDetails(user);
+                UsernamePasswordAuthenticationToken authentication =
+                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
         }
 
         filterChain.doFilter(request, response);

+ 15 - 12
src/main/java/space/anyi/serve/service/impl/UserServiceImpl.java

@@ -4,14 +4,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
-import space.anyi.serve.entity.Response;
-import space.anyi.serve.entity.auth.JwtUserDetails;
-import space.anyi.serve.handler.security.JwtTokenProvider;
-import space.anyi.serve.entity.auth.AuthTokenVo;
 import space.anyi.serve.entity.user.User;
 import space.anyi.serve.mapper.UserMapper;
 import space.anyi.serve.service.UserService;
@@ -79,9 +73,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements Use
     @Override
     public Boolean updatePassword(User user, String oldPassword) {
         User user1 = getById(user.getId());
-        if (!passwordEncoder.matches(oldPassword, user1.getPassword())) {
+        if (!passwordEncoder.matches(user.getPassword(), user1.getPassword())) {
             return false;
         }
+        user.setPassword(passwordEncoder.encode(oldPassword));
         return update(user);
     }
 
@@ -93,6 +88,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements Use
      */
     @Override
     public Boolean insert(User user) {
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
+                .eq(User::getAccount, user.getAccount());
+        if (count(wrapper) > 0) {
+            throw new IllegalArgumentException("账号已存在");
+        }
+        user.setPassword(passwordEncoder.encode(user.getPassword()));
         return save(user);
     }
 
@@ -106,11 +107,13 @@ public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements Use
     public Boolean update(User user) {
         LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<User>()
                 .eq(User::getId, user.getId())
-                .set(Objects.nonNull(user.getAccount()),User::getAccount, user.getAccount())
-                .set(Objects.nonNull(user.getUsername()),User::getUsername, user.getUsername())
-                .set(Objects.nonNull(user.getAvatar()), User::getAvatar,user.getAvatar())
-                .set(Objects.nonNull(user.getRole()),User::getRole, user.getRole())
-                .set(Objects.nonNull(user.getEnable()),User::getEnable, user.getEnable());
+                .set(Objects.nonNull(user.getAccount()), User::getAccount, user.getAccount())
+                .set(Objects.nonNull(user.getUsername()), User::getUsername, user.getUsername())
+                .set(Objects.nonNull(user.getAvatar()), User::getAvatar, user.getAvatar())
+                .set(Objects.nonNull(user.getRole()), User::getRole, user.getRole())
+                .set(Objects.nonNull(user.getEnable()), User::getEnable, user.getEnable())
+                .set(Objects.nonNull(user.getPhoneNumber()), User::getPhoneNumber, user.getPhoneNumber())
+                .set(Objects.nonNull(user.getPassword()), User::getPassword, user.getPassword());
         return this.update(updateWrapper);
     }