🏃 JAVA校园跑腿系统 - 后端完整代码示例

校园跑腿代买代拿 | Spring Boot + MyBatis Plus + MySQL + Redis


📦 一、项目依赖 pom.xml


xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>

    <groupId>com.campus</groupId>
    <artifactId>campus-errand</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <jwt.version>0.12.3</jwt.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- MyBatis Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jwt.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Hutool工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.23</version>
        </dependency>

        <!-- 微信小程序SDK (可选) -->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-miniapp</artifactId>
            <version>4.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.java


java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "操作成功", data);
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }

    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
}

2️⃣ 实体类

👤 User.java - 用户

java

@Data
@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 - 跑腿订单 ⭐

java

@Data
@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 - 跑腿员

java

@Data
@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 - 分类

java

@Data
@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 - 用户模块

java

@RestController
@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 - 跑腿订单 ⭐⭐⭐

java

@RestController
@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
    ) {
        Page<ErrandOrderVO> 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) {
        List<ErrandOrderVO> 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 - 跑腿员模块

java

@RestController
@RequestMapping("/api/runner")
@CrossOrigin
public class RunnerController {

    @Autowired
    private RunnerService runnerService;

    // 附近跑腿员列表
    @GetMapping("/nearby")
    public Result<?> nearby(@RequestParam Double latitude,
                             @RequestParam Double longitude) {
        List<RunnerVO> runners = runnerService.getNearbyRunners(latitude, longitude);
        return Result.success(runners);
    }

    // 跑腿员接单大厅
    @GetMapping("/orders")
    public Result<?> availableOrders(@RequestAttribute Long runnerId) {
        List<ErrandOrderVO> 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 - 跑腿订单服务(核心)

java

@Service
@Slf4j
public class ErrandServiceImpl implements ErrandService {

    @Autowired
    private ErrandOrderMapper orderMapper;
    
    @Autowired
    private RedisTemplate<String, 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 Page<ErrandOrderVO> getOrderList(Integer page, Integer size, 
                                             Integer type, String keyword) {
        Page<ErrandOrder> pageParam = new Page<>(page, size);
        
        LambdaQueryWrapper<ErrandOrder> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ErrandOrder::getStatus, 0) // 只查待接单
               .like(StringUtils.isNotBlank(keyword), 
                     ErrandOrder::getTitle, keyword)
               .eq(type != null, ErrandOrder::getType, type)
               .orderByDesc(ErrandOrder::getCreateTime);
        
        Page<ErrandOrder> result = orderMapper.selectPage(pageParam, wrapper);
        
        // 转换VO
        Page<ErrandOrderVO> 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 - 跑腿员服务

java

@Service
public class RunnerServiceImpl implements RunnerService {

    @Autowired
    private RunnerMapper runnerMapper;

    @Override
    public List<RunnerVO> getNearbyRunners(Double lat, Double lon) {
        // 实际项目应使用MySQL空间函数或Redis GEO
        // 这里简化为查询所有已认证跑腿员
        LambdaQueryWrapper<Runner> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Runner::getStatus, 1); // 已认证
        
        List<Runner> 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 List<ErrandOrderVO> getAvailableOrders(Long runnerId) {
        // 查询待接单订单(排除自己已接的)
        LambdaQueryWrapper<ErrandOrder> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ErrandOrder::getStatus, 0)
               .ne(ErrandOrder::getRunnerId, runnerId)
               .orderByDesc(ErrandOrder::getCreateTime);
        
        List<ErrandOrder> 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.java


java

@Component
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.java


java

@Component
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;
    }
}

🗄️ 四、数据库SQL


sql

CREATE 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.yml


yaml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/campus_errand?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/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 Plus ORM操作
✅ 微信登录 小程序授权
✅ 地理位置计算 附近跑腿员

📱 UniApp前端代码需要吗?我可以继续提供小程序端完整代码! 🚀

更多推荐