Compare commits
4 Commits
078c18fec9
...
81e98a4516
| Author | SHA1 | Date |
|---|---|---|
|
|
81e98a4516 | |
|
|
7f975aac29 | |
|
|
407bdadf6f | |
|
|
7cc0674b90 |
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.URLUtil;
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||||
|
import com.agileboot.common.utils.file.FileUploadUtils;
|
||||||
|
import com.agileboot.domain.system.log.LogDownloadApplicationService;
|
||||||
|
import com.agileboot.domain.system.log.dto.LogFileDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志下载控制器 (管理后台使用)
|
||||||
|
*
|
||||||
|
* @author agileboot
|
||||||
|
*/
|
||||||
|
@Tag(name = "日志下载API", description = "服务器日志下载相关接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/log")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class LogDownloadController extends BaseController {
|
||||||
|
|
||||||
|
private final LogDownloadApplicationService logDownloadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取日志文件列表")
|
||||||
|
@GetMapping("/files")
|
||||||
|
public ResponseDTO<List<LogFileDTO>> getLogFileList() {
|
||||||
|
List<LogFileDTO> fileList = logDownloadService.getLogFileList();
|
||||||
|
return ResponseDTO.ok(fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载日志文件
|
||||||
|
*
|
||||||
|
* @param logType 日志类型: info, error, debug
|
||||||
|
*/
|
||||||
|
@Operation(summary = "下载日志文件")
|
||||||
|
@GetMapping("/download/{logType}")
|
||||||
|
public ResponseEntity<byte[]> downloadLog(@PathVariable String logType) {
|
||||||
|
try {
|
||||||
|
// 1. 获取日志文件内容
|
||||||
|
byte[] fileContent = logDownloadService.getLogFileContent(logType);
|
||||||
|
|
||||||
|
// 2. 获取文件名
|
||||||
|
String fileName = logDownloadService.getLogFileName(logType);
|
||||||
|
|
||||||
|
// 3. 构建下载响应头
|
||||||
|
HttpHeaders headers = FileUploadUtils.getDownloadHeader(fileName);
|
||||||
|
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(fileContent, headers, org.springframework.http.HttpStatus.OK);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
log.error("下载日志文件失败: logType={}, error={}", logType, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载日志文件失败: logType={}", logType, e);
|
||||||
|
throw new ApiException(Business.LOG_FILE_READ_FAILED, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -137,7 +137,8 @@ public class SecurityConfig {
|
||||||
.antMatchers("/login", "/register", "/captchaImage", "/api/**", "/file/**").anonymous()
|
.antMatchers("/login", "/register", "/captchaImage", "/api/**", "/file/**").anonymous()
|
||||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
|
||||||
"/profile/**").permitAll()
|
"/profile/**").permitAll()
|
||||||
.antMatchers("/manual/**", "/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo", "/getConfig").permitAll()
|
.antMatchers("/manual/**", "/qywx/**", "/test/**", "/monitor/**",
|
||||||
|
"/system/log/**", "/getQyUserinfo", "/getConfig").permitAll()
|
||||||
// TODO this is danger.
|
// TODO this is danger.
|
||||||
.antMatchers("/swagger-ui.html").anonymous()
|
.antMatchers("/swagger-ui.html").anonymous()
|
||||||
.antMatchers("/swagger-resources/**").anonymous()
|
.antMatchers("/swagger-resources/**").anonymous()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.agileboot.api.controller;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||||
|
import com.agileboot.common.utils.file.FileUploadUtils;
|
||||||
|
import com.agileboot.domain.system.log.LogDownloadApplicationService;
|
||||||
|
import com.agileboot.domain.system.log.dto.LogFileDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志下载控制器 (客户端API使用)
|
||||||
|
*
|
||||||
|
* @author agileboot
|
||||||
|
*/
|
||||||
|
@Tag(name = "日志下载API", description = "服务器日志下载相关接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/log")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class LogDownloadController {
|
||||||
|
|
||||||
|
private final LogDownloadApplicationService logDownloadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取日志文件列表")
|
||||||
|
@GetMapping("/files")
|
||||||
|
public ResponseDTO<List<LogFileDTO>> getLogFileList() {
|
||||||
|
List<LogFileDTO> fileList = logDownloadService.getLogFileList();
|
||||||
|
return ResponseDTO.ok(fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载日志文件
|
||||||
|
*
|
||||||
|
* @param logType 日志类型: info, error, debug
|
||||||
|
*/
|
||||||
|
@Operation(summary = "下载日志文件")
|
||||||
|
@GetMapping("/download/{logType}")
|
||||||
|
public ResponseEntity<byte[]> downloadLog(@PathVariable String logType) {
|
||||||
|
try {
|
||||||
|
// 1. 获取日志文件内容
|
||||||
|
byte[] fileContent = logDownloadService.getLogFileContent(logType);
|
||||||
|
|
||||||
|
// 2. 获取文件名
|
||||||
|
String fileName = logDownloadService.getLogFileName(logType);
|
||||||
|
|
||||||
|
// 3. 构建下载响应头
|
||||||
|
HttpHeaders headers = FileUploadUtils.getDownloadHeader(fileName);
|
||||||
|
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(fileContent, headers, org.springframework.http.HttpStatus.OK);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
log.error("下载日志文件失败: logType={}, error={}", logType, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载日志文件失败: logType={}", logType, e);
|
||||||
|
throw new ApiException(Business.LOG_FILE_READ_FAILED, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.agileboot.api.controller.agent;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.domain.agent.AgentApplicationService;
|
||||||
|
import com.agileboot.domain.cabinet.smartCabinet.SmartCabinetApplicationService;
|
||||||
|
import com.agileboot.domain.cabinet.smartCabinet.dto.CabinetDetailDTO;
|
||||||
|
import com.agileboot.domain.agent.dto.IdNameDTO;
|
||||||
|
import com.agileboot.domain.shop.shop.ShopApplicationService;
|
||||||
|
import com.agileboot.domain.shop.shop.dto.ShopDetailDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent实体搜索控制器
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@CrossOrigin(origins = "*", allowedHeaders = "*")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/api/agent")
|
||||||
|
@Tag(name = "Agent相关API")
|
||||||
|
public class AgentController {
|
||||||
|
|
||||||
|
private final AgentApplicationService agentApplicationService;
|
||||||
|
private final SmartCabinetApplicationService smartCabinetApplicationService;
|
||||||
|
private final ShopApplicationService shopApplicationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称模糊查询实体列表
|
||||||
|
* 支持查询店铺、柜机、商品三种类型的实体
|
||||||
|
*
|
||||||
|
* @param name 名称关键词,支持模糊匹配
|
||||||
|
* @param entityType 实体类型,可选值:shop(店铺)、cabinet(柜机)、goods(商品)
|
||||||
|
* @return 匹配的实体ID和名称列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据名称模糊查询实体列表")
|
||||||
|
@GetMapping("/searchEntity")
|
||||||
|
public ResponseDTO<List<IdNameDTO>> searchEntity(
|
||||||
|
@RequestParam String name,
|
||||||
|
@RequestParam String entityType
|
||||||
|
) {
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
return ResponseDTO.ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IdNameDTO> result = agentApplicationService.searchEntities(name, entityType);
|
||||||
|
return ResponseDTO.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据柜体ID获取智能柜详细信息
|
||||||
|
* @param cabinetId 柜体ID
|
||||||
|
* @return 包含柜体信息、单元格信息和商品信息的详情
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据柜体ID获取智能柜详细信息")
|
||||||
|
@GetMapping("/cabinetDetail")
|
||||||
|
public ResponseDTO<CabinetDetailDTO> getCabinetDetail(@RequestParam Long cabinetId) {
|
||||||
|
if (cabinetId == null) {
|
||||||
|
return ResponseDTO.fail(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
CabinetDetailDTO result = smartCabinetApplicationService.getCabinetDetailById(cabinetId);
|
||||||
|
return ResponseDTO.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据店铺ID获取店铺详细信息,包含下属柜机列表(不包含格口信息)
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @return 店铺详细信息及柜机列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "根据店铺ID获取店铺详细信息")
|
||||||
|
@GetMapping("/shopDetail")
|
||||||
|
public ResponseDTO<ShopDetailDTO> getShopDetail(@RequestParam Long shopId) {
|
||||||
|
if (shopId == null) {
|
||||||
|
return ResponseDTO.fail(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShopDetailDTO result = shopApplicationService.getShopDetailById(shopId);
|
||||||
|
return ResponseDTO.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,8 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -51,13 +53,13 @@ public class AdminLoginController {
|
||||||
SysUserEntity userEntity = userService.getById(userId);
|
SysUserEntity userEntity = userService.getById(userId);
|
||||||
if (userEntity == null) {
|
if (userEntity == null) {
|
||||||
log.warn("用户不存在, userId: {}", userId);
|
log.warn("用户不存在, userId: {}", userId);
|
||||||
return ResponseDTO.ok(List.of()); // 返回空列表而不是错误
|
return ResponseDTO.ok(new ArrayList<>()); // 返回空列表而不是错误
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 检查用户状态 (可选,根据需求决定)
|
// 2. 检查用户状态 (可选,根据需求决定)
|
||||||
if (userEntity.getStatus() != 1) { // 1表示正常状态
|
if (userEntity.getStatus() != 1) { // 1表示正常状态
|
||||||
log.warn("用户状态异常, userId: {}, status: {}", userId, userEntity.getStatus());
|
log.warn("用户状态异常, userId: {}, status: {}", userId, userEntity.getStatus());
|
||||||
return ResponseDTO.ok(List.of());
|
return ResponseDTO.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 构造 SystemLoginUser (isAdmin设置为false)
|
// 4. 构造 SystemLoginUser (isAdmin设置为false)
|
||||||
|
|
@ -87,12 +89,12 @@ public class AdminLoginController {
|
||||||
SysUserEntity userEntity = userService.getById(userId);
|
SysUserEntity userEntity = userService.getById(userId);
|
||||||
if (userEntity == null) {
|
if (userEntity == null) {
|
||||||
log.warn("用户不存在, userId: {}", userId);
|
log.warn("用户不存在, userId: {}", userId);
|
||||||
return ResponseDTO.ok(List.of());
|
return ResponseDTO.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userEntity.getStatus() != 1) {
|
if (userEntity.getStatus() != 1) {
|
||||||
log.warn("用户状态异常, userId: {}, status: {}", userId, userEntity.getStatus());
|
log.warn("用户状态异常, userId: {}, status: {}", userId, userEntity.getStatus());
|
||||||
return ResponseDTO.ok(List.of());
|
return ResponseDTO.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemLoginUser loginUser = new SystemLoginUser(
|
SystemLoginUser loginUser = new SystemLoginUser(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.agileboot.api.controller.manage;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.core.page.PageDTO;
|
||||||
|
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||||
|
import com.agileboot.domain.cabinet.operation.CabinetCellOperationApplicationService;
|
||||||
|
import com.agileboot.domain.cabinet.operation.command.AddCabinetCellOperationCommand;
|
||||||
|
import com.agileboot.domain.cabinet.operation.command.UpdateCabinetCellOperationCommand;
|
||||||
|
import com.agileboot.domain.cabinet.operation.db.CabinetCellOperationEntity;
|
||||||
|
import com.agileboot.domain.cabinet.operation.dto.CabinetCellOperationDTO;
|
||||||
|
import com.agileboot.domain.cabinet.operation.query.SearchCabinetCellOperationDetailQuery;
|
||||||
|
import com.agileboot.domain.cabinet.operation.query.SearchCabinetCellOperationQuery;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@CrossOrigin(origins = "*", allowedHeaders = "*")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/api/manage/cellOperations")
|
||||||
|
public class CabinetCellOperationApiController {
|
||||||
|
|
||||||
|
private final CabinetCellOperationApplicationService cabinetCellOperationService;
|
||||||
|
|
||||||
|
@Operation(summary = "柜机格口操作列表")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseDTO<PageDTO<CabinetCellOperationDTO>> list(SearchCabinetCellOperationQuery<CabinetCellOperationEntity> query) {
|
||||||
|
PageDTO<CabinetCellOperationDTO> page = cabinetCellOperationService.getCabinetCellOperationList(query);
|
||||||
|
return ResponseDTO.ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "柜机格口操作详情列表")
|
||||||
|
@PostMapping("/page")
|
||||||
|
public ResponseDTO<PageDTO<CabinetCellOperationDTO>> listDetail(SearchCabinetCellOperationDetailQuery query) {
|
||||||
|
PageDTO<CabinetCellOperationDTO> page = cabinetCellOperationService.getOperationDetailList(query);
|
||||||
|
return ResponseDTO.ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "新增柜机格口操作")
|
||||||
|
@PostMapping
|
||||||
|
public ResponseDTO<Void> add(@Validated @RequestBody AddCabinetCellOperationCommand command) {
|
||||||
|
cabinetCellOperationService.addCabinetCellOperation(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改柜机格口操作")
|
||||||
|
@PutMapping("/{operationId}")
|
||||||
|
public ResponseDTO<Void> edit(@PathVariable Long operationId, @Validated @RequestBody UpdateCabinetCellOperationCommand command) {
|
||||||
|
command.setOperationId(operationId);
|
||||||
|
cabinetCellOperationService.updateCabinetCellOperation(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除柜机格口操作")
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public ResponseDTO<Void> remove(@PathVariable @NotNull List<Long> ids) {
|
||||||
|
cabinetCellOperationService.deleteCabinetCellOperation(new BulkOperationCommand<>(ids));
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,6 +86,14 @@ public enum ErrorCode implements ErrorCodeInterface {
|
||||||
|
|
||||||
PERMISSION_NOT_ALLOWED_TO_OPERATE(10202, "没有权限进行此操作,请联系管理员", "Business.NO_PERMISSION_TO_OPERATE"),
|
PERMISSION_NOT_ALLOWED_TO_OPERATE(10202, "没有权限进行此操作,请联系管理员", "Business.NO_PERMISSION_TO_OPERATE"),
|
||||||
|
|
||||||
|
// ----------------------------- LOG -------------------------------------------
|
||||||
|
|
||||||
|
LOG_TYPE_NOT_FOUND(10301, "日志类型 {} 不存在", "Business.LOG_TYPE_NOT_FOUND"),
|
||||||
|
|
||||||
|
LOG_PATH_NOT_CONFIGURED(10302, "日志路径未配置", "Business.LOG_PATH_NOT_CONFIGURED"),
|
||||||
|
|
||||||
|
LOG_FILE_READ_FAILED(10303, "读取日志文件失败: {}", "Business.LOG_FILE_READ_FAILED"),
|
||||||
|
|
||||||
// ----------------------------- LOGIN -----------------------------------------
|
// ----------------------------- LOGIN -----------------------------------------
|
||||||
|
|
||||||
LOGIN_WRONG_USER_PASSWORD(10201, "用户密码错误,请重输", "Business.LOGIN_WRONG_USER_PASSWORD"),
|
LOGIN_WRONG_USER_PASSWORD(10201, "用户密码错误,请重输", "Business.LOGIN_WRONG_USER_PASSWORD"),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.agileboot.domain.agent;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.agileboot.domain.agent.dto.IdNameDTO;
|
||||||
|
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity;
|
||||||
|
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
|
||||||
|
import com.agileboot.domain.shop.goods.db.ShopGoodsEntity;
|
||||||
|
import com.agileboot.domain.shop.goods.db.ShopGoodsService;
|
||||||
|
import com.agileboot.domain.shop.shop.db.ShopEntity;
|
||||||
|
import com.agileboot.domain.shop.shop.db.ShopService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent应用服务层
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AgentApplicationService {
|
||||||
|
|
||||||
|
private final ShopService shopService;
|
||||||
|
private final SmartCabinetService cabinetService;
|
||||||
|
private final ShopGoodsService goodsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称模糊查询实体列表
|
||||||
|
*
|
||||||
|
* @param name 名称关键词,支持模糊匹配
|
||||||
|
* @param entityType 实体类型 (shop/cabinet/goods)
|
||||||
|
* @return 实体ID名称列表,如果没有匹配项返回空列表
|
||||||
|
*/
|
||||||
|
public List<IdNameDTO> searchEntities(String name, String entityType) {
|
||||||
|
if (name == null || name.trim().isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String type = entityType.toLowerCase().trim();
|
||||||
|
if ("shop".equals(type)) {
|
||||||
|
return searchShops(name);
|
||||||
|
} else if ("cabinet".equals(type)) {
|
||||||
|
return searchCabinets(name);
|
||||||
|
} else if ("goods".equals(type)) {
|
||||||
|
return searchGoods(name);
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IdNameDTO> searchShops(String name) {
|
||||||
|
LambdaQueryWrapper<ShopEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.like(ShopEntity::getShopName, name)
|
||||||
|
.eq(ShopEntity::getDeleted, 0)
|
||||||
|
.orderByDesc(ShopEntity::getCreateTime);
|
||||||
|
|
||||||
|
return shopService.list(wrapper).stream()
|
||||||
|
.map(entity -> new IdNameDTO(entity.getShopId(), entity.getShopName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IdNameDTO> searchCabinets(String name) {
|
||||||
|
LambdaQueryWrapper<SmartCabinetEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.like(SmartCabinetEntity::getCabinetName, name)
|
||||||
|
.eq(SmartCabinetEntity::getDeleted, 0)
|
||||||
|
.orderByDesc(SmartCabinetEntity::getCreateTime);
|
||||||
|
|
||||||
|
return cabinetService.list(wrapper).stream()
|
||||||
|
.map(entity -> new IdNameDTO(entity.getCabinetId(), entity.getCabinetName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IdNameDTO> searchGoods(String name) {
|
||||||
|
LambdaQueryWrapper<ShopGoodsEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.like(ShopGoodsEntity::getGoodsName, name)
|
||||||
|
.eq(ShopGoodsEntity::getDeleted, 0)
|
||||||
|
.orderByDesc(ShopGoodsEntity::getCreateTime);
|
||||||
|
|
||||||
|
return goodsService.list(wrapper).stream()
|
||||||
|
.map(entity -> new IdNameDTO(entity.getGoodsId(), entity.getGoodsName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.agileboot.domain.agent.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用ID名称DTO
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class IdNameDTO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
@ -448,4 +448,79 @@ public class SmartCabinetApplicationService {
|
||||||
.map(cell -> new AvailableStorageCellDTO(cell, cabinetMap.get(cell.getCabinetId())))
|
.map(cell -> new AvailableStorageCellDTO(cell, cabinetMap.get(cell.getCabinetId())))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据柜体ID获取智能柜详细信息
|
||||||
|
* @param cabinetId 柜体ID
|
||||||
|
* @return 包含柜体信息、单元格信息和商品信息的单个柜体详情
|
||||||
|
*/
|
||||||
|
public CabinetDetailDTO getCabinetDetailById(Long cabinetId) {
|
||||||
|
// 1. 查询智能柜
|
||||||
|
SmartCabinetEntity cabinet = smartCabinetService.getById(cabinetId);
|
||||||
|
if (cabinet == null || cabinet.getDeleted()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查询该柜体下的所有单元格
|
||||||
|
QueryWrapper<CabinetCellEntity> cabinetCellEntityQueryWrapper = new QueryWrapper<>();
|
||||||
|
cabinetCellEntityQueryWrapper.eq("cabinet_id", cabinetId)
|
||||||
|
.eq("deleted", false);
|
||||||
|
List<CabinetCellEntity> cabinetCells = cabinetCellService.list(cabinetCellEntityQueryWrapper);
|
||||||
|
|
||||||
|
// 3. 查询单元格关联的商品信息
|
||||||
|
List<ShopGoodsEntity> goodsList = new ArrayList<>();
|
||||||
|
List<Long> goodsIds = cabinetCells.stream()
|
||||||
|
.map(CabinetCellEntity::getGoodsId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!goodsIds.isEmpty()) {
|
||||||
|
QueryWrapper<ShopGoodsEntity> shopGoodsEntityQueryWrapper = new QueryWrapper<>();
|
||||||
|
shopGoodsEntityQueryWrapper.in("goods_id", goodsIds)
|
||||||
|
.eq("deleted", false);
|
||||||
|
goodsList = shopGoodsService.list(shopGoodsEntityQueryWrapper);
|
||||||
|
}
|
||||||
|
final List<ShopGoodsEntity> finalGoodsList = goodsList;
|
||||||
|
|
||||||
|
// 4. 构建柜体详情DTO
|
||||||
|
CabinetDetailDTO cabinetDetailDTO = new CabinetDetailDTO();
|
||||||
|
cabinetDetailDTO.setCabinetId(cabinet.getCabinetId());
|
||||||
|
cabinetDetailDTO.setCabinetName(cabinet.getCabinetName());
|
||||||
|
cabinetDetailDTO.setLockControlNo(cabinet.getLockControlNo());
|
||||||
|
|
||||||
|
// 5. 处理柜体下的所有单元格
|
||||||
|
List<CabinetDetailDTO.CellInfoDTO> cellInfoList = cabinetCells.stream()
|
||||||
|
.map(cell -> {
|
||||||
|
CabinetDetailDTO.CellInfoDTO cellInfo = new CabinetDetailDTO.CellInfoDTO();
|
||||||
|
cellInfo.setCellId(cell.getCellId());
|
||||||
|
cellInfo.setCellNo(cell.getCellNo());
|
||||||
|
cellInfo.setPinNo(cell.getPinNo());
|
||||||
|
cellInfo.setStock(cell.getStock());
|
||||||
|
cellInfo.setPassword(cell.getPassword());
|
||||||
|
cellInfo.setUsageStatus(cell.getUsageStatus());
|
||||||
|
cellInfo.setCellType(cell.getCellType());
|
||||||
|
cellInfo.setRemark(cell.getRemark());
|
||||||
|
|
||||||
|
// 处理单元格关联的商品信息
|
||||||
|
if (cell.getGoodsId() != null) {
|
||||||
|
ShopGoodsEntity goods = finalGoodsList.stream()
|
||||||
|
.filter(g -> g.getGoodsId().equals(cell.getGoodsId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (goods != null) {
|
||||||
|
CabinetDetailDTO.ProductInfoDTO product = new CabinetDetailDTO.ProductInfoDTO();
|
||||||
|
product.setGoodsId(goods.getGoodsId());
|
||||||
|
product.setGoodsName(goods.getGoodsName());
|
||||||
|
product.setPrice(goods.getPrice());
|
||||||
|
product.setCoverImg(goods.getCoverImg());
|
||||||
|
cellInfo.setProduct(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cellInfo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
cabinetDetailDTO.setCells(cellInfoList);
|
||||||
|
return cabinetDetailDTO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.agileboot.domain.cabinet.smartCabinet.dto;
|
package com.agileboot.domain.cabinet.smartCabinet.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
@ -7,32 +8,69 @@ import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class CabinetDetailDTO {
|
public class CabinetDetailDTO {
|
||||||
|
@ApiModelProperty("柜机ID")
|
||||||
private Long cabinetId;
|
private Long cabinetId;
|
||||||
|
|
||||||
|
@ApiModelProperty("柜机名称")
|
||||||
private String cabinetName;
|
private String cabinetName;
|
||||||
|
|
||||||
|
@ApiModelProperty("锁控编号")
|
||||||
private Integer lockControlNo;
|
private Integer lockControlNo;
|
||||||
|
|
||||||
|
@ApiModelProperty("格子列表")
|
||||||
private List<CellInfoDTO> cells;
|
private List<CellInfoDTO> cells;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class CellInfoDTO {
|
public static class CellInfoDTO {
|
||||||
|
@ApiModelProperty("格子ID")
|
||||||
private Long cellId;
|
private Long cellId;
|
||||||
|
|
||||||
|
@ApiModelProperty("格子编号")
|
||||||
private Integer cellNo;
|
private Integer cellNo;
|
||||||
|
|
||||||
|
@ApiModelProperty("引脚编号")
|
||||||
private Integer pinNo;
|
private Integer pinNo;
|
||||||
|
|
||||||
|
@ApiModelProperty("库存数量")
|
||||||
private Integer stock;
|
private Integer stock;
|
||||||
|
|
||||||
|
@ApiModelProperty("格子价格")
|
||||||
private BigDecimal cellPrice;
|
private BigDecimal cellPrice;
|
||||||
|
|
||||||
|
@ApiModelProperty("订单ID")
|
||||||
private Long orderId;
|
private Long orderId;
|
||||||
|
|
||||||
|
@ApiModelProperty("订单商品ID")
|
||||||
private Long orderGoodsId;
|
private Long orderGoodsId;
|
||||||
|
|
||||||
|
@ApiModelProperty("商品信息")
|
||||||
private ProductInfoDTO product;
|
private ProductInfoDTO product;
|
||||||
|
|
||||||
|
@ApiModelProperty("密码")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@ApiModelProperty("使用状态(1空闲 2已占用)")
|
||||||
private Integer usageStatus;
|
private Integer usageStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty("格口类型(1小格 2中格 3大格 4超大格)")
|
||||||
private Integer cellType;
|
private Integer cellType;
|
||||||
|
|
||||||
|
@ApiModelProperty("备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class ProductInfoDTO {
|
public static class ProductInfoDTO {
|
||||||
|
@ApiModelProperty("商品ID")
|
||||||
private Long goodsId;
|
private Long goodsId;
|
||||||
|
|
||||||
|
@ApiModelProperty("商品名称")
|
||||||
private String goodsName;
|
private String goodsName;
|
||||||
|
|
||||||
|
@ApiModelProperty("商品价格")
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@ApiModelProperty("封面图URL")
|
||||||
private String coverImg;
|
private String coverImg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@ import com.agileboot.domain.shop.shop.command.UpdateShopCommand;
|
||||||
import com.agileboot.domain.shop.shop.db.ShopEntity;
|
import com.agileboot.domain.shop.shop.db.ShopEntity;
|
||||||
import com.agileboot.domain.shop.shop.db.ShopService;
|
import com.agileboot.domain.shop.shop.db.ShopService;
|
||||||
import com.agileboot.domain.shop.shop.dto.ShopDTO;
|
import com.agileboot.domain.shop.shop.dto.ShopDTO;
|
||||||
|
import com.agileboot.domain.shop.shop.dto.ShopDetailDTO;
|
||||||
import com.agileboot.domain.shop.shop.model.ShopModel;
|
import com.agileboot.domain.shop.shop.model.ShopModel;
|
||||||
import com.agileboot.domain.shop.shop.model.ShopModelFactory;
|
import com.agileboot.domain.shop.shop.model.ShopModelFactory;
|
||||||
import com.agileboot.domain.shop.shop.query.SearchShopQuery;
|
import com.agileboot.domain.shop.shop.query.SearchShopQuery;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -109,4 +111,49 @@ public class ShopApplicationService {
|
||||||
public List<Integer> getDistinctModeList() {
|
public List<Integer> getDistinctModeList() {
|
||||||
return shopService.getDistinctModeList();
|
return shopService.getDistinctModeList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据店铺ID获取店铺详细信息,包含下属柜机列表(不包含格口信息)
|
||||||
|
* @param shopId 店铺ID
|
||||||
|
* @return 店铺详细信息及柜机列表
|
||||||
|
*/
|
||||||
|
public ShopDetailDTO getShopDetailById(Long shopId) {
|
||||||
|
if (shopId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShopEntity shop = shopService.getById(shopId);
|
||||||
|
if (shop == null || shop.getDeleted()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryWrapper<SmartCabinetEntity> cabinetWrapper = new QueryWrapper<>();
|
||||||
|
cabinetWrapper.eq("shop_id", shopId)
|
||||||
|
.eq("deleted", false);
|
||||||
|
List<SmartCabinetEntity> cabinets = smartCabinetService.list(cabinetWrapper);
|
||||||
|
|
||||||
|
ShopDetailDTO shopDetailDTO = new ShopDetailDTO();
|
||||||
|
shopDetailDTO.setShopId(shop.getShopId());
|
||||||
|
shopDetailDTO.setShopName(shop.getShopName());
|
||||||
|
shopDetailDTO.setCorpid(shop.getCorpid());
|
||||||
|
shopDetailDTO.setBelongType(shop.getBelongType());
|
||||||
|
shopDetailDTO.setMode(shop.getMode());
|
||||||
|
shopDetailDTO.setBalanceEnable(shop.getBalanceEnable());
|
||||||
|
|
||||||
|
List<ShopDetailDTO.CabinetInfoDTO> cabinetInfoList = new ArrayList<>();
|
||||||
|
for (SmartCabinetEntity cabinet : cabinets) {
|
||||||
|
ShopDetailDTO.CabinetInfoDTO cabinetInfo = new ShopDetailDTO.CabinetInfoDTO();
|
||||||
|
cabinetInfo.setCabinetId(cabinet.getCabinetId());
|
||||||
|
cabinetInfo.setCabinetName(cabinet.getCabinetName());
|
||||||
|
cabinetInfo.setMainCabinet(cabinet.getMainCabinet());
|
||||||
|
cabinetInfo.setMqttServerId(cabinet.getMqttServerId());
|
||||||
|
cabinetInfo.setTemplateNo(cabinet.getTemplateNo());
|
||||||
|
cabinetInfo.setLockControlNo(cabinet.getLockControlNo());
|
||||||
|
cabinetInfo.setReturnDeadline(cabinet.getReturnDeadline());
|
||||||
|
cabinetInfoList.add(cabinetInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
shopDetailDTO.setCabinets(cabinetInfoList);
|
||||||
|
return shopDetailDTO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.agileboot.domain.shop.shop.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ShopDetailDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty("商店ID")
|
||||||
|
private Long shopId;
|
||||||
|
|
||||||
|
@ApiModelProperty("商店名称")
|
||||||
|
private String shopName;
|
||||||
|
|
||||||
|
@ApiModelProperty("企业微信id")
|
||||||
|
private String corpid;
|
||||||
|
|
||||||
|
@ApiModelProperty("归属类型(0-借还柜 1-固资通)")
|
||||||
|
private Integer belongType;
|
||||||
|
|
||||||
|
@ApiModelProperty("运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式 5-暂存模式)")
|
||||||
|
private Integer mode;
|
||||||
|
|
||||||
|
@ApiModelProperty("借呗支付(1-正常使用 0-禁止使用)")
|
||||||
|
private Integer balanceEnable;
|
||||||
|
|
||||||
|
@ApiModelProperty("柜机列表")
|
||||||
|
private List<CabinetInfoDTO> cabinets;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class CabinetInfoDTO {
|
||||||
|
@ApiModelProperty("柜机唯一ID")
|
||||||
|
private Long cabinetId;
|
||||||
|
|
||||||
|
@ApiModelProperty("柜机名称")
|
||||||
|
private String cabinetName;
|
||||||
|
|
||||||
|
@ApiModelProperty("归属主柜ID")
|
||||||
|
private Long mainCabinet;
|
||||||
|
|
||||||
|
@ApiModelProperty("MQTT服务ID")
|
||||||
|
private Long mqttServerId;
|
||||||
|
|
||||||
|
@ApiModelProperty("柜机模版编号")
|
||||||
|
private String templateNo;
|
||||||
|
|
||||||
|
@ApiModelProperty("锁控板序号")
|
||||||
|
private Integer lockControlNo;
|
||||||
|
|
||||||
|
@ApiModelProperty("归还期限(天),0表示不限制")
|
||||||
|
private Integer returnDeadline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.agileboot.domain.system.log;
|
||||||
|
|
||||||
|
import com.agileboot.domain.system.log.dto.LogFileDTO;
|
||||||
|
import com.agileboot.domain.system.log.service.LogFileService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志下载应用服务
|
||||||
|
*
|
||||||
|
* @author agileboot
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class LogDownloadApplicationService {
|
||||||
|
|
||||||
|
private final LogFileService logFileService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件列表
|
||||||
|
*/
|
||||||
|
public List<LogFileDTO> getLogFileList() {
|
||||||
|
return logFileService.getLogFileList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件内容
|
||||||
|
*/
|
||||||
|
public byte[] getLogFileContent(String logType) {
|
||||||
|
return logFileService.getLogFileContent(logType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件名
|
||||||
|
*/
|
||||||
|
public String getLogFileName(String logType) {
|
||||||
|
return logFileService.getLogFileName(logType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.agileboot.domain.system.log.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志文件信息DTO
|
||||||
|
*
|
||||||
|
* @author agileboot
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LogFileDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志类型: info, error, debug
|
||||||
|
*/
|
||||||
|
private String logType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
*/
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件是否存在
|
||||||
|
*/
|
||||||
|
private Boolean exists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后修改时间
|
||||||
|
*/
|
||||||
|
private Long lastModified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件大小(字节)
|
||||||
|
*/
|
||||||
|
private Long size;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.agileboot.domain.system.log.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.agileboot.domain.system.log.dto.LogFileDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志文件服务接口
|
||||||
|
*
|
||||||
|
* @author agileboot
|
||||||
|
*/
|
||||||
|
public interface LogFileService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可下载的日志文件列表
|
||||||
|
*
|
||||||
|
* @return 日志文件信息列表
|
||||||
|
*/
|
||||||
|
List<LogFileDTO> getLogFileList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据日志类型获取日志文件字节内容
|
||||||
|
*
|
||||||
|
* @param logType 日志类型 (info, error, debug)
|
||||||
|
* @return 文件字节数组
|
||||||
|
*/
|
||||||
|
byte[] getLogFileContent(String logType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件名
|
||||||
|
*
|
||||||
|
* @param logType 日志类型
|
||||||
|
* @return 文件名
|
||||||
|
*/
|
||||||
|
String getLogFileName(String logType);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
package com.agileboot.domain.system.log.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||||
|
import com.agileboot.domain.system.log.dto.LogFileDTO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志文件服务实现
|
||||||
|
*
|
||||||
|
* @author agileboot
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class LogFileServiceImpl implements LogFileService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志路径
|
||||||
|
*/
|
||||||
|
@Value("${logging.file.path:}")
|
||||||
|
private String logPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许的日志类型
|
||||||
|
*/
|
||||||
|
private static final List<String> ALLOWED_LOG_TYPES = Arrays.asList("info", "error", "debug");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志类型到文件名前缀的映射
|
||||||
|
*/
|
||||||
|
private static final String LOG_FILE_PREFIX_INFO = "sys-info";
|
||||||
|
private static final String LOG_FILE_PREFIX_ERROR = "sys-error";
|
||||||
|
private static final String LOG_FILE_PREFIX_DEBUG = "sys-debug";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<LogFileDTO> getLogFileList() {
|
||||||
|
if (StrUtil.isEmpty(logPath)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ALLOWED_LOG_TYPES.stream()
|
||||||
|
.map(this::buildLogFileDTO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private LogFileDTO buildLogFileDTO(String logType) {
|
||||||
|
String fileName = getLogFileName(logType);
|
||||||
|
String filePath = logPath + File.separator + fileName;
|
||||||
|
File file = new File(filePath);
|
||||||
|
|
||||||
|
LogFileDTO dto = new LogFileDTO();
|
||||||
|
dto.setLogType(logType);
|
||||||
|
dto.setFileName(fileName);
|
||||||
|
dto.setExists(file.exists());
|
||||||
|
dto.setLastModified(file.exists() ? file.lastModified() : null);
|
||||||
|
dto.setSize(file.exists() ? file.length() : null);
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getLogFileContent(String logType) {
|
||||||
|
// 1. 参数校验
|
||||||
|
if (StrUtil.isEmpty(logType)) {
|
||||||
|
throw new ApiException(Business.COMMON_BAD_REQUEST, "日志类型不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 日志类型校验 - 防止目录遍历攻击
|
||||||
|
String normalizedType = logType.toLowerCase().trim();
|
||||||
|
if (!ALLOWED_LOG_TYPES.contains(normalizedType)) {
|
||||||
|
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND,
|
||||||
|
"日志类型", normalizedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 校验日志路径配置
|
||||||
|
if (StrUtil.isEmpty(logPath)) {
|
||||||
|
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND,
|
||||||
|
"日志配置", "日志路径未配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 构建文件路径
|
||||||
|
String fileName = getLogFileName(normalizedType);
|
||||||
|
String filePath = logPath + File.separator + fileName;
|
||||||
|
|
||||||
|
// 5. 验证文件路径安全 - 确保路径在日志目录下
|
||||||
|
File logDir = new File(logPath);
|
||||||
|
File targetFile = new File(filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String canonicalDirPath = logDir.getCanonicalPath();
|
||||||
|
String canonicalFilePath = targetFile.getCanonicalPath();
|
||||||
|
|
||||||
|
if (!canonicalFilePath.startsWith(canonicalDirPath + File.separator)) {
|
||||||
|
throw new ApiException(Business.COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD, fileName);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("验证文件路径失败: {}", filePath, e);
|
||||||
|
throw new ApiException(Business.COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 检查文件是否存在
|
||||||
|
if (!targetFile.exists() || !targetFile.isFile()) {
|
||||||
|
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND, "日志文件", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 读取文件内容
|
||||||
|
try {
|
||||||
|
return FileUtil.readBytes(targetFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("读取日志文件失败: {}", filePath, e);
|
||||||
|
throw new ApiException(Business.UPLOAD_FILE_FAILED, "读取日志文件失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLogFileName(String logType) {
|
||||||
|
String lowerType = logType.toLowerCase();
|
||||||
|
if ("info".equals(lowerType)) {
|
||||||
|
return LOG_FILE_PREFIX_INFO + ".log";
|
||||||
|
} else if ("error".equals(lowerType)) {
|
||||||
|
return LOG_FILE_PREFIX_ERROR + ".log";
|
||||||
|
} else if ("debug".equals(lowerType)) {
|
||||||
|
return LOG_FILE_PREFIX_DEBUG + ".log";
|
||||||
|
} else {
|
||||||
|
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND, "日志类型", logType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,295 +0,0 @@
|
||||||
# ManageGoodsController 接口文档
|
|
||||||
|
|
||||||
## 控制器概述
|
|
||||||
|
|
||||||
**基础路径:** `/api/manage/goods`
|
|
||||||
|
|
||||||
**控制器类:** `com.agileboot.api.controller.manage.ManageGoodsController`
|
|
||||||
|
|
||||||
**功能描述:** 商品管理接口,提供商品的增删改查功能。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 接口列表
|
|
||||||
|
|
||||||
### 1. 获取商品列表
|
|
||||||
|
|
||||||
**接口路径:** `GET /api/manage/goods/list`
|
|
||||||
|
|
||||||
**功能描述:** 分页查询商品列表,支持多条件筛选和排序。
|
|
||||||
|
|
||||||
**请求参数:**
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 描述 | 示例 |
|
|
||||||
|--------|------|------|------|------|
|
|
||||||
| `pageNum` | Integer | 否 | 页码,默认1,最大值200 | `1` |
|
|
||||||
| `pageSize` | Integer | 否 | 每页大小,默认10,最大值500 | `10` |
|
|
||||||
| `orderColumn` | String | 否 | 排序字段 | `"createTime"` |
|
|
||||||
| `orderDirection` | String | 否 | 排序方向:`"ascending"`(升序) 或 `"descending"`(降序) | `"descending"` |
|
|
||||||
| `beginTime` | Date | 否 | 开始时间(格式:yyyy-MM-dd) | `"2025-01-01"` |
|
|
||||||
| `endTime` | Date | 否 | 结束时间(格式:yyyy-MM-dd) | `"2025-12-31"` |
|
|
||||||
| `goodsName` | String | 否 | 商品名称(模糊查询) | `"测试商品"` |
|
|
||||||
| `categoryId` | Long | 否 | 商品分类ID | `1` |
|
|
||||||
| `externalGoodsId` | Long | 否 | 外部归属类型的商品ID | `1001` |
|
|
||||||
| `corpid` | String | 否 | 企业微信id | `"wxcorpid123"` |
|
|
||||||
| `status` | Integer | 否 | 商品状态(1上架 2下架) | `1` |
|
|
||||||
| `autoApproval` | Integer | 否 | 免审批(0否 1是) | `1` |
|
|
||||||
| `minPrice` | BigDecimal | 否 | 最低价格 | `10.00` |
|
|
||||||
| `maxPrice` | BigDecimal | 否 | 最高价格 | `100.00` |
|
|
||||||
| `belongType` | Integer | 否 | 归属类型(0-借还柜 1-固资通) | `0` |
|
|
||||||
|
|
||||||
**返回类型:** `ResponseDTO<PageDTO<ShopGoodsDTO>>`
|
|
||||||
|
|
||||||
**响应字段:**
|
|
||||||
|
|
||||||
`ResponseDTO` 通用结构:
|
|
||||||
- `code`: Integer - 响应码
|
|
||||||
- `msg`: String - 响应消息
|
|
||||||
- `data`: `PageDTO<ShopGoodsDTO>` - 分页数据
|
|
||||||
|
|
||||||
`PageDTO` 分页结构:
|
|
||||||
- `total`: Long - 总记录数
|
|
||||||
- `rows`: `List<ShopGoodsDTO>` - 当前页数据列表
|
|
||||||
|
|
||||||
`ShopGoodsDTO` 商品详情字段:
|
|
||||||
|
|
||||||
| 字段名 | 类型 | 描述 |
|
|
||||||
|--------|------|------|
|
|
||||||
| `goodsId` | Long | 商品唯一ID |
|
|
||||||
| `goodsName` | String | 商品名称 |
|
|
||||||
| `categoryId` | Long | 分类ID |
|
|
||||||
| `externalGoodsId` | Long | 外部商品ID |
|
|
||||||
| `corpid` | String | 企业微信id |
|
|
||||||
| `categoryName` | String | 分类名称 |
|
|
||||||
| `belongType` | Long | 归属类型(0-借还柜 1-固资通) |
|
|
||||||
| `price` | BigDecimal | 销售价格 |
|
|
||||||
| `stock` | Integer | 库存数量 |
|
|
||||||
| `status` | Integer | 商品状态(1上架 2下架) |
|
|
||||||
| `autoApproval` | Integer | 免审批(0否 1是) |
|
|
||||||
| `coverImg` | String | 封面图URL |
|
|
||||||
| `creatorId` | Long | 创建者ID |
|
|
||||||
| `creatorName` | String | 创建者名称 |
|
|
||||||
| `createTime` | Date | 创建时间 |
|
|
||||||
| `remark` | String | 备注 |
|
|
||||||
| `cabinetName` | String | 柜机名称 |
|
|
||||||
| `cellNo` | Integer | 格口号 |
|
|
||||||
| `cellNoStr` | String | 格口号(字符串格式) |
|
|
||||||
| `totalStock` | Integer | 已分配库存 |
|
|
||||||
| `shopNameStr` | String | 地址名称 |
|
|
||||||
| `usageInstruction` | String | 商品使用说明 |
|
|
||||||
| `monthlyPurchaseLimit` | Integer | 每人每月限购数量 |
|
|
||||||
|
|
||||||
**示例请求:**
|
|
||||||
```
|
|
||||||
GET /api/manage/goods/list?pageNum=1&pageSize=10&status=1&goodsName=测试
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 新增商品
|
|
||||||
|
|
||||||
**接口路径:** `POST /api/manage/goods`
|
|
||||||
|
|
||||||
**功能描述:** 创建新商品。
|
|
||||||
|
|
||||||
**请求体类型:** `AddGoodsCommand` (继承自 `ShopGoodsEntity`)
|
|
||||||
|
|
||||||
**请求字段:**
|
|
||||||
|
|
||||||
| 字段名 | 类型 | 必填 | 描述 | 示例 |
|
|
||||||
|--------|------|------|------|------|
|
|
||||||
| `goodsName` | String | 是 | 商品名称 | `"测试商品"` |
|
|
||||||
| `categoryId` | Long | 否 | 商品分类ID | `1` |
|
|
||||||
| `externalGoodsId` | Long | 否 | 外部归属类型的商品ID | `1001` |
|
|
||||||
| `corpid` | String | 否 | 企业微信id | `"wxcorpid123"` |
|
|
||||||
| `monthlyPurchaseLimit` | Integer | 否 | 每人每月限购数量 | `5` |
|
|
||||||
| `price` | BigDecimal | 是 | 销售价格 | `99.99` |
|
|
||||||
| `stock` | Integer | 否 | 库存数量 | `100` |
|
|
||||||
| `status` | Integer | 否 | 商品状态(1上架 2下架),默认值需确认 | `1` |
|
|
||||||
| `autoApproval` | Integer | 否 | 免审批(0否 1是),默认值需确认 | `0` |
|
|
||||||
| `coverImg` | String | 否 | 封面图URL | `"https://example.com/image.jpg"` |
|
|
||||||
| `goodsDetail` | String | 否 | 商品详情(支持2000汉字+10个图片链接) | `"<p>商品描述</p>"` |
|
|
||||||
| `remark` | String | 否 | 备注 | `"测试商品备注"` |
|
|
||||||
| `usageInstruction` | String | 否 | 商品使用说明 | `"使用说明内容"` |
|
|
||||||
| `belongType` | Integer | 否 | 归属类型(0-借还柜 1-固资通) | `0` |
|
|
||||||
|
|
||||||
**注意:** `goodsId` 字段为自增主键,请求时无需提供。
|
|
||||||
|
|
||||||
**返回类型:** `ResponseDTO<Void>`
|
|
||||||
|
|
||||||
**响应字段:**
|
|
||||||
- `code`: Integer - 响应码
|
|
||||||
- `msg`: String - 响应消息
|
|
||||||
- `data`: null
|
|
||||||
|
|
||||||
**示例请求:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"goodsName": "测试商品",
|
|
||||||
"price": 99.99,
|
|
||||||
"stock": 100,
|
|
||||||
"status": 1,
|
|
||||||
"coverImg": "https://example.com/image.jpg"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 删除商品
|
|
||||||
|
|
||||||
**接口路径:** `DELETE /api/manage/goods/{goodsIds}`
|
|
||||||
|
|
||||||
**功能描述:** 批量删除商品。
|
|
||||||
|
|
||||||
**路径参数:**
|
|
||||||
- `goodsIds`: 商品ID列表,多个ID用逗号分隔
|
|
||||||
|
|
||||||
**请求示例:**
|
|
||||||
```
|
|
||||||
DELETE /api/manage/goods/1,2,3
|
|
||||||
```
|
|
||||||
|
|
||||||
**内部处理:** 控制器将路径参数转换为 `BulkOperationCommand<Long>` 对象:
|
|
||||||
```java
|
|
||||||
{
|
|
||||||
"ids": [1, 2, 3] // Set<Long> 类型,自动去重
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回类型:** `ResponseDTO<Void>`
|
|
||||||
|
|
||||||
**响应字段:**
|
|
||||||
- `code`: Integer - 响应码
|
|
||||||
- `msg`: String - 响应消息
|
|
||||||
- `data`: null
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 修改商品
|
|
||||||
|
|
||||||
**接口路径:** `PUT /api/manage/goods/{goodsId}`
|
|
||||||
|
|
||||||
**功能描述:** 更新商品信息。
|
|
||||||
|
|
||||||
**路径参数:**
|
|
||||||
- `goodsId`: 商品ID
|
|
||||||
|
|
||||||
**请求体类型:** `UpdateGoodsCommand` (继承自 `AddGoodsCommand`)
|
|
||||||
|
|
||||||
**请求字段:** 与 `AddGoodsCommand` 相同,所有字段均为可选更新。
|
|
||||||
|
|
||||||
**特殊处理:** 接口会自动将路径参数中的 `goodsId` 设置到 command 对象中。
|
|
||||||
|
|
||||||
**返回类型:** `ResponseDTO<Void>`
|
|
||||||
|
|
||||||
**响应字段:**
|
|
||||||
- `code`: Integer - 响应码
|
|
||||||
- `msg`: String - 响应消息
|
|
||||||
- `data`: null
|
|
||||||
|
|
||||||
**示例请求:**
|
|
||||||
```
|
|
||||||
PUT /api/manage/goods/1
|
|
||||||
```
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"goodsName": "更新后的商品名称",
|
|
||||||
"price": 88.88
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. 获取单个商品信息
|
|
||||||
|
|
||||||
**接口路径:** `GET /api/manage/goods/getGoodsInfo`
|
|
||||||
|
|
||||||
**功能描述:** 根据商品ID获取单个商品的详细信息。
|
|
||||||
|
|
||||||
**查询参数:**
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 描述 | 示例 |
|
|
||||||
|--------|------|------|------|------|
|
|
||||||
| `goodsId` | Long | 是 | 商品ID | `1` |
|
|
||||||
|
|
||||||
**返回类型:** `ResponseDTO<ShopGoodsDTO>`
|
|
||||||
|
|
||||||
**响应字段:**
|
|
||||||
- `code`: Integer - 响应码
|
|
||||||
- `msg`: String - 响应消息
|
|
||||||
- `data`: `ShopGoodsDTO` - 商品详情(字段同接口1)
|
|
||||||
|
|
||||||
**示例请求:**
|
|
||||||
```
|
|
||||||
GET /api/manage/goods/getGoodsInfo?goodsId=1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 通用数据结构
|
|
||||||
|
|
||||||
### ResponseDTO 通用响应结构
|
|
||||||
```java
|
|
||||||
public class ResponseDTO<T> {
|
|
||||||
private Integer code; // 响应码
|
|
||||||
private String msg; // 响应消息
|
|
||||||
private T data; // 响应数据
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### PageDTO 分页结构
|
|
||||||
```java
|
|
||||||
public class PageDTO<T> {
|
|
||||||
private Long total; // 总记录数
|
|
||||||
private List<T> rows; // 当前页数据列表
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### BulkOperationCommand 批量操作命令
|
|
||||||
```java
|
|
||||||
public class BulkOperationCommand<T> {
|
|
||||||
private Set<T> ids; // 操作ID集合(自动去重)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **字段默认值:** 部分字段(如 `status`, `autoApproval`)的默认值需查看业务逻辑确认
|
|
||||||
2. **ID 生成:** `goodsId` 为数据库自增字段,创建时无需提供
|
|
||||||
3. **批量删除:** 删除接口支持批量操作,ID列表会自动去重
|
|
||||||
4. **分页限制:** 最大页码200,每页最大大小500
|
|
||||||
5. **时间格式:** 时间参数需使用 `yyyy-MM-dd` 格式
|
|
||||||
6. **企业微信集成:** `corpid` 字段用于企业微信多租户支持
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关实体类位置
|
|
||||||
|
|
||||||
| 类名 | 路径 |
|
|
||||||
|------|------|
|
|
||||||
| `ManageGoodsController` | `agileboot-api/src/main/java/com/agileboot/api/controller/manage/` |
|
|
||||||
| `ShopGoodsEntity` | `agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/` |
|
|
||||||
| `AddGoodsCommand` | `agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/command/` |
|
|
||||||
| `UpdateGoodsCommand` | `agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/command/` |
|
|
||||||
| `ShopGoodsDTO` | `agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/dto/` |
|
|
||||||
| `SearchShopGoodsQuery` | `agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/query/` |
|
|
||||||
| `BulkOperationCommand` | `agileboot-domain/src/main/java/com/agileboot/domain/common/command/` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 架构设计说明
|
|
||||||
|
|
||||||
本控制器遵循项目的 **DDD/CQRS 架构**:
|
|
||||||
|
|
||||||
- **查询操作**(GET):使用 `SearchShopGoodsQuery` 对象,通过 `GoodsApplicationService.getGoodsList()` 处理
|
|
||||||
- **命令操作**(POST/PUT/DELETE):使用 `AddGoodsCommand`/`UpdateGoodsCommand`/`BulkOperationCommand`,通过 `GoodsApplicationService` 的对应方法处理
|
|
||||||
- **数据流转**:Controller → Command/Query → ApplicationService → Domain Model → DB Entity
|
|
||||||
|
|
||||||
**注意**:`AddGoodsCommand` 和 `UpdateGoodsCommand` 都继承自 `ShopGoodsEntity`,因此共享相同的字段定义。在实际使用中,`goodsId` 字段在新增时由数据库自增生成,在更新时通过路径参数传入。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*文档生成时间:2025-12-11*
|
|
||||||
*基于代码分析:`agileboot-api/src/main/java/com/agileboot/api/controller/manage/ManageGoodsController.java`*
|
|
||||||
|
|
@ -1,522 +0,0 @@
|
||||||
# UserBalanceController 接口文档
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
`UserBalanceController` 提供用户余额管理的RESTful接口,包括用户余额的查询、新增、更新、删除以及余额的增减操作。
|
|
||||||
|
|
||||||
**基础路径**: `/ab98/balance`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 接口列表
|
|
||||||
|
|
||||||
### 1. 用户余额列表查询
|
|
||||||
|
|
||||||
**接口地址**: `GET /ab98/balance`
|
|
||||||
|
|
||||||
**接口描述**: 分页查询用户余额列表
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 否 | 企业微信ID | ww1234567890 |
|
|
||||||
| openid | String | 否 | 微信openid | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o |
|
|
||||||
| ab98UserId | Long | 否 | 汇邦云用户ID | 10001 |
|
|
||||||
| qyUserId | Long | 否 | 企业用户ID | 20001 |
|
|
||||||
| pageNum | Integer | 否 | 页码(默认1) | 1 |
|
|
||||||
| pageSize | Integer | 否 | 每页条数(默认10) | 10 |
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<PageDTO<UserBalanceDTO>>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 100,
|
|
||||||
"pageNum": 1,
|
|
||||||
"pageSize": 10,
|
|
||||||
"pages": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 用户余额详情
|
|
||||||
|
|
||||||
**接口地址**: `GET /ab98/balance/{id}`
|
|
||||||
|
|
||||||
**接口描述**: 根据主键ID查询用户余额详情
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| id | Long | 是 | 主键ID | 1 |
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<UserBalanceDTO>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 根据企业微信ID和汇邦云用户ID查询用户余额
|
|
||||||
|
|
||||||
**接口地址**: `GET /ab98/balance/byCorpidAb98`
|
|
||||||
|
|
||||||
**接口描述**: 根据企业微信ID和汇邦云用户ID查询用户余额
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 是 | 企业微信ID | ww1234567890 |
|
|
||||||
| ab98UserId | Long | 是 | 汇邦云用户ID | 10001 |
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<UserBalanceDTO>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 根据openid查询用户余额
|
|
||||||
|
|
||||||
**接口地址**: `GET /ab98/balance/byOpenid`
|
|
||||||
|
|
||||||
**接口描述**: 根据微信openid查询用户余额
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| openid | String | 是 | 微信openid | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o |
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<UserBalanceDTO>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. 新增用户余额
|
|
||||||
|
|
||||||
**接口地址**: `POST /ab98/balance`
|
|
||||||
|
|
||||||
**接口描述**: 新增用户余额记录
|
|
||||||
|
|
||||||
**请求体** (JSON对象):
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 备注 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 是 | 企业微信ID | 不能为空 |
|
|
||||||
| openid | String | 否 | 微信openid | - |
|
|
||||||
| ab98UserId | Long | 是 | 汇邦云用户ID | 不能为空 |
|
|
||||||
| qyUserId | Long | 否 | 企业用户ID | - |
|
|
||||||
| balance | Long | 否 | 用户余额(分) | 金额单位为分 |
|
|
||||||
| useBalance | Long | 否 | 可用余额(分) | 金额单位为分 |
|
|
||||||
| balanceLimit | Long | 否 | 余额限制(分) | 金额单位为分 |
|
|
||||||
|
|
||||||
**请求示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<UserBalanceDTO>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**操作日志**: 记录新增操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. 修改用户余额
|
|
||||||
|
|
||||||
**接口地址**: `PUT /ab98/balance/{id}`
|
|
||||||
|
|
||||||
**接口描述**: 修改用户余额记录
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| id | Long | 是 | 主键ID | 1 |
|
|
||||||
|
|
||||||
**请求体** (JSON对象):
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 备注 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 是 | 企业微信ID | 不能为空 |
|
|
||||||
| openid | String | 否 | 微信openid | - |
|
|
||||||
| ab98UserId | Long | 是 | 汇邦云用户ID | 不能为空 |
|
|
||||||
| qyUserId | Long | 否 | 企业用户ID | - |
|
|
||||||
| balance | Long | 否 | 用户余额(分) | 金额单位为分 |
|
|
||||||
| useBalance | Long | 否 | 可用余额(分) | 金额单位为分 |
|
|
||||||
| balanceLimit | Long | 否 | 余额限制(分) | 金额单位为分 |
|
|
||||||
|
|
||||||
**请求示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<UserBalanceDTO>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**操作日志**: 记录修改操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. 删除用户余额
|
|
||||||
|
|
||||||
**接口地址**: `DELETE /ab98/balance/{id}`
|
|
||||||
|
|
||||||
**接口描述**: 删除用户余额记录
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| id | Long | 是 | 主键ID | 1 |
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<Void>`
|
|
||||||
|
|
||||||
**操作日志**: 记录删除操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. 批量删除用户余额
|
|
||||||
|
|
||||||
**接口地址**: `DELETE /ab98/balance/batch/{ids}`
|
|
||||||
|
|
||||||
**接口描述**: 批量删除用户余额记录
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 示例 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| ids | List<Long> | 是 | 主键ID列表 | 1,2,3 |
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<Void>`
|
|
||||||
|
|
||||||
**操作日志**: 记录删除操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. 增加用户余额
|
|
||||||
|
|
||||||
**接口地址**: `POST /ab98/balance/addBalance`
|
|
||||||
|
|
||||||
**接口描述**: 增加用户余额
|
|
||||||
|
|
||||||
**请求体** (JSON对象):
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 备注 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 是 | 企业微信ID | 不能为空 |
|
|
||||||
| ab98UserId | Long | 是 | 汇邦云用户ID | 不能为空 |
|
|
||||||
| amount | Long | 是 | 增加金额(分) | 金额单位为分,不能为空 |
|
|
||||||
|
|
||||||
**请求示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"amount": 10000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<String>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "增加用户余额成功",
|
|
||||||
"data": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**失败响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 500,
|
|
||||||
"msg": "增加用户余额失败",
|
|
||||||
"data": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**操作日志**: 记录修改操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 10. 减少用户余额
|
|
||||||
|
|
||||||
**接口地址**: `POST /ab98/balance/subtractBalance`
|
|
||||||
|
|
||||||
**接口描述**: 减少用户余额
|
|
||||||
|
|
||||||
**请求体** (JSON对象):
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 备注 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 是 | 企业微信ID | 不能为空 |
|
|
||||||
| ab98UserId | Long | 是 | 汇邦云用户ID | 不能为空 |
|
|
||||||
| amount | Long | 是 | 减少金额(分) | 金额单位为分,不能为空 |
|
|
||||||
|
|
||||||
**请求示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"amount": 5000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<String>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "减少用户余额成功",
|
|
||||||
"data": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**失败响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 500,
|
|
||||||
"msg": "减少用户余额失败",
|
|
||||||
"data": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**操作日志**: 记录修改操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 11. 插入或更新用户余额
|
|
||||||
|
|
||||||
**接口地址**: `POST /ab98/balance/insertOrUpdate`
|
|
||||||
|
|
||||||
**接口描述**: 插入或更新用户余额(如果存在则更新,不存在则新增)
|
|
||||||
|
|
||||||
**请求体** (JSON对象):
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 是否必填 | 描述 | 备注 |
|
|
||||||
|--------|------|----------|------|------|
|
|
||||||
| corpid | String | 是 | 企业微信ID | 不能为空 |
|
|
||||||
| openid | String | 否 | 微信openid | - |
|
|
||||||
| ab98UserId | Long | 是 | 汇邦云用户ID | 不能为空 |
|
|
||||||
| qyUserId | Long | 否 | 企业用户ID | - |
|
|
||||||
| balance | Long | 否 | 用户余额(分) | 金额单位为分 |
|
|
||||||
| useBalance | Long | 否 | 可用余额(分) | 金额单位为分 |
|
|
||||||
| balanceLimit | Long | 否 | 余额限制(分) | 金额单位为分 |
|
|
||||||
|
|
||||||
**请求示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**返回结果**: `ResponseDTO<UserBalanceDTO>`
|
|
||||||
|
|
||||||
**成功响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": {
|
|
||||||
"userBalanceId": 1,
|
|
||||||
"corpid": "ww1234567890",
|
|
||||||
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
|
|
||||||
"ab98UserId": 10001,
|
|
||||||
"qyUserId": 20001,
|
|
||||||
"balance": 100000,
|
|
||||||
"useBalance": 95000,
|
|
||||||
"balanceLimit": 500000,
|
|
||||||
"creatorId": 1,
|
|
||||||
"updaterId": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**操作日志**: 记录修改操作
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 数据结构
|
|
||||||
|
|
||||||
### UserBalanceDTO(用户余额DTO)
|
|
||||||
|
|
||||||
| 字段名 | 类型 | 描述 | 示例 |
|
|
||||||
|--------|------|------|------|
|
|
||||||
| userBalanceId | Long | 主键ID | 1 |
|
|
||||||
| corpid | String | 企业微信ID | ww1234567890 |
|
|
||||||
| openid | String | 微信openid | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o |
|
|
||||||
| ab98UserId | Long | 汇邦云用户ID | 10001 |
|
|
||||||
| qyUserId | Long | 企业用户ID | 20001 |
|
|
||||||
| balance | Long | 用户余额(分) | 100000 |
|
|
||||||
| useBalance | Long | 可用余额(分) | 95000 |
|
|
||||||
| balanceLimit | Long | 余额限制(分) | 500000 |
|
|
||||||
| creatorId | Long | 创建者ID | 1 |
|
|
||||||
| updaterId | Long | 更新者ID | 1 |
|
|
||||||
|
|
||||||
**说明**: 余额相关字段单位均为**分**,例如100元 = 10000分
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **金额单位**: 所有余额相关字段(balance、useBalance、balanceLimit、amount)单位均为**分**,而非元
|
|
||||||
2. **必填参数**: 标有"不能为空"的参数为必填项,请求时必须提供
|
|
||||||
3. **操作日志**: 新增、修改、删除操作会自动记录操作日志
|
|
||||||
4. **唯一性**: corpid + ab98UserId 组合应保证唯一性
|
|
||||||
5. **分页查询**: list接口支持分页,默认pageNum=1,pageSize=10
|
|
||||||
6. **批量操作**: 批量删除支持一次性删除多个记录,ID之间用逗号分隔
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 错误码说明
|
|
||||||
|
|
||||||
| 错误码 | 说明 |
|
|
||||||
|--------|------|
|
|
||||||
| 200 | 操作成功 |
|
|
||||||
| 400 | 请求参数错误 |
|
|
||||||
| 500 | 服务器内部错误 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**生成时间**: 2025-11-24
|
|
||||||
**文档版本**: v1.0
|
|
||||||
**控制器**: com.agileboot.admin.controller.ab98.UserBalanceController
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
-- 此SQL用于查询商品库存与格口库存不一致的数据
|
||||||
|
-- 筛选条件:商品库存 < 格口库存(说明格口库存超出商品库存,需要重新计算)
|
||||||
|
-- 主要用途:发现库存异常数据,定位需要重新计算格口数据的商品
|
||||||
|
SELECT
|
||||||
|
sg.goods_id,
|
||||||
|
sg.goods_name,
|
||||||
|
sg.stock AS '商品库存',
|
||||||
|
COALESCE(SUM(cc.stock), 0) AS '格口库存',
|
||||||
|
sc.cabinet_name,
|
||||||
|
GROUP_CONCAT(DISTINCT cc.cell_no ORDER BY cc.cell_no ASC) AS '格口号'
|
||||||
|
FROM
|
||||||
|
shop_goods sg
|
||||||
|
LEFT JOIN
|
||||||
|
cabinet_cell cc ON sg.goods_id = cc.goods_id
|
||||||
|
AND cc.deleted = 0
|
||||||
|
AND cc.goods_id IS NOT NULL
|
||||||
|
LEFT JOIN
|
||||||
|
smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id
|
||||||
|
AND sc.deleted = 0
|
||||||
|
WHERE
|
||||||
|
sg.deleted = 0
|
||||||
|
GROUP BY
|
||||||
|
sg.goods_id,
|
||||||
|
sg.goods_name,
|
||||||
|
sg.stock,
|
||||||
|
sc.cabinet_id,
|
||||||
|
sc.cabinet_name
|
||||||
|
HAVING
|
||||||
|
sg.stock < COALESCE(SUM(cc.stock), 0)
|
||||||
|
AND sc.cabinet_name IS NOT NULL
|
||||||
|
ORDER BY
|
||||||
|
sg.goods_id,
|
||||||
|
sc.cabinet_name;
|
||||||
Loading…
Reference in New Issue