feat: 新增器材管理和课程预约后端接口

This commit is contained in:
Administrator 2026-07-13 15:18:02 +08:00
commit cee9efe2fc
14 changed files with 571 additions and 0 deletions

View file

@ -0,0 +1,57 @@
package edu.situ.stlserver.controller;
import edu.situ.stlserver.model.dto.EnrollmentQueryDTO;
import edu.situ.stlserver.model.entity.Enrollment;
import edu.situ.stlserver.model.vo.HttpResult;
import edu.situ.stlserver.service.EnrollmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/enrollment")
public class EnrollmentController {
@Autowired
private EnrollmentService enrollmentService;
@GetMapping
public HttpResult getByPage(Integer page, Integer limit, EnrollmentQueryDTO queryDTO) {
return HttpResult.success(enrollmentService.getByPage(page, limit, queryDTO));
}
@PostMapping
public HttpResult enroll(@RequestBody Enrollment enrollment) {
enrollmentService.enroll(enrollment);
return HttpResult.success();
}
@PutMapping("/{id}/cancel")
public HttpResult cancel(@PathVariable Integer id) {
enrollmentService.cancel(id);
return HttpResult.success();
}
@PutMapping("/{id}/checkin")
public HttpResult checkin(@PathVariable Integer id) {
enrollmentService.checkin(id);
return HttpResult.success();
}
@PutMapping("/{id}/uncheckin")
public HttpResult uncheckin(@PathVariable Integer id) {
enrollmentService.uncheckin(id);
return HttpResult.success();
}
@PutMapping("/{id}/restore")
public HttpResult restore(@PathVariable Integer id) {
enrollmentService.restore(id);
return HttpResult.success();
}
@DeleteMapping("/{id}")
public HttpResult delete(@PathVariable Integer id) {
enrollmentService.delete(id);
return HttpResult.success();
}
}

View file

@ -0,0 +1,56 @@
package edu.situ.stlserver.controller;
import edu.situ.stlserver.model.dto.EquipmentQueryDTO;
import edu.situ.stlserver.model.entity.Equipment;
import edu.situ.stlserver.model.vo.HttpResult;
import edu.situ.stlserver.service.EquipmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/equipment")
public class EquipmentController {
@Autowired
private EquipmentService equipmentService;
@GetMapping
public HttpResult getByPage(Integer page, Integer limit, EquipmentQueryDTO queryDTO) {
return HttpResult.success(equipmentService.getByPage(page, limit, queryDTO));
}
@GetMapping("/{id}")
public HttpResult getById(@PathVariable Integer id) {
return HttpResult.success(equipmentService.getById(id));
}
@PostMapping
public HttpResult add(@RequestBody Equipment equipment) {
equipmentService.add(equipment);
return HttpResult.success();
}
@PutMapping
public HttpResult update(@RequestBody Equipment equipment) {
equipmentService.update(equipment);
return HttpResult.success();
}
@DeleteMapping("/{id}")
public HttpResult delete(@PathVariable Integer id) {
equipmentService.delete(id);
return HttpResult.success();
}
@PutMapping("/{id}/repair")
public HttpResult repair(@PathVariable Integer id) {
equipmentService.repair(id);
return HttpResult.success();
}
@PutMapping("/{id}/restore")
public HttpResult restore(@PathVariable Integer id) {
equipmentService.restore(id);
return HttpResult.success();
}
}

View file

@ -0,0 +1,56 @@
package edu.situ.stlserver.dao;
import edu.situ.stlserver.model.entity.Enrollment;
import edu.situ.stlserver.model.dto.EnrollmentQueryDTO;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface EnrollmentMapper {
@Select(value = {
"<script>",
"SELECT e.*, m.name AS member_name, c.name AS course_name ",
"FROM enrollments e ",
"LEFT JOIN members m ON e.member_id = m.id ",
"LEFT JOIN courses c ON e.course_id = c.id ",
"<where>",
"<if test=\"memberId != null\"> AND e.member_id = #{memberId}</if>",
"<if test=\"courseId != null\"> AND e.course_id = #{courseId}</if>",
"<if test=\"status != null and status != ''\"> AND e.status = #{status}</if>",
"</where>",
"ORDER BY e.enroll_date DESC",
"</script>"
})
List<Enrollment> select(EnrollmentQueryDTO queryDTO);
@Select("SELECT * FROM enrollments WHERE id = #{id}")
Enrollment selectById(Integer id);
@Select("SELECT * FROM enrollments WHERE member_id = #{memberId} AND course_id = #{courseId} AND status = '已报名'")
Enrollment selectByMemberAndCourse(@Param("memberId") Integer memberId, @Param("courseId") Integer courseId);
@Insert("INSERT INTO enrollments(member_id, course_id, enroll_date, status) " +
"VALUES(#{memberId}, #{courseId}, #{enrollDate}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
void insert(Enrollment enrollment);
@Update("UPDATE enrollments SET status = #{status} WHERE id = #{id}")
void updateStatus(@Param("id") Integer id, @Param("status") String status);
@Delete("DELETE FROM enrollments WHERE id = #{id}")
void delete(Integer id);
// 更新课程报名人数
@Update("UPDATE courses SET enrolled = enrolled + #{delta} WHERE id = #{courseId}")
void updateCourseEnrolled(@Param("courseId") Integer courseId, @Param("delta") int delta);
// 查询课程当前报名人数
@Select("SELECT enrolled FROM courses WHERE id = #{courseId}")
Integer selectCourseEnrolled(Integer courseId);
// 查询课程容量
@Select("SELECT capacity FROM courses WHERE id = #{courseId}")
Integer selectCourseCapacity(Integer courseId);
}

View file

@ -0,0 +1,41 @@
package edu.situ.stlserver.dao;
import edu.situ.stlserver.model.entity.Equipment;
import edu.situ.stlserver.model.dto.EquipmentQueryDTO;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface EquipmentMapper {
@Select(value = {
"<script>",
"SELECT * FROM `equipment`",
"<where>",
"<if test=\"name != null and name != ''\"> AND `name` LIKE CONCAT('%', #{name}, '%')</if>",
"<if test=\"type != null and type != ''\"> AND `type` = #{type}</if>",
"<if test=\"status != null and status != ''\"> AND `status` = #{status}</if>",
"</where>",
"</script>"
})
List<Equipment> select(EquipmentQueryDTO queryDTO);
@Select("SELECT * FROM equipment WHERE id = #{id}")
Equipment selectById(Integer id);
@Insert("INSERT INTO equipment(name, type, brand, model, purchase_date, status, location, remark) " +
"VALUES(#{name}, #{type}, #{brand}, #{model}, #{purchaseDate}, #{status}, #{location}, #{remark})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
void insert(Equipment equipment);
@Update("UPDATE equipment SET name=#{name}, type=#{type}, brand=#{brand}, model=#{model}, " +
"purchase_date=#{purchaseDate}, status=#{status}, location=#{location}, remark=#{remark} WHERE id=#{id}")
void update(Equipment equipment);
@Delete("DELETE FROM equipment WHERE id = #{id}")
void delete(Integer id);
@Update("UPDATE equipment SET status = #{status} WHERE id = #{id}")
void updateStatus(@Param("id") Integer id, @Param("status") String status);
}

View file

@ -0,0 +1,14 @@
package edu.situ.stlserver.model.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@NoArgsConstructor
@Data
public class EnrollmentQueryDTO {
private Integer memberId;
private Integer courseId;
private String status;
}

View file

@ -0,0 +1,14 @@
package edu.situ.stlserver.model.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@NoArgsConstructor
@Data
public class EquipmentQueryDTO {
private String name;
private String type;
private String status;
}

View file

@ -0,0 +1,20 @@
package edu.situ.stlserver.model.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@NoArgsConstructor
@Data
public class Enrollment {
private Integer id;
private Integer memberId;
private Integer courseId;
private String enrollDate;
private String status;
// 关联查询字段
private String memberName;
private String courseName;
}

View file

@ -0,0 +1,20 @@
package edu.situ.stlserver.model.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@NoArgsConstructor
@Data
public class Equipment {
private Integer id;
private String name;
private String type;
private String brand;
private String model;
private String purchaseDate;
private String status;
private String location;
private String remark;
}

View file

@ -0,0 +1,15 @@
package edu.situ.stlserver.service;
import com.github.pagehelper.PageInfo;
import edu.situ.stlserver.model.dto.EnrollmentQueryDTO;
import edu.situ.stlserver.model.entity.Enrollment;
public interface EnrollmentService {
PageInfo<Enrollment> getByPage(Integer page, Integer limit, EnrollmentQueryDTO queryDTO);
void enroll(Enrollment enrollment);
void cancel(Integer id);
void checkin(Integer id);
void uncheckin(Integer id);
void restore(Integer id);
void delete(Integer id);
}

View file

@ -0,0 +1,15 @@
package edu.situ.stlserver.service;
import com.github.pagehelper.PageInfo;
import edu.situ.stlserver.model.dto.EquipmentQueryDTO;
import edu.situ.stlserver.model.entity.Equipment;
public interface EquipmentService {
PageInfo<Equipment> getByPage(Integer page, Integer limit, EquipmentQueryDTO queryDTO);
Equipment getById(Integer id);
void add(Equipment equipment);
void update(Equipment equipment);
void delete(Integer id);
void repair(Integer id);
void restore(Integer id);
}

View file

@ -0,0 +1,132 @@
package edu.situ.stlserver.service.impl;
import cn.hutool.core.date.DateUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import edu.situ.stlserver.dao.EnrollmentMapper;
import edu.situ.stlserver.model.dto.EnrollmentQueryDTO;
import edu.situ.stlserver.model.entity.Enrollment;
import edu.situ.stlserver.service.EnrollmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class EnrollmentServiceImpl implements EnrollmentService {
@Autowired
private EnrollmentMapper enrollmentMapper;
@Override
public PageInfo<Enrollment> getByPage(Integer page, Integer limit, EnrollmentQueryDTO queryDTO) {
if (page == null) page = 1;
if (limit == null) limit = 10;
PageHelper.startPage(page, limit);
return new PageInfo<>(enrollmentMapper.select(queryDTO));
}
@Override
@Transactional
public void enroll(Enrollment enrollment) {
// 校验是否已报名
Enrollment existing = enrollmentMapper.selectByMemberAndCourse(enrollment.getMemberId(), enrollment.getCourseId());
if (existing != null) {
throw new RuntimeException("该会员已报名此课程");
}
// 校验课程容量
Integer enrolled = enrollmentMapper.selectCourseEnrolled(enrollment.getCourseId());
Integer capacity = enrollmentMapper.selectCourseCapacity(enrollment.getCourseId());
if (enrolled != null && capacity != null && enrolled >= capacity) {
throw new RuntimeException("该课程已满员");
}
enrollment.setEnrollDate(DateUtil.formatDateTime(DateUtil.date()));
enrollment.setStatus("已报名");
enrollmentMapper.insert(enrollment);
// 更新课程报名人数
enrollmentMapper.updateCourseEnrolled(enrollment.getCourseId(), 1);
}
@Override
@Transactional
public void cancel(Integer id) {
Enrollment enrollment = enrollmentMapper.selectById(id);
if (enrollment == null) {
throw new RuntimeException("预约记录不存在");
}
if ("已取消".equals(enrollment.getStatus())) {
throw new RuntimeException("该预约已取消");
}
enrollmentMapper.updateStatus(id, "已取消");
// 释放课程名额
enrollmentMapper.updateCourseEnrolled(enrollment.getCourseId(), -1);
}
@Override
public void checkin(Integer id) {
Enrollment enrollment = enrollmentMapper.selectById(id);
if (enrollment == null) {
throw new RuntimeException("预约记录不存在");
}
if (!"已报名".equals(enrollment.getStatus())) {
throw new RuntimeException("只有已报名的记录才能签到");
}
enrollmentMapper.updateStatus(id, "已签到");
}
@Override
public void uncheckin(Integer id) {
Enrollment enrollment = enrollmentMapper.selectById(id);
if (enrollment == null) {
throw new RuntimeException("预约记录不存在");
}
if (!"已签到".equals(enrollment.getStatus())) {
throw new RuntimeException("只有已签到的记录才能取消签到");
}
enrollmentMapper.updateStatus(id, "已报名");
}
@Override
@Transactional
public void restore(Integer id) {
Enrollment enrollment = enrollmentMapper.selectById(id);
if (enrollment == null) {
throw new RuntimeException("预约记录不存在");
}
if (!"已取消".equals(enrollment.getStatus())) {
throw new RuntimeException("只有已取消的记录才能恢复");
}
// 校验课程容量
Integer enrolled = enrollmentMapper.selectCourseEnrolled(enrollment.getCourseId());
Integer capacity = enrollmentMapper.selectCourseCapacity(enrollment.getCourseId());
if (enrolled != null && capacity != null && enrolled >= capacity) {
throw new RuntimeException("该课程已满员,无法恢复报名");
}
enrollmentMapper.updateStatus(id, "已报名");
// 恢复课程报名人数
enrollmentMapper.updateCourseEnrolled(enrollment.getCourseId(), 1);
}
@Override
@Transactional
public void delete(Integer id) {
Enrollment enrollment = enrollmentMapper.selectById(id);
if (enrollment == null) {
throw new RuntimeException("预约记录不存在");
}
enrollmentMapper.delete(id);
// 如果是有效报名,释放课程名额
if ("已报名".equals(enrollment.getStatus()) || "已签到".equals(enrollment.getStatus())) {
enrollmentMapper.updateCourseEnrolled(enrollment.getCourseId(), -1);
}
}
}

View file

@ -0,0 +1,66 @@
package edu.situ.stlserver.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import edu.situ.stlserver.dao.EquipmentMapper;
import edu.situ.stlserver.model.dto.EquipmentQueryDTO;
import edu.situ.stlserver.model.entity.Equipment;
import edu.situ.stlserver.service.EquipmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EquipmentServiceImpl implements EquipmentService {
@Autowired
private EquipmentMapper equipmentMapper;
@Override
public PageInfo<Equipment> getByPage(Integer page, Integer limit, EquipmentQueryDTO queryDTO) {
if (page == null) page = 1;
if (limit == null) limit = 10;
PageHelper.startPage(page, limit);
return new PageInfo<>(equipmentMapper.select(queryDTO));
}
@Override
public Equipment getById(Integer id) {
return equipmentMapper.selectById(id);
}
@Override
public void add(Equipment equipment) {
if (equipment.getStatus() == null || equipment.getStatus().isEmpty()) {
equipment.setStatus("正常");
}
equipmentMapper.insert(equipment);
}
@Override
public void update(Equipment equipment) {
equipmentMapper.update(equipment);
}
@Override
public void delete(Integer id) {
equipmentMapper.delete(id);
}
@Override
public void repair(Integer id) {
Equipment equipment = equipmentMapper.selectById(id);
if (!"正常".equals(equipment.getStatus())) {
throw new RuntimeException("只有正常状态的器材才能送修");
}
equipmentMapper.updateStatus(id, "维修中");
}
@Override
public void restore(Integer id) {
Equipment equipment = equipmentMapper.selectById(id);
if (!"维修中".equals(equipment.getStatus())) {
throw new RuntimeException("只有维修中的器材才能恢复");
}
equipmentMapper.updateStatus(id, "正常");
}
}

View file

@ -65,3 +65,44 @@ INSERT INTO `courses` (`name`, `type`, `coach_name`, `price`, `duration`, `statu
('空手道', '搏击', '杨丽', 279, '55分钟', '进行中', '防身技能', '2024-08-09 18:30', 16, 20), ('空手道', '搏击', '杨丽', 279, '55分钟', '进行中', '防身技能', '2024-08-09 18:30', 16, 20),
('肚皮舞', '舞蹈', '黄勇', 189, '45分钟', '进行中', '女性专属', '2024-08-10 13:00', 22, 25), ('肚皮舞', '舞蹈', '黄勇', 189, '45分钟', '进行中', '女性专属', '2024-08-10 13:00', 22, 25),
('CrossFit', '综合', '徐静', 359, '60分钟', '进行中', '综合训练', '2024-08-11 07:30', 18, 20); ('CrossFit', '综合', '徐静', 359, '60分钟', '进行中', '综合训练', '2024-08-11 07:30', 18, 20);
-- 器材模拟数据
INSERT INTO `equipment` (`name`, `type`, `brand`, `model`, `purchase_date`, `status`, `location`, `remark`) VALUES
('跑步机 T100', '有氧', 'Technogym', 'T100 Pro', '2023-06-15', '正常', '有氧区A', '高端商用跑步机'),
('椭圆机 E200', '有氧', 'Life Fitness', 'E200', '2023-06-15', '正常', '有氧区A', '低冲击有氧训练'),
('动感单车 S50', '有氧', 'Spinning', 'S50', '2023-08-20', '正常', '动感单车房', '团体课程专用'),
('划船机 R30', '有氧', 'Concept2', 'RowErg', '2024-01-10', '维修中', '有氧区B', '水阻划船机,需更换水箱'),
('史密斯机 SM100', '力量', 'Hammer Strength', 'Smith Machine', '2023-06-15', '正常', '力量区', '综合力量训练'),
('龙门架 C200', '力量', 'Life Fitness', 'Cable Motion', '2023-06-15', '正常', '力量区', '多功能缆绳训练'),
('卧推架 BP50', '力量', 'Rogue', 'BP50', '2023-09-01', '正常', '力量区', '平板卧推专用'),
('深蹲架 SQ30', '自由重量', 'Rogue', 'R-3', '2023-09-01', '正常', '自由重量区', '力量训练核心设备'),
('哑铃套装', '自由重量', 'Jordan', '5-50kg', '2023-07-10', '正常', '自由重量区', '5-50kg各一对'),
('杠铃杆 Olympic', '自由重量', 'Eleiko', 'IWF', '2023-07-10', '正常', '自由重量区', '奥林匹克标准杠铃'),
('壶铃套装', '功能训练', 'Rogue', '8-32kg', '2024-02-15', '正常', '功能训练区', '8-32kg组合'),
('战绳 Rope50', '功能训练', 'Onnit', '50ft', '2024-03-01', '正常', '功能训练区', '50英尺战绳'),
('药球 Ball10', '功能训练', 'Dynamax', '10lb', '2024-03-01', '正常', '功能训练区', '10磅药球'),
('TRX悬挂训练带', '功能训练', 'TRX', 'Pro4', '2024-04-10', '已报废', '功能训练区', '使用超过2年,已更换'),
('瑜伽垫套装', '有氧', 'Manduka', 'PRO', '2024-05-20', '正常', '瑜伽室', '20张专业瑜伽垫');
-- 预约模拟数据
INSERT INTO `enrollments` (`member_id`, `course_id`, `enroll_date`, `status`) VALUES
(1, 1, '2024-08-01 10:00:00', '已报名'),
(1, 3, '2024-08-01 10:30:00', '已签到'),
(2, 2, '2024-08-01 11:00:00', '已报名'),
(2, 5, '2024-08-01 11:30:00', '已签到'),
(3, 1, '2024-08-02 09:00:00', '已签到'),
(3, 4, '2024-08-02 09:30:00', '已报名'),
(4, 7, '2024-08-02 14:00:00', '已报名'),
(4, 9, '2024-08-02 14:30:00', '已签到'),
(5, 3, '2024-08-03 10:00:00', '已取消'),
(5, 6, '2024-08-03 10:30:00', '已报名'),
(6, 2, '2024-08-03 15:00:00', '已报名'),
(6, 8, '2024-08-03 15:30:00', '已签到'),
(7, 5, '2024-08-04 09:00:00', '已报名'),
(7, 10, '2024-08-04 09:30:00', '已报名'),
(8, 1, '2024-08-04 11:00:00', '已报名'),
(8, 3, '2024-08-04 11:30:00', '已取消'),
(9, 4, '2024-08-05 14:00:00', '已报名'),
(9, 9, '2024-08-05 14:30:00', '已签到'),
(10, 7, '2024-08-05 16:00:00', '已报名'),
(10, 11, '2024-08-05 16:30:00', '已报名');

View file

@ -45,3 +45,27 @@ CREATE TABLE IF NOT EXISTS `courses` (
`enrolled` INT DEFAULT 0 COMMENT '已报名人数', `enrolled` INT DEFAULT 0 COMMENT '已报名人数',
`capacity` INT NOT NULL COMMENT '课程容量' `capacity` INT NOT NULL COMMENT '课程容量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 器材表
CREATE TABLE IF NOT EXISTS `equipment` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL COMMENT '器材名称',
`type` VARCHAR(50) NOT NULL COMMENT '器材类型:有氧/力量/自由重量/功能训练',
`brand` VARCHAR(50) DEFAULT NULL COMMENT '品牌',
`model` VARCHAR(50) DEFAULT NULL COMMENT '型号',
`purchase_date` DATE DEFAULT NULL COMMENT '购买日期',
`status` VARCHAR(20) NOT NULL DEFAULT '正常' COMMENT '状态:正常/维修中/已报废',
`location` VARCHAR(100) DEFAULT NULL COMMENT '存放位置',
`remark` TEXT DEFAULT NULL COMMENT '备注'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 预约表
CREATE TABLE IF NOT EXISTS `enrollments` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`member_id` INT NOT NULL COMMENT '会员ID',
`course_id` INT NOT NULL COMMENT '课程ID',
`enroll_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '报名时间',
`status` VARCHAR(20) NOT NULL DEFAULT '已报名' COMMENT '状态:已报名/已签到/已取消',
FOREIGN KEY (`member_id`) REFERENCES `members`(`id`),
FOREIGN KEY (`course_id`) REFERENCES `courses`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;