JAVA校园跑腿代买代拿社区-校园跑腿小程序的后端代码示例
JAVA校园跑腿系统 - 后端完整代码示例校园跑腿代买代拿| Spring Boot MyBatis Plus MySQL Redis 一、项目依赖pom.xmlxml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.0/version /parent groupIdcom.campus/groupId artifactIdcampus-errand/artifactId version1.0.0/version packagingjar/packaging properties java.version17/java.version mybatis-plus.version3.5.5/mybatis-plus.version jwt.version0.12.3/jwt.version /properties dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis Plus -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version${mybatis-plus.version}/version /dependency !-- MySQL -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- JWT -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version${jwt.version}/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version${jwt.version}/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version${jwt.version}/version scoperuntime/scope /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Hutool工具类 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.23/version /dependency !-- 微信小程序SDK (可选) -- dependency groupIdcom.github.binarywang/groupId artifactIdweixin-java-miniapp/artifactId version4.5.0/version /dependency /dependencies /project 二、项目结构src/main/java/com/campus/errand/ ├── CampusErrandApplication.java # 启动类 ├── config/ │ ├── CorsConfig.java # 跨域配置 │ ├── MyBatisPlusConfig.java # MP配置 │ ├── RedisConfig.java # Redis配置 │ └── JwtConfig.java # JWT配置 ├── common/ │ ├── Result.java # 统一返回 │ ├── PageResult.java # 分页返回 │ ├── BaseEntity.java # 基础实体 │ └── BusinessException.java # 业务异常 ├── entity/ │ ├── User.java # 用户 │ ├── ErrandOrder.java # 跑腿订单 │ ├── Runner.java # 跑腿员 │ └── Category.java # 分类 ├── mapper/ │ ├── UserMapper.java │ ├── ErrandOrderMapper.java │ └── RunnerMapper.java ├── service/ │ ├── UserService.java │ ├── ErrandService.java │ └── RunnerService.java ├── controller/ │ ├── UserController.java │ ├── ErrandController.java │ └── RunnerController.java └── utils/ └── JwtUtils.java # JWT工具 三、核心代码1️⃣ 统一返回Result.javajavaData AllArgsConstructor NoArgsConstructor public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { return new Result(200, 操作成功, data); } public static T ResultT success() { return success(null); } public static T ResultT error(String message) { return new Result(500, message, null); } public static T ResultT error(Integer code, String message) { return new Result(code, message, null); } }2️⃣ 实体类User.java- 用户javaData TableName(user) public class User extends BaseEntity { TableId(type IdType.AUTO) private Long id; private String openid; // 微信openid private String nickname; // 昵称 private String avatar; // 头像 private String phone; // 手机号 private Integer role; // 0-普通用户 1-跑腿员 2-管理员 private BigDecimal balance; // 余额 private Integer status; // 0-禁用 1-正常 }ErrandOrder.java- 跑腿订单 ⭐javaData TableName(errand_order) public class ErrandOrder extends BaseEntity { TableId(type IdType.AUTO) private Long id; private String orderNo; // 订单号 private Long userId; // 发布者ID private Long runnerId; // 接单跑腿员ID private Integer type; // 1-代买 2-代拿 3-代送 4-其他 private String title; // 标题 private String description; // 详细描述 private String pickupAddress; // 取货地址 private String deliveryAddress; // 送货地址 private String contactPhone; // 联系电话 private BigDecimal reward; // 跑腿费 private Integer status; // 0-待接单 1-进行中 2-已完成 3-已取消 4-已退款 private LocalDateTime acceptTime; // 接单时间 private LocalDateTime finishTime; // 完成时间 private String remark; // 备注 }Runner.java- 跑腿员javaData TableName(runner) public class Runner extends BaseEntity { TableId(type IdType.AUTO) private Long id; private Long userId; // 关联用户ID private String realName; // 真实姓名 private String studentId; // 学号 private String idCard; // 身份证 private Integer status; // 0-待审核 1-已认证 2-已拒绝 private Integer orderCount; // 接单总数 private BigDecimal totalEarning; // 总收入 private Double rating; // 评分 4.5-5.0 }Category.java- 分类javaData TableName(category) public class Category { TableId(type IdType.AUTO) private Long id; private String name; // 分类名 private String icon; // 图标 private Integer sort; // 排序 private Integer status; // 0-禁用 1-启用 }3️⃣ Controller层 ⭐UserController.java- 用户模块javaRestController RequestMapping(/api/user) CrossOrigin public class UserController { Autowired private UserService userService; // 微信登录 PostMapping(/wxLogin) public Result? wxLogin(RequestBody WxLoginDTO dto) { String token userService.wxLogin(dto.getCode()); return Result.success(Map.of(token, token)); } // 获取用户信息 GetMapping(/info) public Result? getUserInfo(RequestAttribute Long userId) { User user userService.getById(userId); return Result.success(user); } // 申请成为跑腿员 PostMapping(/applyRunner) public Result? applyRunner(RequestAttribute Long userId, RequestBody RunnerApplyDTO dto) { userService.applyRunner(userId, dto); return Result.success(); } // 充值 PostMapping(/recharge) public Result? recharge(RequestAttribute Long userId, RequestParam BigDecimal amount) { userService.recharge(userId, amount); return Result.success(); } }ErrandController.java- 跑腿订单 ⭐⭐⭐javaRestController RequestMapping(/api/errand) CrossOrigin public class ErrandController { Autowired private ErrandService errandService; // 发布跑腿订单 PostMapping(/publish) public Result? publish(RequestAttribute Long userId, RequestBody ErrandOrderDTO dto) { String orderNo errandService.publish(userId, dto); return Result.success(Map.of(orderNo, orderNo)); } // 首页 - 订单列表(待接单) GetMapping(/list) public Result? list( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer type, RequestParam(required false) String keyword ) { PageErrandOrderVO result errandService.getOrderList(page, size, type, keyword); return Result.success(result); } // 订单详情 GetMapping(/detail/{id}) public Result? detail(PathVariable Long id) { ErrandOrderVO order errandService.getOrderDetail(id); return Result.success(order); } // 接单 PostMapping(/accept/{orderId}) public Result? accept(PathVariable Long orderId, RequestAttribute Long runnerId) { errandService.acceptOrder(orderId, runnerId); return Result.success(); } // 更新订单状态 PostMapping(/updateStatus) public Result? updateStatus(RequestBody StatusUpdateDTO dto) { errandService.updateStatus(dto); return Result.success(); } // 我的订单(发布者) GetMapping(/myOrders) public Result? myOrders(RequestAttribute Long userId, RequestParam Integer status) { ListErrandOrderVO orders errandService.getUserOrders(userId, status); return Result.success(orders); } // 取消订单 PostMapping(/cancel/{orderId}) public Result? cancel(PathVariable Long orderId, RequestAttribute Long userId) { errandService.cancelOrder(orderId, userId); return Result.success(); } // 评价 PostMapping(/rate/{orderId}) public Result? rate(PathVariable Long orderId, RequestAttribute Long userId, RequestParam Integer score, RequestParam String content) { errandService.rateOrder(orderId, userId, score, content); return Result.success(); } }RunnerController.java- 跑腿员模块javaRestController RequestMapping(/api/runner) CrossOrigin public class RunnerController { Autowired private RunnerService runnerService; // 附近跑腿员列表 GetMapping(/nearby) public Result? nearby(RequestParam Double latitude, RequestParam Double longitude) { ListRunnerVO runners runnerService.getNearbyRunners(latitude, longitude); return Result.success(runners); } // 跑腿员接单大厅 GetMapping(/orders) public Result? availableOrders(RequestAttribute Long runnerId) { ListErrandOrderVO orders runnerService.getAvailableOrders(runnerId); return Result.success(orders); } // 跑腿员统计 GetMapping(/stats) public Result? stats(RequestAttribute Long runnerId) { RunnerStatsVO stats runnerService.getStats(runnerId); return Result.success(stats); } }4️⃣ Service层 ⭐⭐⭐ErrandService.java- 跑腿订单服务核心javaService Slf4j public class ErrandServiceImpl implements ErrandService { Autowired private ErrandOrderMapper orderMapper; Autowired private RedisTemplateString, Object redisTemplate; /** * 发布跑腿订单 */ Override Transactional(rollbackFor Exception.class) public String publish(Long userId, ErrandOrderDTO dto) { // 1. 生成订单号 String orderNo ER System.currentTimeMillis() RandomUtil.randomNumbers(4); // 2. 保存订单 ErrandOrder order new ErrandOrder(); BeanUtils.copyProperties(dto, order); order.setOrderNo(orderNo); order.setUserId(userId); order.setStatus(0); // 待接单 order.setReward(dto.getReward()); orderMapper.insert(order); // 3. 推送到Redis实时队列 (通知附近跑腿员) String key errand:waiting: dto.getType(); redisTemplate.opsForList().rightPush(key, orderNo); // 设置过期时间 30分钟 redisTemplate.expire(key, 30, TimeUnit.MINUTES); log.info(新订单发布: {}, orderNo); return orderNo; } /** * 接单 */ Override Transactional(rollbackFor Exception.class) public void acceptOrder(Long orderId, Long runnerId) { // 1. 查询订单 ErrandOrder order orderMapper.selectById(orderId); if (order null) throw new BusinessException(订单不存在); if (order.getStatus() ! 0) throw new BusinessException(订单已被接单); // 2. 分布式锁防止超卖 String lockKey errand:lock: orderId; Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (!Boolean.TRUE.equals(locked)) { throw new BusinessException(订单正在被处理请稍后重试); } try { // 3. 更新订单 order.setRunnerId(runnerId); order.setStatus(1); // 进行中 order.setAcceptTime(LocalDateTime.now()); orderMapper.updateById(order); // 4. 从等待队列移除 redisTemplate.opsForList().remove(errand:waiting: order.getType(), orderId); log.info(跑腿员{}接单: {}, runnerId, orderId); } finally { redisTemplate.delete(lockKey); } } /** * 订单列表(首页) */ Override public PageErrandOrderVO getOrderList(Integer page, Integer size, Integer type, String keyword) { PageErrandOrder pageParam new Page(page, size); LambdaQueryWrapperErrandOrder wrapper new LambdaQueryWrapper(); wrapper.eq(ErrandOrder::getStatus, 0) // 只查待接单 .like(StringUtils.isNotBlank(keyword), ErrandOrder::getTitle, keyword) .eq(type ! null, ErrandOrder::getType, type) .orderByDesc(ErrandOrder::getCreateTime); PageErrandOrder result orderMapper.selectPage(pageParam, wrapper); // 转换VO PageErrandOrderVO voPage new Page(); BeanUtils.copyProperties(result, voPage, records); voPage.setRecords(result.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList())); return voPage; } /** * 取消订单 */ Override Transactional(rollbackFor Exception.class) public void cancelOrder(Long orderId, Long userId) { ErrandOrder order orderMapper.selectById(orderId); if (order null) throw new BusinessException(订单不存在); if (!order.getUserId().equals(userId)) { throw new BusinessException(无权操作此订单); } if (order.getStatus() ! 0) { throw new BusinessException(只能取消待接单订单); } order.setStatus(3); // 已取消 orderMapper.updateById(order); // 退还跑腿费(如果已支付) if (order.getReward() ! null order.getReward().compareTo(BigDecimal.ZERO) 0) { // 调用支付退款接口... } } /** * 评价订单 */ Override Transactional(rollbackFor Exception.class) public void rateOrder(Long orderId, Long userId, Integer score, String content) { ErrandOrder order orderMapper.selectById(orderId); if (order null || !order.getUserId().equals(userId)) { throw new BusinessException(订单不存在); } if (order.getStatus() ! 2) { throw new BusinessException(只能评价已完成订单); } // 更新评分(这里简化实际应关联评价表) order.setRemark(content); orderMapper.updateById(order); // 更新跑腿员评分 Runner runner runnerMapper.selectById(order.getRunnerId()); if (runner ! null) { Double newRating (runner.getRating() * runner.getOrderCount() score) / (runner.getOrderCount() 1); runner.setRating(newRating); runner.setOrderCount(runner.getOrderCount() 1); runnerMapper.updateById(runner); } } private ErrandOrderVO convertToVO(ErrandOrder order) { ErrandOrderVO vo new ErrandOrderVO(); BeanUtils.copyProperties(order, vo); // 查询发布者信息 User user userMapper.selectById(order.getUserId()); if (user ! null) { vo.setUserNickname(user.getNickname()); vo.setUserAvatar(user.getAvatar()); } return vo; } }RunnerService.java- 跑腿员服务javaService public class RunnerServiceImpl implements RunnerService { Autowired private RunnerMapper runnerMapper; Override public ListRunnerVO getNearbyRunners(Double lat, Double lon) { // 实际项目应使用MySQL空间函数或Redis GEO // 这里简化为查询所有已认证跑腿员 LambdaQueryWrapperRunner wrapper new LambdaQueryWrapper(); wrapper.eq(Runner::getStatus, 1); // 已认证 ListRunner runners runnerMapper.selectList(wrapper); return runners.stream() .map(r - { RunnerVO vo new RunnerVO(); BeanUtils.copyProperties(r, vo); // 计算距离(简化) vo.setDistance(calculateDistance(lat, lon, r.getLatitude(), r.getLongitude())); return vo; }) .sorted(Comparator.comparing(RunnerVO::getDistance)) .collect(Collectors.toList()); } Override public ListErrandOrderVO getAvailableOrders(Long runnerId) { // 查询待接单订单(排除自己已接的) LambdaQueryWrapperErrandOrder wrapper new LambdaQueryWrapper(); wrapper.eq(ErrandOrder::getStatus, 0) .ne(ErrandOrder::getRunnerId, runnerId) .orderByDesc(ErrandOrder::getCreateTime); ListErrandOrder orders orderMapper.selectList(wrapper); return orders.stream() .map(this::convertToVO) .collect(Collectors.toList()); } Override public RunnerStatsVO getStats(Long runnerId) { RunnerStatsVO stats new RunnerStatsVO(); // 今日接单数 stats.setTodayOrders(orderMapper.countTodayOrders(runnerId)); // 总收入 stats.setTotalEarning(runnerMapper.sumEarning(runnerId)); // 评分 Runner runner runnerMapper.selectById(runnerId); stats.setRating(runner ! null ? runner.getRating() : 0.0); return stats; } private Double calculateDistance(Double lat1, Double lon1, Double lat2, Double lon2) { // Haversine公式计算距离(km) double R 6371; double dLat Math.toRadians(lat2 - lat1); double dLon Math.toRadians(lon2 - lon1); double a Math.sin(dLat/2) * Math.sin(dLat/2) Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2); double c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } }5️⃣ JWT工具类JwtUtils.javajavaComponent public class JwtUtils { Value(${jwt.secret:campus-errand-secret-key-2024}) private String secret; private Key getSignKey() { return Keys.hmacShaKeyFor(secret.getBytes()); } /** * 生成Token */ public String generateToken(Long userId) { return Jwts.builder() .subject(String.valueOf(userId)) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .signWith(getSignKey()) .compact(); } /** * 解析Token */ public Long parseToken(String token) { return Long.parseLong(Jwts.parser() .verifyWith(getSignKey()) .build() .parseSignedClaims(token) .getPayload() .getSubject()); } /** * 验证Token */ public boolean validateToken(String token) { try { Jwts.parser().verifyWith(getSignKey()).build().parseSignedClaims(token); return true; } catch (JwtException e) { return false; } } }6️⃣ 拦截器JwtInterceptor.javajavaComponent public class JwtInterceptor implements HandlerInterceptor { Autowired private JwtUtils jwtUtils; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (!(handler instanceof HandlerMethod)) return true; String token request.getHeader(Authorization); if (StringUtils.isBlank(token) || !token.startsWith(Bearer )) { throw new BusinessException(请先登录); } token token.substring(7); if (!jwtUtils.validateToken(token)) { throw new BusinessException(Token已过期); } Long userId jwtUtils.parseToken(token); request.setAttribute(userId, userId); return true; } }️ 四、数据库SQLsqlCREATE DATABASE campus_errand DEFAULT CHARSET utf8mb4; USE campus_errand; -- 用户表 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(100) UNIQUE COMMENT 微信openid, nickname VARCHAR(100) COMMENT 昵称, avatar VARCHAR(500) COMMENT 头像, phone VARCHAR(20) COMMENT 手机号, role TINYINT DEFAULT 0 COMMENT 0-用户 1-跑腿员 2-管理员, balance DECIMAL(10,2) DEFAULT 0 COMMENT 余额, status TINYINT DEFAULT 1 COMMENT 0-禁用 1-正常, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 用户表; -- 跑腿订单表 CREATE TABLE errand_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(64) UNIQUE NOT NULL COMMENT 订单号, user_id BIGINT NOT NULL COMMENT 发布者ID, runner_id BIGINT COMMENT 跑腿员ID, type TINYINT COMMENT 1-代买 2-代拿 3-代送 4-其他, title VARCHAR(200) COMMENT 标题, description TEXT COMMENT 详细描述, pickup_address VARCHAR(500) COMMENT 取货地址, delivery_address VARCHAR(500) COMMENT 送货地址, contact_phone VARCHAR(20) COMMENT 联系电话, reward DECIMAL(10,2) COMMENT 跑腿费, status TINYINT DEFAULT 0 COMMENT 0-待接单 1-进行中 2-已完成 3-已取消 4-已退款, accept_time DATETIME COMMENT 接单时间, finish_time DATETIME COMMENT 完成时间, remark VARCHAR(500) COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_status (status), INDEX idx_user (user_id), INDEX idx_runner (runner_id) ) COMMENT 跑腿订单表; -- 跑腿员表 CREATE TABLE runner ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT UNIQUE NOT NULL COMMENT 用户ID, real_name VARCHAR(50) COMMENT 真实姓名, student_id VARCHAR(50) COMMENT 学号, id_card VARCHAR(18) COMMENT 身份证, status TINYINT DEFAULT 0 COMMENT 0-待审核 1-已认证 2-已拒绝, order_count INT DEFAULT 0 COMMENT 接单总数, total_earning DECIMAL(10,2) DEFAULT 0 COMMENT 总收入, rating DOUBLE DEFAULT 5.0 COMMENT 评分, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 跑腿员表; -- 分类表 CREATE TABLE category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名, icon VARCHAR(200) COMMENT 图标, sort INT DEFAULT 0 COMMENT 排序, status TINYINT DEFAULT 1 COMMENT 0-禁用 1-启用 ) COMMENT 分类表; -- 插入测试分类 INSERT INTO category (name, icon, sort) VALUES (代买饭, /static/icons/food.png, 1), (代拿快递, /static/icons/express.png, 2), (代送文件, /static/icons/file.png, 3), (代打印, /static/icons/print.png, 4), (其他, /static/icons/other.png, 5); 五、配置文件application.ymlyamlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/campus_errand?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver data: redis: host: localhost port: 6379 password: database: 0 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: campus-errand-secret-key-2024-very-long-secure-key # 微信小程序配置(可选) wx: miniapp: appid: your_appid secret: your_secret 六、API接口总览方法接口说明认证POST/api/user/wxLogin微信登录❌GET/api/user/info获取用户信息✅POST/api/user/applyRunner申请跑腿员✅POST/api/errand/publish发布跑腿订单✅GET/api/errand/list订单列表(首页)❌GET/api/errand/detail/{id}订单详情❌POST/api/errand/accept/{id}接单✅(跑腿员)POST/api/errand/cancel/{id}取消订单✅POST/api/errand/rate/{id}评价订单✅GET/api/runner/nearby附近跑腿员❌GET/api/runner/orders接单大厅✅(跑腿员) 七、核心业务流程用户发布订单 → Redis实时推送 → 跑腿员抢单(分布式锁) → 接单 → 完成 → 评价 ↓ [待接单] → [进行中] → [已完成] → [已评价] ↓ 30分钟无人接单 → 自动取消 → 退款 关键技术点技术用途✅ Redis List实时订单队列✅ 分布式锁防止并发接单✅ JWT用户认证✅ MyBatis PlusORM操作✅ 微信登录小程序授权✅ 地理位置计算附近跑腿员UniApp前端代码需要吗我可以继续提供小程序端完整代码