package com.example.demo.auth; import com.example.demo.common.AppDefaultsProperties; import org.springframework.beans.factory.annotation.Autowired; import com.example.demo.common.ShopDefaultsProperties; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.sql.PreparedStatement; import java.util.HashMap; import java.util.Map; @Service public class RegisterService { private final JdbcTemplate jdbcTemplate; private final JwtService jwtService; private final JwtProperties jwtProps; private final ShopDefaultsProperties shopDefaults; private AppDefaultsProperties appDefaults; public RegisterService(JdbcTemplate jdbcTemplate, JwtService jwtService, JwtProperties jwtProps, ShopDefaultsProperties shopDefaults) { this.jdbcTemplate = jdbcTemplate; this.jwtService = jwtService; this.jwtProps = jwtProps; this.shopDefaults = shopDefaults; } @Autowired public void setAppDefaults(AppDefaultsProperties appDefaults) { this.appDefaults = appDefaults; } private String hashPassword(String raw) { try { return org.springframework.security.crypto.bcrypt.BCrypt.hashpw(raw, org.springframework.security.crypto.bcrypt.BCrypt.gensalt(10)); } catch (Exception e) { throw new IllegalStateException("密码加密失败", e); } } public static class RegisterRequest { public String phone; // 必填:11位 public String name; // 可选:默认用脱敏手机号 public String password; // 可选:如提供则保存密码哈希 } public static class RegisterResponse { public String token; public long expiresIn; public Map user; } @Transactional public RegisterResponse register(RegisterRequest req) { ensurePhoneFormat(req.phone); String phone = req.phone.trim(); String displayName = (req.name == null || req.name.isBlank()) ? maskPhoneForName(phone) : req.name.trim(); // 已存在则直接签发令牌 var existing = jdbcTemplate.queryForList("SELECT id, shop_id FROM users WHERE phone=? LIMIT 1", phone); Long userId; Long shopId; if (!existing.isEmpty()) { Map row = existing.get(0); userId = ((Number)row.get("id")).longValue(); shopId = ((Number)row.get("shop_id")).longValue(); } else { // 1) 创建店铺 String shopName = String.format(shopDefaults.getNamePattern(), displayName); GeneratedKeyHolder shopKey = new GeneratedKeyHolder(); jdbcTemplate.update(con -> { PreparedStatement ps = con.prepareStatement( "INSERT INTO shops(name, status, created_at, updated_at) VALUES (?,1,NOW(),NOW())", java.sql.Statement.RETURN_GENERATED_KEYS); ps.setString(1, shopName); return ps; }, shopKey); Number shopGenId = shopKey.getKey(); if (shopGenId == null) throw new IllegalStateException("创建店铺失败"); shopId = shopGenId.longValue(); // 2) 创建店主用户(owner) GeneratedKeyHolder userKey = new GeneratedKeyHolder(); final Long sid = shopId; jdbcTemplate.update(con -> { PreparedStatement ps = con.prepareStatement( "INSERT INTO users(shop_id, phone, name, role, password_hash, status, is_owner, created_at, updated_at) " + "VALUES (?,?,?,?,?,1,1,NOW(),NOW())", java.sql.Statement.RETURN_GENERATED_KEYS); ps.setLong(1, sid); ps.setString(2, phone); ps.setString(3, displayName); ps.setString(4, shopDefaults.getOwnerRole()); // 如提供密码,存入哈希;否则设为 NULL String pwd = (req.password == null || req.password.isBlank()) ? null : hashPassword(req.password); if (pwd != null) ps.setString(5, pwd); else ps.setNull(5, java.sql.Types.VARCHAR); return ps; }, userKey); Number userGenId = userKey.getKey(); if (userGenId == null) throw new IllegalStateException("创建用户失败"); userId = userGenId.longValue(); // 3) 创建默认账户(现金/银行存款/微信) createDefaultAccounts(shopId, userId); } String token = jwtService.signToken(userId, shopId, phone, "register"); RegisterResponse out = new RegisterResponse(); out.token = token; out.expiresIn = jwtProps.getTtlSeconds(); HashMap userMap = new HashMap<>(); userMap.put("userId", userId); userMap.put("shopId", shopId); userMap.put("phone", phone); out.user = userMap; return out; } private void createDefaultAccounts(Long shopId, Long userId) { // 现金 jdbcTemplate.update( "INSERT INTO accounts(shop_id,user_id,name,`type`,balance,status,created_at,updated_at) " + "SELECT ?, ?, ?, 'cash', 0, 1, NOW(), NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM accounts WHERE shop_id=? AND name=?)", shopId, userId, appDefaults.getAccountCashName(), shopId, appDefaults.getAccountCashName()); // 银行存款 jdbcTemplate.update( "INSERT INTO accounts(shop_id,user_id,name,`type`,balance,status,created_at,updated_at) " + "SELECT ?, ?, ?, 'bank', 0, 1, NOW(), NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM accounts WHERE shop_id=? AND name=?)", shopId, userId, appDefaults.getAccountBankName(), shopId, appDefaults.getAccountBankName()); // 微信 jdbcTemplate.update( "INSERT INTO accounts(shop_id,user_id,name,`type`,balance,status,created_at,updated_at) " + "SELECT ?, ?, ?, 'wechat', 0, 1, NOW(), NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM accounts WHERE shop_id=? AND name=?)", shopId, userId, appDefaults.getAccountWechatName(), shopId, appDefaults.getAccountWechatName()); } 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("手机号格式不正确"); } private static String maskPhoneForName(String phone) { String p = String.valueOf(phone); if (p.length() == 11) return "用户" + p.substring(0,3) + "****" + p.substring(7); return "手机用户"; } }