diff --git a/src/main/java/com/example/venue_reservation_service/controller/UserController.java b/src/main/java/com/example/venue_reservation_service/controller/UserController.java index b80c439..336a9cc 100644 --- a/src/main/java/com/example/venue_reservation_service/controller/UserController.java +++ b/src/main/java/com/example/venue_reservation_service/controller/UserController.java @@ -9,6 +9,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/user") @@ -39,8 +40,8 @@ public class UserController { @ApiOperation("用户个人信息完善") @PostMapping("/update") - public Result update(@RequestBody User user){ - return userService.userUpdate(user); + public Result update(@RequestBody UserInfo userInfo){ + return userService.userUpdate(userInfo); } @ApiOperation("获取用户列表") @@ -103,4 +104,16 @@ public class UserController { public Result passwd(@RequestBody PasswdDTO dto){ return userService.passwd(dto); } + + @ApiOperation("用户头像上传") + @PostMapping("/upload") + public Result upload(@RequestParam("id") Integer userId, @RequestParam("image")MultipartFile file){ + return userService.upload(userId, file); + } + + @ApiOperation("用户信息获取") + @GetMapping("/info/{id}") + public Result info(@PathVariable("id") Integer userId){ + return userService.getUserInfo(userId); + } } diff --git a/src/main/java/com/example/venue_reservation_service/domain/User.java b/src/main/java/com/example/venue_reservation_service/domain/User.java index 26f8d24..4c3ddcf 100644 --- a/src/main/java/com/example/venue_reservation_service/domain/User.java +++ b/src/main/java/com/example/venue_reservation_service/domain/User.java @@ -66,6 +66,24 @@ public class User implements Serializable { @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDateTime registerTime; + /** + * 用户头像 + */ + private String avatar; + + /** + * 是否上传头像图片 + */ + private Integer isUpload; + + /** + * 用户信息修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDateTime updateTime; + + @TableField(exist = false) private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/example/venue_reservation_service/dto/UserInfo.java b/src/main/java/com/example/venue_reservation_service/dto/UserInfo.java new file mode 100644 index 0000000..4e1f2cf --- /dev/null +++ b/src/main/java/com/example/venue_reservation_service/dto/UserInfo.java @@ -0,0 +1,15 @@ +package com.example.venue_reservation_service.dto; + +import lombok.Data; + +@Data +public class UserInfo { + + private Integer id; + + private String username; + + private String phone; + + private String email; +} diff --git a/src/main/java/com/example/venue_reservation_service/service/UserService.java b/src/main/java/com/example/venue_reservation_service/service/UserService.java index 5c08f96..2089358 100644 --- a/src/main/java/com/example/venue_reservation_service/service/UserService.java +++ b/src/main/java/com/example/venue_reservation_service/service/UserService.java @@ -4,6 +4,7 @@ import com.example.venue_reservation_service.domain.User; import com.baomidou.mybatisplus.extension.service.IService; import com.example.venue_reservation_service.dto.*; import com.example.venue_reservation_service.vo.Result; +import org.springframework.web.multipart.MultipartFile; /** * @author 31586 @@ -16,7 +17,7 @@ public interface UserService extends IService { Result adminLogin(AdminDTO dto); - Result userUpdate(User user); + Result userUpdate(UserInfo userInfo); Result getUserList(UserDTO dto); @@ -38,4 +39,7 @@ public interface UserService extends IService { Result passwd(PasswdDTO dto); + Result upload(Integer userId, MultipartFile file); + + Result getUserInfo(Integer userId); } diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java index a073b9e..c760071 100644 --- a/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java +++ b/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java @@ -1,5 +1,6 @@ package com.example.venue_reservation_service.service.impl; +import cn.hutool.core.lang.UUID; import cn.hutool.jwt.JWTUtil; import com.auth0.jwt.JWT; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -18,18 +19,19 @@ import com.example.venue_reservation_service.vo.QueryVO; import com.example.venue_reservation_service.vo.Result; import com.github.yulichang.wrapper.MPJLambdaWrapper; import jakarta.annotation.Resource; +import org.apache.commons.io.FilenameUtils; import org.checkerframework.checker.units.qual.A; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.UUID; /** * @author 31586 @@ -66,6 +68,11 @@ public class UserServiceImpl extends ServiceImpl @Value("${spring.application.name}") private String name; + @Value("${access.url}") + private String imgUrl; + + @Resource + private MinioUtil minioUtil; @Override public Result getOpenId(String code) { @@ -86,9 +93,10 @@ public class UserServiceImpl extends ServiceImpl user = new User(); user.setUsername("用户" + System.currentTimeMillis()); user.setOpenId(openid); - user.setType(0); + user.setType(0); // 表示普通用户 user.setAccount(LocalDate.now().getYear() +"-"+ System.currentTimeMillis()); user.setRegisterTime(LocalDateTime.now()); + user.setIsUpload(0); save(user); // 添加新的账户余额信息 @@ -98,6 +106,9 @@ public class UserServiceImpl extends ServiceImpl // 获取用户账号余额数据 Double money = balanceService.getOne(Wrappers.lambdaQuery().eq(Balance::getUserId, user.getId())) .getBalance(); + if(user.getIsUpload() == 1){ + user.setAvatar(imgUrl + user.getAvatar()); + } user.setBalance(money); LoginVo loginVo = new LoginVo(user, JwtUtil.createToken(user.getId())); return Result.ok(loginVo).message("登录成功"); @@ -133,23 +144,32 @@ public class UserServiceImpl extends ServiceImpl return Result.fail().message("账号密码不匹配"); } user.setAccessCode(""); - + if(user.getIsUpload() == 1){ + user.setAvatar(imgUrl + user.getAvatar()); + } LoginVo vo = new LoginVo(user, JwtUtil.createToken(user.getId())); return Result.ok(vo).message("登录成功"); } + /** + * 用户信息修改 + * 可修改内容:用户名(username)、电话(phone)、邮箱(email) + */ @Override - public Result userUpdate(User user) { - String username = getById(user.getId()).getUsername(); - if(!username.equals(user.getUsername())){ - // 表示用户修改了用户名 - User one = getOne(Wrappers.lambdaQuery() - .eq(User::getUsername, user.getUsername())); - if (Optional.ofNullable(one).isPresent()) { - return Result.fail().message("当前用户名已经被使用"); - } + public Result userUpdate(UserInfo userInfo) { + User user = getById(userInfo.getId()); + if(Optional.ofNullable(user).isEmpty()){ + return Result.fail().message("用户信息不存在"); } + user.setUsername(userInfo.getUsername()); + // TODO 校验手机号是否合法 + // 1. 手机号未被他人使用 + // 2. 符合国家标准 + user.setUserPhone(userInfo.getPhone()); + user.setEmail(userInfo.getEmail()); + user.setUpdateTime(LocalDateTime.now()); updateById(user); + user.setAccessCode(""); return Result.ok(user).message("用户信息修改成功"); } @@ -324,6 +344,54 @@ public class UserServiceImpl extends ServiceImpl updateById(user); return Result.ok().message("密码修改成功"); } + + @Override + public Result upload(Integer userId, MultipartFile file) { + User user = getById(userId); + if (Optional.ofNullable(user).isEmpty()) { + return Result.fail().message("用户信息不存在"); + } + // 检查是否上传头像 + if(user.getIsUpload() == 1){ + // 删除原来的图片 + try { + minioUtil.removeFile(user.getAvatar()); + }catch (Exception e){ + e.printStackTrace(); + return Result.fail().message("服务错误,请稍后重试"); + } + } + if (ImageValidator.validateImageFile(file)) { + // 生成新的名称 + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); // 获取文件扩展名 + String filename = UUID.randomUUID().toString().replaceAll("-", "") + "." + extension; + try { + minioUtil.uploadFile(file, filename, file.getContentType()); + }catch (Exception e){ + e.printStackTrace(); + return Result.fail().message("服务错误,图片上传失败,请稍后重试"); + } + user.setIsUpload(1); + user.setAvatar(filename); + updateById(user); + return Result.ok().message("头像图片上传成功"); + } + return Result.fail().message("非图片文件或文件过大"); + } + + @Override + public Result getUserInfo(Integer userId) { + User user = getById(userId); + Integer type = user.getType(); + if(type == 1 || type == 0){ + // 脱敏处理 + user.setUserPhone(PhoneUtil.maskPhone(user.getUserPhone())); + user.setEmail(PhoneUtil.maskEmail(user.getEmail())); + } + user.setAvatar(imgUrl + user.getAvatar()); + user.setAccessCode(""); + return Result.ok(user).message("用户信息查询成功"); + } } diff --git a/src/main/java/com/example/venue_reservation_service/utils/DownExcel.java b/src/main/java/com/example/venue_reservation_service/utils/DownExcel.java index 1064b8e..027464b 100644 --- a/src/main/java/com/example/venue_reservation_service/utils/DownExcel.java +++ b/src/main/java/com/example/venue_reservation_service/utils/DownExcel.java @@ -14,7 +14,7 @@ public class DownExcel { response.setCharacterEncoding("UTF-8");// 设置字符编码 response.setHeader("Content-disposition", "attachment;filename=reservation-"+substring+".xlsx"); // 设置响应头 EasyExcel.write(response.getOutputStream(), t) - .sheet("模板") + .sheet("数据报表") .doWrite(list); //用io流来写入数据 } } \ No newline at end of file diff --git a/src/main/java/com/example/venue_reservation_service/utils/ImageValidator.java b/src/main/java/com/example/venue_reservation_service/utils/ImageValidator.java new file mode 100644 index 0000000..4c8d148 --- /dev/null +++ b/src/main/java/com/example/venue_reservation_service/utils/ImageValidator.java @@ -0,0 +1,54 @@ +package com.example.venue_reservation_service.utils; + +import org.springframework.web.multipart.MultipartFile; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.Set; + +public class ImageValidator { + + // 允许的图片扩展名(小写) + private static final Set ALLOWED_EXTENSIONS = Set.of("jpg", "jpeg", "png", "gif", "bmp", "webp"); + // 250MB 字节限制 (250 * 1024 * 1024) + private static final long MAX_SIZE_BYTES = 262144000L; + + /** + * 验证是否为图片文件且大小<250MB + * @param file MultipartFile对象 + * @return true=验证通过, false=验证失败 + */ + public static boolean validateImageFile(MultipartFile file) { + // 1. 基础检查 + if (file == null || file.isEmpty()) { + return false; // 空文件直接拒绝[3,5](@ref) + } + + // 2. 验证文件大小 + if (file.getSize() > MAX_SIZE_BYTES) { + return false; // 超过250MB[6,7](@ref) + } + + // 3. 校验扩展名 + String originalName = file.getOriginalFilename(); + if (originalName == null || originalName.lastIndexOf(".") == -1) { + return false; // 无扩展名文件[5](@ref) + } + String extension = originalName.substring(originalName.lastIndexOf(".") + 1).toLowerCase(); + if (!ALLOWED_EXTENSIONS.contains(extension)) { + return false; // 扩展名不在白名单[5](@ref) + } + + // 4. 通过文件头验证真实格式(防止伪扩展名) + try (ImageInputStream iis = ImageIO.createImageInputStream(file.getInputStream())) { + if (iis == null) return false; // 无法创建流 + + Iterator readers = ImageIO.getImageReaders(iis); + return readers.hasNext(); // 存在ImageReader说明是有效图片[2,5](@ref) + } catch (IOException e) { + return false; // 读取异常视为非图片 + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/venue_reservation_service/utils/PhoneUtil.java b/src/main/java/com/example/venue_reservation_service/utils/PhoneUtil.java index c301c0d..5fe81a1 100644 --- a/src/main/java/com/example/venue_reservation_service/utils/PhoneUtil.java +++ b/src/main/java/com/example/venue_reservation_service/utils/PhoneUtil.java @@ -50,4 +50,11 @@ public class PhoneUtil { // 替换为6个星号 return "******" + domain; } + + // 手机号脱敏 + public static String maskPhone(String phone) { + if (phone == null || phone.length() != 11) return phone; + return phone.substring(0, 3) + "****" + phone.substring(7); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index eee6ecc..d192910 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -33,6 +33,7 @@ spring: min-idle: 0 jackson: date-format: yyyy-MM-dd HH:mm:ss + weixin: app_id: wxce0025f2c0b10a8e secret: 686870f1b1a875369ee979e0648fa950 @@ -48,7 +49,8 @@ minio: access-key: minioadmin secret-key: minioadmin bucket: venue-sport - +access: + url: http://${localhosturl}:9000/${minio.bucket}/ logging: level: root: info diff --git a/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java b/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java index 166cb68..f687a42 100644 --- a/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java +++ b/src/test/java/com/example/venue_reservation_service/VenueReservationServiceApplicationTests.java @@ -1,5 +1,6 @@ package com.example.venue_reservation_service; +import cn.hutool.core.lang.UUID; import com.example.venue_reservation_service.controller.UserController; import com.example.venue_reservation_service.dto.AdminDTO; import com.example.venue_reservation_service.utils.MD5Util; @@ -8,6 +9,7 @@ import jakarta.annotation.Resource; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import org.apache.commons.io.FilenameUtils; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -24,7 +26,8 @@ class VenueReservationServiceApplicationTests { @Test void contextLoads() throws Exception { - + String extension = FilenameUtils.getExtension("image.jpeg"); // 返回 "jpeg" + System.out.println( UUID.randomUUID().toString().replaceAll("-", "")); }