当前位置: 首页 > article >正文

Java全栈项目:校园共享单车管理平台

项目介绍

校园共享单车管理平台是一个基于Spring Boot + Vue.js的全栈项目,旨在为校园提供一个完整的共享单车管理解决方案。该系统支持学生租借单车、管理员管理车辆和用户等功能。

技术栈

后端

  • Spring Boot 2.7.x
  • Spring Security
  • MyBatis-Plus
  • MySQL 8.0
  • Redis
  • JWT

前端

  • Vue 3
  • Element Plus
  • Axios
  • Vuex
  • Vue Router

核心功能模块

1. 用户管理模块

  • 学生注册与登录
  • 管理员账户管理
  • 基于JWT的身份认证
  • 角色权限控制

2. 单车管理模块

@Service
public class BikeService {
    // 添加单车
    public void addBike(BikeDTO bikeDTO) {
        // 验证单车信息
        // 生成唯一二维码
        // 保存单车信息
    }
    
    // 查询可用单车
    public List<BikeVO> getAvailableBikes() {
        // 返回所有状态为可用的单车
    }
}

3. 租借管理模块

  • 扫码租车
  • 实时计费
  • 还车确认
  • 故障报修

4. 支付模块

  • 余额充值
  • 费用计算
  • 支付记录

数据库设计

主要包含以下表:

-- 用户表
CREATE TABLE t_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(100) NOT NULL,
    role VARCHAR(20) NOT NULL,
    balance DECIMAL(10,2) DEFAULT 0,
    create_time DATETIME
);

-- 单车表
CREATE TABLE t_bike (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    bike_code VARCHAR(50) NOT NULL,
    status INT DEFAULT 0,
    location VARCHAR(100),
    create_time DATETIME
);

-- 订单表
CREATE TABLE t_order (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT,
    bike_id BIGINT,
    start_time DATETIME,
    end_time DATETIME,
    amount DECIMAL(10,2),
    status INT
);

项目亮点

  1. 采用微服务架构,便于扩展
  2. 使用Redis缓存热点数据
  3. 整合支付宝支付接口
  4. 实现实时位置追踪
  5. 基于AOP的操作日志记录

项目部署

  • 使用Docker容器化部署
  • Nginx反向代理
  • Jenkins自动化部署

性能优化

  1. 数据库索引优化
  2. 接口缓存策略
  3. 前端资源压缩
  4. 图片懒加载

安全措施

  1. XSS防御
  2. SQL注入防护
  3. 敏感数据加密
  4. 接口限流

总结

本项目采用主流的Java全栈技术栈,实现了一个功能完整的校园共享单车管理系统。通过这个项目,不仅能够学习到完整的全栈开发流程,还能掌握项目部署和性能优化等实用技能。

校园共享单车管理平台 - 用户与单车模块详解

一、用户管理模块详细设计

1. 实体类设计

@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String role;      // ROLE_ADMIN, ROLE_STUDENT
    private BigDecimal balance;
    private String phone;
    private String studentId; // 学号
    private Integer status;   // 0-禁用 1-正常
    private LocalDateTime createTime;
}

2. 登录注册接口

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @PostMapping("/register")
    public Result register(@RequestBody RegisterDTO dto) {
        // 1. 验证学号是否已注册
        // 2. 密码加密
        // 3. 保存用户信息
        return Result.success();
    }
    
    @PostMapping("/login")
    public Result login(@RequestBody LoginDTO dto) {
        // 1. 验证用户名密码
        // 2. 生成JWT Token
        // 3. 返回用户信息和Token
        return Result.success(loginVO);
    }
}

3. JWT工具类

@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", userDetails.getUsername());
        claims.put("role", userDetails.getAuthorities());
        
        return Jwts.builder()
            .setClaims(claims)
            .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000))
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
    }
    
    public Boolean validateToken(String token, UserDetails userDetails) {
        // 验证token是否有效
    }
}

4. 安全配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            .antMatchers("/api/student/**").hasRole("STUDENT")
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

二、单车管理模块详细设计

1. 实体类设计

@Data
@TableName("t_bike")
public class Bike {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String bikeCode;    // 单车编号
    private Integer status;     // 0-空闲 1-使用中 2-维修中 3-报废
    private String location;    // 位置信息
    private Double latitude;    // 纬度
    private Double longitude;   // 经度
    private Integer battery;    // 电量百分比
    private LocalDateTime lastMaintenanceTime; // 最后维护时间
    private LocalDateTime createTime;
}

2. 单车管理接口

@RestController
@RequestMapping("/api/bikes")
public class BikeController {
    
    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Result addBike(@RequestBody BikeDTO dto) {
        return bikeService.addBike(dto);
    }
    
    @GetMapping("/available")
    public Result getAvailableBikes(
        @RequestParam Double latitude,
        @RequestParam Double longitude,
        @RequestParam(defaultValue = "1000") Integer radius
    ) {
        return bikeService.getNearbyBikes(latitude, longitude, radius);
    }
    
    @PutMapping("/{id}/status")
    @PreAuthorize("hasRole('ADMIN')")
    public Result updateBikeStatus(
        @PathVariable Long id,
        @RequestParam Integer status
    ) {
        return bikeService.updateStatus(id, status);
    }
}

3. 单车服务实现

@Service
@Slf4j
public class BikeServiceImpl implements BikeService {
    
    @Autowired
    private BikeMapper bikeMapper;
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Override
    public Result addBike(BikeDTO dto) {
        // 1. 生成唯一单车编号
        String bikeCode = generateBikeCode();
        
        // 2. 保存单车信息
        Bike bike = new Bike();
        BeanUtils.copyProperties(dto, bike);
        bike.setBikeCode(bikeCode);
        bike.setStatus(0);
        bike.setCreateTime(LocalDateTime.now());
        
        bikeMapper.insert(bike);
        
        // 3. 更新Redis中的可用单车信息
        String key = String.format("bike:location:%s", bike.getId());
        redisTemplate.opsForGeo().add(
            "bike:locations",
            new Point(bike.getLongitude(), bike.getLatitude()),
            key
        );
        
        return Result.success();
    }
    
    @Override
    public Result getNearbyBikes(Double latitude, Double longitude, Integer radius) {
        // 1. 从Redis中查询附近的单车
        Circle circle = new Circle(
            new Point(longitude, latitude),
            new Distance(radius, Metrics.METERS)
        );
        
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = 
            redisTemplate.opsForGeo().radius("bike:locations", circle);
            
        // 2. 转换为VO对象
        List<BikeVO> bikes = convertToVOList(results);
        
        return Result.success(bikes);
    }
}

4. 缓存设计

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 设置序列化器
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        return template;
    }
}

三、接口文档示例

用户注册

  • 请求路径:POST /api/auth/register
  • 请求参数:
{
    "username": "张三",
    "password": "123456",
    "studentId": "2021001",
    "phone": "13800138000"
}
  • 响应结果:
{
    "code": 200,
    "message": "注册成功",
    "data": null
}

查询附近单车

  • 请求路径:GET /api/bikes/available
  • 请求参数:
    • latitude: 纬度
    • longitude: 经度
    • radius: 搜索半径(米)
  • 响应结果:
{
    "code": 200,
    "message": "success",
    "data": [
        {
            "id": 1,
            "bikeCode": "B001",
            "distance": 100,
            "battery": 90,
            "latitude": 30.123456,
            "longitude": 120.123456
        }
    ]
}

四、注意事项

  1. 用户密码使用BCrypt加密存储
  2. JWT Token需要定期刷新
  3. 单车位置信息使用Redis GEO类型存储
  4. 接口调用需要做频率限制
  5. 重要操作需要记录审计日志

校园共享单车管理平台 - 租借与支付模块详解

一、租借管理模块

1. 订单实体设计

@Data
@TableName("t_rental_order")
public class RentalOrder {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long userId;
    private Long bikeId;
    private String orderNo;        // 订单编号
    private Integer status;        // 0-进行中 1-已完成 2-已取消
    private BigDecimal amount;     // 订单金额
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private String startLocation;  // 起始位置
    private String endLocation;    // 结束位置
    private Integer duration;      // 骑行时长(分钟)
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

2. 租车流程实现

@Service
@Slf4j
public class RentalServiceImpl implements RentalService {
    
    @Autowired
    private BikeService bikeService;
    
    @Autowired
    private RentalOrderMapper orderMapper;
    
    @Transactional
    @Override
    public Result startRental(Long userId, String bikeCode) {
        // 1. 验证用户状态和余额
        UserInfo user = userService.checkUserStatus(userId);
        
        // 2. 验证单车状态
        Bike bike = bikeService.getBikeByCode(bikeCode);
        if (bike.getStatus() != 0) {
            throw new BusinessException("该单车不可租用");
        }
        
        // 3. 创建租借订单
        RentalOrder order = new RentalOrder();
        order.setUserId(userId);
        order.setBikeId(bike.getId());
        order.setOrderNo(generateOrderNo());
        order.setStatus(0);
        order.setStartTime(LocalDateTime.now());
        order.setStartLocation(bike.getLocation());
        
        orderMapper.insert(order);
        
        // 4. 更新单车状态
        bikeService.updateBikeStatus(bike.getId(), 1);
        
        return Result.success(order);
    }
    
    @Transactional
    @Override
    public Result endRental(Long orderId, String endLocation) {
        // 1. 获取订单信息
        RentalOrder order = orderMapper.selectById(orderId);
        if (order == null || order.getStatus() != 0) {
            throw new BusinessException("无效的订单");
        }
        
        // 2. 计算费用
        LocalDateTime now = LocalDateTime.now();
        Duration duration = Duration.between(order.getStartTime(), now);
        int minutes = (int) duration.toMinutes();
        BigDecimal amount = calculateAmount(minutes);
        
        // 3. 更新订单信息
        order.setEndTime(now);
        order.setEndLocation(endLocation);
        order.setDuration(minutes);
        order.setAmount(amount);
        order.setStatus(1);
        orderMapper.updateById(order);
        
        // 4. 更新单车状态
        bikeService.updateBikeStatus(order.getBikeId(), 0);
        
        // 5. 扣除用户余额
        userService.deductBalance(order.getUserId(), amount);
        
        return Result.success(order);
    }
}

3. 故障报修功能

@Data
@TableName("t_repair_record")
public class RepairRecord {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long bikeId;
    private Long userId;
    private String description;    // 故障描述
    private String images;         // 图片URL,多个用逗号分隔
    private Integer status;        // 0-待处理 1-处理中 2-已修复
    private String handleNote;     // 处理说明
    private LocalDateTime createTime;
    private LocalDateTime handleTime;
}
@RestController
@RequestMapping("/api/repairs")
public class RepairController {
    
    @PostMapping
    public Result reportRepair(@RequestBody RepairDTO dto) {
        return repairService.createRepairRecord(dto);
    }
    
    @PutMapping("/{id}/handle")
    @PreAuthorize("hasRole('ADMIN')")
    public Result handleRepair(@PathVariable Long id, @RequestBody HandleDTO dto) {
        return repairService.handleRepair(id, dto);
    }
}

二、支付模块

1. 支付相关实体

@Data
@TableName("t_payment_record")
public class PaymentRecord {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long userId;
    private String tradeNo;        // 交易流水号
    private BigDecimal amount;     // 充值金额
    private Integer payType;       // 1-支付宝 2-微信
    private Integer status;        // 0-待支付 1-支付成功 2-支付失败
    private String paymentId;      // 第三方支付ID
    private LocalDateTime payTime; // 支付时间
    private LocalDateTime createTime;
}

2. 余额充值实现

@Service
@Slf4j
public class PaymentServiceImpl implements PaymentService {
    
    @Autowired
    private AlipayClient alipayClient;
    
    @Override
    public Result createRecharge(RechargeDTO dto) {
        // 1. 创建支付记录
        PaymentRecord record = new PaymentRecord();
        record.setUserId(dto.getUserId());
        record.setAmount(dto.getAmount());
        record.setPayType(dto.getPayType());
        record.setTradeNo(generateTradeNo());
        record.setStatus(0);
        paymentRecordMapper.insert(record);
        
        // 2. 调用支付宝接口
        AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
        request.setBizContent("{" +
            "\"out_trade_no\":\"" + record.getTradeNo() + "\"," +
            "\"total_amount\":\"" + record.getAmount() + "\"," +
            "\"subject\":\"共享单车余额充值\"" +
            "}");
            
        try {
            AlipayTradeCreateResponse response = alipayClient.execute(request);
            if (response.isSuccess()) {
                return Result.success(response.getTradeNo());
            }
        } catch (AlipayApiException e) {
            log.error("调用支付宝接口失败", e);
        }
        
        return Result.error("创建支付订单失败");
    }
    
    @Transactional
    @Override
    public void handlePayCallback(PayCallbackDTO dto) {
        // 1. 验证支付信息
        PaymentRecord record = paymentRecordMapper.selectByTradeNo(dto.getOutTradeNo());
        if (record == null || record.getStatus() != 0) {
            return;
        }
        
        // 2. 更新支付记录
        record.setStatus(1);
        record.setPaymentId(dto.getTradeNo());
        record.setPayTime(LocalDateTime.now());
        paymentRecordMapper.updateById(record);
        
        // 3. 更新用户余额
        userService.addBalance(record.getUserId(), record.getAmount());
    }
}

3. 费用计算策略

@Service
public class BillingServiceImpl implements BillingService {
    
    @Value("${billing.base-price}")
    private BigDecimal basePrice;  // 基础价格
    
    @Value("${billing.per-minute}")
    private BigDecimal perMinute;  // 每分钟费用
    
    @Override
    public BigDecimal calculateAmount(int minutes) {
        if (minutes <= 0) {
            return BigDecimal.ZERO;
        }
        
        // 基础费用 + 时长费用
        return basePrice.add(perMinute.multiply(new BigDecimal(minutes)));
    }
    
    @Override
    public BigDecimal calculateDeposit() {
        // 计算押金金额
        return new BigDecimal("199");
    }
}

4. 支付宝配置

@Configuration
public class AlipayConfig {
    
    @Value("${alipay.app-id}")
    private String appId;
    
    @Value("${alipay.private-key}")
    private String privateKey;
    
    @Value("${alipay.public-key}")
    private String publicKey;
    
    @Bean
    public AlipayClient alipayClient() {
        return new DefaultAlipayClient(
            "https://openapi.alipay.com/gateway.do",
            appId,
            privateKey,
            "json",
            "UTF-8",
            publicKey,
            "RSA2"
        );
    }
}

三、接口文档示例

开始租车

  • 请求路径:POST /api/rentals/start
  • 请求参数:
{
    "bikeCode": "B001"
}
  • 响应结果:
{
    "code": 200,
    "message": "success",
    "data": {
        "orderNo": "RO202403150001",
        "startTime": "2024-03-15 10:00:00"
    }
}

创建充值订单

  • 请求路径:POST /api/payments/recharge
  • 请求参数:
{
    "amount": 50.00,
    "payType": 1
}
  • 响应结果:
{
    "code": 200,
    "message": "success",
    "data": {
        "tradeNo": "P202403150001",
        "payUrl": "https://openapi.alipay.com/gateway.do?..."
    }
}

四、注意事项

  1. 租车和还车操作需要添加分布式锁
  2. 支付回调要考虑重复通知的情况
  3. 费用计算需要考虑优惠券和会员折扣
  4. 重要金额操作需要记录详细日志
  5. 订单状态变更需要发送消息通知
  6. 支付接口需要做签名验证
  7. 图片上传需要做大小和格式限制

校园共享单车 - 微信支付接口集成

一、微信支付配置

1. 配置文件

wechat:
  pay:
    appId: wxxxxxxxxxxx
    mchId: 1900000109
    apiKey: 8934e7d15453e97507ef794cf7b0519d
    apiV3Key: 31499L9VCD3M9G3j3JM9n8dj1f3j139
    certPath: /path/to/apiclient_cert.p12
    notifyUrl: https://example.com/api/payments/wx/callback

2. 微信支付配置类

@Configuration
@ConfigurationProperties(prefix = "wechat.pay")
@Data
public class WxPayConfig {
    private String appId;
    private String mchId;
    private String apiKey;
    private String apiV3Key;
    private String certPath;
    private String notifyUrl;
    
    @Bean
    public WxPayService wxPayService() throws Exception {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(appId);
        payConfig.setMchId(mchId);
        payConfig.setMchKey(apiKey);
        payConfig.setKeyPath(certPath);
        
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }
}

二、支付服务实现

1. 支付服务接口扩展

public interface PaymentService {
    // 创建支付宝充值订单
    Result createAlipayRecharge(RechargeDTO dto);
    
    // 创建微信充值订单
    Result createWxPayRecharge(RechargeDTO dto);
    
    // 处理微信支付回调
    void handleWxPayCallback(String xmlData);
}

2. 微信支付实现

@Service
@Slf4j
public class WxPayServiceImpl {
    
    @Autowired
    private WxPayService wxPayService;
    
    @Autowired
    private WxPayConfig wxPayConfig;
    
    @Override
    public Result createWxPayRecharge(RechargeDTO dto) {
        try {
            // 1. 创建支付记录
            PaymentRecord record = createPaymentRecord(dto);
            
            // 2. 构建支付请求参数
            WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
            request.setBody("共享单车余额充值");
            request.setOutTradeNo(record.getTradeNo());
            request.setTotalFee(convertToFen(dto.getAmount())); // 转换为分
            request.setSpbillCreateIp(dto.getClientIp());
            request.setNotifyUrl(wxPayConfig.getNotifyUrl());
            request.setTradeType("JSAPI");  // 小程序支付
            request.setOpenid(dto.getOpenid());
            
            // 3. 调用统一下单接口
            WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);
            
            // 4. 生成支付参数
            WxPayMpOrderResult payInfo = WxPayMpOrderResult.builder()
                .appId(result.getAppid())
                .timeStamp(String.valueOf(System.currentTimeMillis() / 1000))
                .nonceStr(result.getNonceStr())
                .packageValue("prepay_id=" + result.getPrepayId())
                .signType("MD5")
                .build();
            
            // 5. 签名
            payInfo.setPaySign(SignUtils.createSign(payInfo, wxPayConfig.getApiKey()));
            
            return Result.success(payInfo);
            
        } catch (WxPayException e) {
            log.error("微信支付下单失败", e);
            return Result.error("创建支付订单失败");
        }
    }
    
    @Transactional
    @Override
    public void handleWxPayCallback(String xmlData) {
        try {
            // 1. 解析回调数据
            WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData);
            
            // 2. 验证支付结果
            if (!"SUCCESS".equals(notifyResult.getResultCode())) {
                log.error("支付失败:{}", notifyResult.getErrCodeDes());
                return;
            }
            
            // 3. 查询支付记录
            PaymentRecord record = paymentRecordMapper.selectByTradeNo(
                notifyResult.getOutTradeNo()
            );
            
            if (record == null || record.getStatus() != 0) {
                return;
            }
            
            // 4. 验证支付金额
            if (!record.getAmount().multiply(new BigDecimal("100"))
                    .equals(new BigDecimal(notifyResult.getTotalFee()))) {
                log.error("支付金额不匹配");
                return;
            }
            
            // 5. 更新支付记录
            record.setStatus(1);
            record.setPaymentId(notifyResult.getTransactionId());
            record.setPayTime(LocalDateTime.now());
            paymentRecordMapper.updateById(record);
            
            // 6. 更新用户余额
            userService.addBalance(record.getUserId(), record.getAmount());
            
        } catch (WxPayException e) {
            log.error("处理微信支付回调失败", e);
            throw new BusinessException("处理支付回调失败");
        }
    }
    
    private Integer convertToFen(BigDecimal amount) {
        return amount.multiply(new BigDecimal("100")).intValue();
    }
}

3. 支付回调接口

@RestController
@RequestMapping("/api/payments")
@Slf4j
public class PaymentController {
    
    @Autowired
    private PaymentService paymentService;
    
    @PostMapping("/wx/callback")
    public String handleWxPayCallback(HttpServletRequest request) {
        try {
            // 1. 读取回调数据
            String xmlData = IOUtils.toString(request.getInputStream(), "UTF-8");
            log.info("收到微信支付回调:{}", xmlData);
            
            // 2. 处理回调
            paymentService.handleWxPayCallback(xmlData);
            
            // 3. 返回成功响应
            return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
            
        } catch (Exception e) {
            log.error("处理微信支付回调异常", e);
            return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[处理失败]]></return_msg></xml>";
        }
    }
}

三、工具类

1. 签名工具类

public class SignUtils {
    
    public static String createSign(Object data, String key) {
        // 1. 将对象转为Map
        Map<String, String> params = objectToMap(data);
        
        // 2. 按照键值排序
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        
        // 3. 拼接签名字符串
        StringBuilder signStr = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            if (StringUtils.isNotEmpty(entry.getValue())) {
                signStr.append(entry.getKey())
                       .append("=")
                       .append(entry.getValue())
                       .append("&");
            }
        }
        signStr.append("key=").append(key);
        
        // 4. MD5加密
        return DigestUtils.md5Hex(signStr.toString()).toUpperCase();
    }
    
    private static Map<String, String> objectToMap(Object obj) {
        // 使用反射将对象转换为Map
    }
}

四、接口文档

创建微信支付订单

  • 请求路径:POST /api/payments/wx/recharge
  • 请求参数:
{
    "amount": 50.00,
    "openid": "oxxxxxxxxxxxxxxxxxx",
    "clientIp": "127.0.0.1"
}
  • 响应结果:
{
    "code": 200,
    "message": "success",
    "data": {
        "appId": "wxxxxxxxxxxx",
        "timeStamp": "1634567890",
        "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
        "packageValue": "prepay_id=wx201410272009395522657a690389285100",
        "signType": "MD5",
        "paySign": "C380BEC2BFD727A4B6845133519F3AD6"
    }
}

五、注意事项

  1. 安全考虑
@Configuration
public class WxPaySecurityConfig {
    
    @Bean
    public SignatureVerifier wxPaySignatureVerifier() {
        return new SignatureVerifier() {
            @Override
            public boolean verify(String body, String signature) {
                // 验证签名
            }
        };
    }
    
    @Bean
    public RequestInterceptor wxPayRequestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void intercept(RequestFacade request) {
                // 添加请求头
                request.addHeader("Content-Type", "application/json");
                request.addHeader("Accept", "application/json");
            }
        };
    }
}
  1. 关键注意点:

    • 所有金额计算使用BigDecimal
    • 微信支付金额单位为分
    • 支付回调需要验证签名
    • 回调接口需要做幂等处理
    • 敏感配置信息需要加密存储
    • 证书文件需要安全保存
    • 重要操作需要记录日志
    • 考虑并发情况下的数据一致性
  2. 异常处理:

@ControllerAdvice
public class WxPayExceptionHandler {
    
    @ExceptionHandler(WxPayException.class)
    public Result handleWxPayException(WxPayException e) {
        log.error("微信支付异常", e);
        return Result.error("支付处理失败");
    }
}
  1. 支付状态轮询:
@Scheduled(fixedDelay = 60000)
public void checkPaymentStatus() {
    List<PaymentRecord> pendingRecords = paymentRecordMapper.selectByStatus(0);
    for (PaymentRecord record : pendingRecords) {
        try {
            WxPayOrderQueryResult result = wxPayService.queryOrder("", record.getTradeNo());
            if ("SUCCESS".equals(result.getTradeState())) {
                handleWxPayCallback(result);
            }
        } catch (WxPayException e) {
            log.error("查询支付状态失败", e);
        }
    }
}

http://www.kler.cn/a/443172.html

相关文章:

  • 设计模式 行为型 责任链模式(Chain of Responsibility Pattern)与 常见技术框架应用 解析
  • day02-前端Web-JavaScript
  • 51单片机(一) keil4工程与小灯实验
  • STL——二叉搜索树
  • PLC实现HTTP协议JSON格式数据上报对接的参数配置说明
  • Chrome_60.0.3112.113_x64 单文件版 下载
  • 红狮金业:央行利率决议对贵金属市场的影响
  • A5433 Java+Jsp+Servlet+MySQL+微信小程序+LW+在线点餐小程序的设计与实现 源码 配置 文档
  • 说说es6 promise async await 以及 promise A+规范的了解
  • 使用winscp从windows访问Ubuntu进行文件传输
  • MySQL高级技术:性能优化与死锁处理
  • 深入解析Ubuntu 20.04中ROS的catkin_make工具
  • 《鸿蒙开发-答案之书》字符串占位符格式化
  • 【Unity】环境配置与安装
  • Vue工具和面试题目(二)
  • Oracle 临时表空间管理与最佳实践
  • 视频生成缩略图
  • 什么是大型语言模型
  • 王佩丰24节Excel学习笔记——第十二讲:match + index
  • 从零开始:PHP基础教程系列-第10篇:错误处理与调试技巧
  • 20241218_segmentation
  • MySQL-9.1.0 GTID模式
  • ROS2_进阶笔记
  • 3.9、mixins配置(混入)
  • Netcat:网络中的瑞士军刀
  • Mapbox-GL 的源码解读的一般步骤