package com.example.demo.user; import com.example.demo.attachment.AttachmentUrlValidator; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.Map; @Service public class UserService { private final JdbcTemplate jdbcTemplate; private final AttachmentUrlValidator urlValidator; public UserService(JdbcTemplate jdbcTemplate, AttachmentUrlValidator urlValidator) { this.jdbcTemplate = jdbcTemplate; this.urlValidator = urlValidator; } @Transactional(readOnly = true) public Map getProfile(Long userId) { return jdbcTemplate.query( con -> { var ps = con.prepareStatement("SELECT id, shop_id, phone, email, name, avatar_url FROM users WHERE id=? LIMIT 1"); ps.setLong(1, userId); return ps; }, rs -> { if (!rs.next()) return null; Map m = new HashMap<>(); m.put("id", rs.getLong("id")); m.put("shopId", rs.getLong("shop_id")); m.put("phone", rs.getString("phone")); m.put("email", rs.getString("email")); m.put("name", rs.getString("name")); m.put("avatarUrl", rs.getString("avatar_url")); return m; } ); } public static class UpdateProfileRequest { public String name; public String avatarUrl; } @Transactional public void updateProfile(Long userId, UpdateProfileRequest req) { if ((req.name == null || req.name.isBlank()) && (req.avatarUrl == null || req.avatarUrl.isBlank())) { return; // nothing to update } StringBuilder sql = new StringBuilder("UPDATE users SET "); java.util.List args = new java.util.ArrayList<>(); boolean first = true; if (req.name != null) { String nm = req.name.trim(); if (nm.isEmpty()) throw new IllegalArgumentException("姓名不能为空"); if (nm.length() > 64) throw new IllegalArgumentException("姓名过长"); sql.append(first ? "name=?" : ", name=?"); args.add(nm); first = false; } if (req.avatarUrl != null) { String au = req.avatarUrl.trim(); if (au.isEmpty()) { sql.append(first ? "avatar_url=NULL" : ", avatar_url=NULL"); } else if (au.startsWith("/")) { String normalized = au.replaceAll("/{2,}", "/"); sql.append(first ? "avatar_url=?" : ", avatar_url=?"); args.add(normalized); } else { var vr = urlValidator.validate(au); sql.append(first ? "avatar_url=?" : ", avatar_url=?"); args.add(vr.url()); } first = false; } sql.append(", updated_at=NOW() WHERE id=?"); args.add(userId); jdbcTemplate.update(sql.toString(), args.toArray()); } public static class ChangePasswordRequest { public String oldPassword; public String newPassword; } @Transactional public void changePassword(Long userId, ChangePasswordRequest req) { if (req.newPassword == null || req.newPassword.isBlank()) throw new IllegalArgumentException("新密码不能为空"); if (req.newPassword.length() < 6) throw new IllegalArgumentException("新密码至少6位"); Map row = jdbcTemplate.query( con -> { var ps = con.prepareStatement("SELECT password_hash FROM users WHERE id=?"); ps.setLong(1, userId); return ps; }, rs -> { if (rs.next()) { Map m = new HashMap<>(); m.put("password_hash", rs.getString(1)); return m; } return null; } ); if (row == null) throw new IllegalArgumentException("用户不存在"); String existing = (String) row.get("password_hash"); if (existing != null && !existing.isBlank()) { if (req.oldPassword == null || req.oldPassword.isBlank()) throw new IllegalArgumentException("请提供旧密码"); boolean ok = BCrypt.checkpw(req.oldPassword, existing); if (!ok) throw new IllegalArgumentException("旧密码不正确"); } String newHash = BCrypt.hashpw(req.newPassword, BCrypt.gensalt()); jdbcTemplate.update("UPDATE users SET password_hash=?, updated_at=NOW() WHERE id=?", newHash, userId); } public static class ChangePhoneRequest { public String phone; public String code; } @Transactional public void changePhone(Long userId, ChangePhoneRequest req) { ensurePhoneFormat(req.phone); // 无需验证码,直接保存;保留唯一性与格式校验 try { jdbcTemplate.update("UPDATE users SET phone=?, updated_at=NOW() WHERE id=?", req.phone, userId); } catch (DataIntegrityViolationException e) { throw new IllegalArgumentException("该手机号已被占用"); } } private void ensurePhoneFormat(String phone) { if (phone == null || phone.isBlank()) throw new IllegalArgumentException("手机号不能为空"); String p = phone.replaceAll("\\s+", ""); if (!p.matches("^1\\d{10}$")) throw new IllegalArgumentException("手机号格式不正确"); } }