Compare commits

..

3 Commits

24 changed files with 396 additions and 13 deletions

View File

@ -61,6 +61,11 @@
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -1,19 +1,28 @@
package github.benjamin.equipreservebackend.controller;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import github.benjamin.equipreservebackend.entity.Device;
import github.benjamin.equipreservebackend.response.ResponseResult;
import github.benjamin.equipreservebackend.service.DeviceService;
import github.benjamin.equipreservebackend.service.ReservationService;
import github.benjamin.equipreservebackend.vo.DeviceAdminVO;
import github.benjamin.equipreservebackend.vo.DeviceStatsVO;
import github.benjamin.equipreservebackend.vo.DeviceUserVO;
import github.benjamin.equipreservebackend.vo.TimeRangeVO;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
@RestController
@ -45,7 +54,7 @@ public class DeviceController {
@PreAuthorize("hasRole('DEVICE_ADMIN')")
@PostMapping("/{userId}")
public ResponseResult<Device> addDevice(@PathVariable("userId") Long userId,
@RequestBody Device device) {
@RequestBody Device device) {
deviceService.addDevice(userId, device);
return ResponseResult.success(device);
}
@ -69,9 +78,9 @@ public class DeviceController {
@PreAuthorize("hasRole('DEVICE_ADMIN')")
@GetMapping("/{userId}")
public ResponseResult<Page<DeviceAdminVO>> getAdminDevice(@PathVariable("userId") Long userId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String name) {
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String name) {
Page<Device> pageRequest = new Page<>(page, size);
return ResponseResult.success(deviceService.getAdminDevice(pageRequest, userId, name));
@ -84,4 +93,31 @@ public class DeviceController {
String imagePath = deviceService.saveImage(id, image);
return ResponseResult.success(imagePath);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/usage-stats")
public ResponseResult<List<DeviceStatsVO>> getDeviceUsageStats(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate start,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
return ResponseResult.success(deviceService.getDeviceUsageStats(start, end));
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/usage-stats/export")
public void exportDeviceUsageStats(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate start,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end,
HttpServletResponse response) throws IOException {
String fileName = String.format("设备使用统计_%s-%s.xlsx", start.format(DateTimeFormatter.ISO_DATE), end.format(DateTimeFormatter.ISO_DATE));
String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + encodedName);
// 查询数据
List<DeviceStatsVO> data = deviceService.getDeviceUsageStats(start, end);
// 使用EasyExcel写入response流
EasyExcel.write(response.getOutputStream(), DeviceStatsVO.class)
.sheet("设备使用统计")
.doWrite(data);
}
}

View File

@ -0,0 +1,24 @@
package github.benjamin.equipreservebackend.controller;
import github.benjamin.equipreservebackend.response.ResponseResult;
import github.benjamin.equipreservebackend.service.RoleService;
import github.benjamin.equipreservebackend.vo.RoleVO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/role")
@RequiredArgsConstructor
public class RoleController {
private final RoleService roleService;
@GetMapping
public ResponseResult<List<RoleVO>> getRoles() {
return ResponseResult.success(roleService.list().stream().map(RoleVO::new).toList());
}
}

View File

@ -0,0 +1,26 @@
package github.benjamin.equipreservebackend.controller;
import github.benjamin.equipreservebackend.mapper.TeamMapper;
import github.benjamin.equipreservebackend.response.ResponseResult;
import github.benjamin.equipreservebackend.service.TeamService;
import github.benjamin.equipreservebackend.vo.TeamVO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping
@RequiredArgsConstructor
public class TeamController {
private final TeamService teamService;
@GetMapping("/teams")
public ResponseResult<List<TeamVO>> getTeams() {
return ResponseResult.success(teamService.getTeams());
}
}

View File

@ -1,5 +1,6 @@
package github.benjamin.equipreservebackend.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import github.benjamin.equipreservebackend.dto.LoginRequest;
import github.benjamin.equipreservebackend.dto.UserDTO;
import github.benjamin.equipreservebackend.entity.Role;
@ -11,6 +12,7 @@ import github.benjamin.equipreservebackend.utils.JwtUtil;
import github.benjamin.equipreservebackend.vo.LoginResponse;
import github.benjamin.equipreservebackend.vo.UserVO;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -37,11 +39,33 @@ public class UserController {
return ResponseResult.success(userService.getUserVO(id));
}
@PatchMapping("/user/{id}")
@PutMapping("/user/{id}")
public ResponseResult<UserVO> updateUser(@PathVariable Long id,
@RequestBody UserDTO dto) {
userService.updateUser(id, dto);
return ResponseResult.success(userService.getUserVO(id));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/user")
public ResponseResult<?> addUser(@RequestBody UserDTO dto) {
userService.addUser(dto);
return ResponseResult.success();
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/user")
public ResponseResult<Page<UserVO>> getUserVOs(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String name) {
Page<User> pageRequest = new Page<>(page, size);
return ResponseResult.success(userService.getUserVOs(pageRequest, name));
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/user/{userId}")
public ResponseResult<?> deleteUser(@PathVariable("userId") Long userId) {
userService.deleteById(userId);
return ResponseResult.success();
}
}

View File

@ -5,8 +5,11 @@ import lombok.Data;
@Data
public class UserDTO {
private String username;
private String name;
private String phone;
private String password;
private Long teamId;
private Long roleId;
}

View File

@ -1,12 +1,15 @@
package github.benjamin.equipreservebackend.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import github.benjamin.equipreservebackend.dto.UserDTO;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@TableName("users")
@NoArgsConstructor
public class User {
private Long id;
@ -19,4 +22,12 @@ public class User {
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
public User(UserDTO dto) {
this.username = dto.getUsername();
this.password = dto.getPassword();
this.name = dto.getName();
this.phone = dto.getPhone();
this.teamId = dto.getTeamId();
}
}

View File

@ -0,0 +1,17 @@
package github.benjamin.equipreservebackend.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@TableName("user_roles")
@NoArgsConstructor
@AllArgsConstructor
public class UserRole {
private Long userId;
private Long roleId;
}

View File

@ -2,8 +2,13 @@ package github.benjamin.equipreservebackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import github.benjamin.equipreservebackend.entity.Device;
import github.benjamin.equipreservebackend.vo.DeviceStatsVO;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface DeviceMapper extends BaseMapper<Device> {
List<DeviceStatsVO> getDeviceUsageStats(LocalDate start, LocalDate end);
}

View File

@ -0,0 +1,9 @@
package github.benjamin.equipreservebackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import github.benjamin.equipreservebackend.entity.UserRole;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRoleMapper extends BaseMapper<UserRole> {
}

View File

@ -3,10 +3,13 @@ package github.benjamin.equipreservebackend.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import github.benjamin.equipreservebackend.entity.Device;
import github.benjamin.equipreservebackend.vo.DeviceAdminVO;
import github.benjamin.equipreservebackend.vo.DeviceStatsVO;
import github.benjamin.equipreservebackend.vo.DeviceUserVO;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
public interface DeviceService {
Page<DeviceUserVO> getUserDevices(Page<Device> pageRequest, String name);
@ -27,4 +30,6 @@ public interface DeviceService {
* @return 分页后的设备列表
*/
Page<DeviceAdminVO> getAdminDevice(Page<Device> pageRequest, Long userId, String name);
List<DeviceStatsVO> getDeviceUsageStats(LocalDate start, LocalDate end);
}

View File

@ -0,0 +1,7 @@
package github.benjamin.equipreservebackend.service;
import com.baomidou.mybatisplus.extension.service.IService;
import github.benjamin.equipreservebackend.entity.Role;
public interface RoleService extends IService<Role> {
}

View File

@ -1,4 +1,9 @@
package github.benjamin.equipreservebackend.service;
import github.benjamin.equipreservebackend.vo.TeamVO;
import java.util.List;
public interface TeamService {
List<TeamVO> getTeams();
}

View File

@ -1,5 +1,6 @@
package github.benjamin.equipreservebackend.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import github.benjamin.equipreservebackend.dto.UserDTO;
import github.benjamin.equipreservebackend.entity.User;
import github.benjamin.equipreservebackend.security.SecurityUser;
@ -14,4 +15,10 @@ public interface UserService {
UserVO getUserVO(Long id);
void updateUser(Long id, UserDTO dto);
Page<UserVO> getUserVOs(Page<User> pageRequest, String name);
void deleteById(Long userId);
void addUser(UserDTO dto);
}

View File

@ -17,6 +17,7 @@ import github.benjamin.equipreservebackend.service.ReservationService;
import github.benjamin.equipreservebackend.utils.FileUtil;
import github.benjamin.equipreservebackend.utils.PageUtil;
import github.benjamin.equipreservebackend.vo.DeviceAdminVO;
import github.benjamin.equipreservebackend.vo.DeviceStatsVO;
import github.benjamin.equipreservebackend.vo.DeviceUserVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -29,6 +30,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@ -149,6 +151,11 @@ public class DeviceServiceImpl implements DeviceService {
return res;
}
@Override
public List<DeviceStatsVO> getDeviceUsageStats(LocalDate start, LocalDate end) {
return deviceMapper.getDeviceUsageStats(start, end);
}
private DeviceUserVO buildDeviceVO(Device device, List<Reservation> reservations) {
DeviceUserVO vo = new DeviceUserVO(device);
// TODO 显示设备状态逻辑根据客户需求修改

View File

@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -48,19 +49,15 @@ public class ReservationServiceImpl implements ReservationService {
LocalDate endTime = now.plusDays(days);
return reservationMapper.selectList(new LambdaQueryWrapper<Reservation>()
.in(Reservation::getDeviceId, devicesIds)
.eq(Reservation::getStatus, ReservationStatus.APPROVED)
.in(Reservation::getStatus, List.of(ReservationStatus.APPROVED, ReservationStatus.APPROVED_ASSIST))
.between(Reservation::getStartTime, now, endTime));
}
@Override
public void addReservation(Reservation reservation) {
User user = userMapper.selectById(reservation.getUserId());
Team team = teamMapper.selectById(user.getTeamId());
Device device = deviceMapper.selectById(reservation.getDeviceId());
reservation.setApplicantName(user.getName());
reservation.setApplicantTeam(team.getName());
reservation.setApplicantContact(user.getPhone());
reservation.setDeviceAdminId(device.getDeviceAdminId());
reservation.setStatus(String.valueOf(ReservationStatus.PENDING_LEADER));
reservationMapper.insert(reservation);
@ -71,6 +68,13 @@ public class ReservationServiceImpl implements ReservationService {
Page<Reservation> reservations = reservationMapper.selectPage(pageRequest, new LambdaQueryWrapper<Reservation>()
.eq(Reservation::getUserId, userId)
.orderByDesc(Reservation::getCreatedTime));
Page<UserReservationVO> res = PageUtil.copyPage(reservations);
if (reservations.getRecords().isEmpty()) {
res.setTotal(0);
res.setRecords(Collections.EMPTY_LIST);
return res;
}
// 获取设备名称
List<Long> deviceIds = reservations.getRecords().stream()
.map(Reservation::getDeviceId)
@ -88,7 +92,7 @@ public class ReservationServiceImpl implements ReservationService {
.distinct()
.toList();
Page<UserReservationVO> res = PageUtil.copyPage(reservations);
List<UserReservationVO> vos;
Map<Long, User> deviceAdminMap = userMapper.selectList(new LambdaQueryWrapper<User>()
.in(User::getId, deviceAdminIDs))
@ -106,7 +110,7 @@ public class ReservationServiceImpl implements ReservationService {
public List<TimeRangeVO> getUnavailableTimes(Long id) {
List<Reservation> reservations = reservationMapper.selectList(new LambdaQueryWrapper<Reservation>()
.eq(Reservation::getDeviceId, id)
.eq(Reservation::getStatus, "APPROVED")
.in(Reservation::getStatus, List.of(ReservationStatus.APPROVED, ReservationStatus.APPROVED_ASSIST))
.gt(Reservation::getEndTime, LocalDate.now()));
return reservations.stream()

View File

@ -0,0 +1,11 @@
package github.benjamin.equipreservebackend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import github.benjamin.equipreservebackend.entity.Role;
import github.benjamin.equipreservebackend.mapper.RoleMapper;
import github.benjamin.equipreservebackend.service.RoleService;
import org.springframework.stereotype.Service;
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
}

View File

@ -0,0 +1,25 @@
package github.benjamin.equipreservebackend.service.impl;
import github.benjamin.equipreservebackend.entity.Team;
import github.benjamin.equipreservebackend.mapper.TeamMapper;
import github.benjamin.equipreservebackend.service.TeamService;
import github.benjamin.equipreservebackend.vo.TeamVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class TeamServiceImpl implements TeamService {
private final TeamMapper teamMapper;
@Override
public List<TeamVO> getTeams() {
List<Team> teams = teamMapper.selectList(null);
return teams.stream().map(TeamVO::new).toList();
}
}

View File

@ -2,18 +2,22 @@ package github.benjamin.equipreservebackend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import github.benjamin.equipreservebackend.dto.UserDTO;
import github.benjamin.equipreservebackend.entity.Role;
import github.benjamin.equipreservebackend.entity.Team;
import github.benjamin.equipreservebackend.entity.User;
import github.benjamin.equipreservebackend.entity.UserRole;
import github.benjamin.equipreservebackend.exception.ApiException;
import github.benjamin.equipreservebackend.mapper.RoleMapper;
import github.benjamin.equipreservebackend.mapper.TeamMapper;
import github.benjamin.equipreservebackend.mapper.UserMapper;
import github.benjamin.equipreservebackend.mapper.UserRoleMapper;
import github.benjamin.equipreservebackend.response.ResponseCode;
import github.benjamin.equipreservebackend.security.SecurityUser;
import github.benjamin.equipreservebackend.service.UserService;
import github.benjamin.equipreservebackend.utils.JwtUtil;
import github.benjamin.equipreservebackend.utils.PageUtil;
import github.benjamin.equipreservebackend.utils.PasswordUtil;
import github.benjamin.equipreservebackend.vo.UserVO;
import lombok.RequiredArgsConstructor;
@ -21,7 +25,10 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@ -33,6 +40,8 @@ public class UserServiceImpl implements UserService {
private final TeamMapper teamMapper;
private final UserRoleMapper userRoleMapper;
private final JwtUtil jwtUtil;
@Override
@ -66,11 +75,44 @@ public class UserServiceImpl implements UserService {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
.eq(User::getId, id)
.set(StringUtils.hasText(dto.getName()), User::getName, dto.getName())
.set(StringUtils.hasText(dto.getPhone()), User::getPhone, dto.getPhone());
.set(StringUtils.hasText(dto.getPhone()), User::getPhone, dto.getPhone())
.set(StringUtils.hasText(dto.getUsername()),User::getUsername, dto.getUsername())
.set(Objects.nonNull(dto.getTeamId()), User::getTeamId, dto.getTeamId());
if (StringUtils.hasText(dto.getPassword())) {
String encoded = PasswordUtil.encode(dto.getPassword());
wrapper.set(User::getPassword, encoded);
}
userMapper.update(wrapper);
}
@Override
public Page<UserVO> getUserVOs(Page<User> pageRequest, String name) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.orderByAsc(User::getName);
if (StringUtils.hasText(name)) {
wrapper.like(User::getName, name);
}
Page<User> users = userMapper.selectPage(pageRequest, wrapper);
Map<Long, Team> teamMap = teamMapper.selectList(null).stream()
.collect(Collectors.toMap(Team::getId, Function.identity()));
Map<Long, Long> roleMap = userRoleMapper.selectList(null).stream()
.collect(Collectors.toMap(UserRole::getUserId, UserRole::getRoleId, (v1, v2) -> v1));
Page<UserVO> res = PageUtil.copyPage(users);
res.setRecords(users.getRecords().stream().map(u -> new UserVO(u, teamMap, roleMap)).toList());
return res;
}
@Override
public void deleteById(Long userId) {
userMapper.deleteById(userId);
}
@Override
public void addUser(UserDTO dto) {
String encoded = PasswordUtil.encode(dto.getPassword());
dto.setPassword(encoded);
User user = new User(dto);
userMapper.insert(user);
userRoleMapper.insert(new UserRole(user.getId(), dto.getRoleId()));
}
}

View File

@ -0,0 +1,29 @@
package github.benjamin.equipreservebackend.vo;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
@Data
public class DeviceStatsVO {
@JsonSerialize(using = ToStringSerializer.class)
@ExcelIgnore
private Long deviceId;
@ColumnWidth(15)
@ExcelProperty("设备名称")
private String deviceName;
@ColumnWidth(15)
@ExcelProperty("使用次数")
private Integer usageCount;
@ColumnWidth(20)
@ExcelProperty("使用时长(天)")
private Integer totalUsageDays;
}

View File

@ -0,0 +1,19 @@
package github.benjamin.equipreservebackend.vo;
import github.benjamin.equipreservebackend.entity.Role;
import github.benjamin.equipreservebackend.entity.Team;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class RoleVO {
private String label;
private String value;
public RoleVO(Role r) {
this.label = r.getName();
this.value = r.getId().toString();
}
}

View File

@ -0,0 +1,18 @@
package github.benjamin.equipreservebackend.vo;
import github.benjamin.equipreservebackend.entity.Team;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class TeamVO {
private String label;
private String value;
public TeamVO(Team t) {
this.label = t.getName();
this.value = t.getId().toString();
}
}

View File

@ -1,25 +1,47 @@
package github.benjamin.equipreservebackend.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import github.benjamin.equipreservebackend.entity.Team;
import github.benjamin.equipreservebackend.entity.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserVO {
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
private String username;
private String team;
@JsonSerialize(using = ToStringSerializer.class)
private Long teamId;
private String name;
private String phone;
@JsonSerialize(using = ToStringSerializer.class)
private Long roleId;
public UserVO(User u, Team t) {
this.userId = u.getId();
this.username = u.getUsername();
this.name = u.getName();
this.phone = u.getPhone();
this.team = t.getName();
this.teamId = u.getId();
}
public UserVO(User u, Map<Long, Team> teamMap, Map<Long, Long> roleMap) {
this.userId = u.getId();
this.username = u.getUsername();
this.name = u.getName();
this.phone = u.getPhone();
this.team = teamMap.get(u.getTeamId()).getName();
this.teamId = u.getTeamId();
this.roleId = roleMap.get(u.getId());
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="github.benjamin.equipreservebackend.mapper.DeviceMapper">
<select id="getDeviceUsageStats" resultType="github.benjamin.equipreservebackend.vo.DeviceStatsVO">
SELECT
d.id AS deviceId,
d.name AS deviceName,
COUNT(*) AS usageCount,
SUM(TIMESTAMPDIFF(DAY, r.start_time, r.end_time)) AS totalUsageDays
FROM reservations r
JOIN devices d ON r.device_id = d.id
WHERE r.status IN ('APPROVED', 'APPROVED_ASSIST')
AND r.start_time >= #{start}
AND r.end_time &lt; #{end}
GROUP BY d.id
ORDER BY totalUsageDays DESC
</select>
</mapper>