Initial commit: backend code
This commit is contained in:
17
src/main/java/com/example/building/BuildingApplication.java
Normal file
17
src/main/java/com/example/building/BuildingApplication.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.example.building;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 建材销售管家启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.example.building.mapper")
|
||||
public class BuildingApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BuildingApplication.class, args);
|
||||
}
|
||||
}
|
||||
133
src/main/java/com/example/building/common/JwtUtil.java
Normal file
133
src/main/java/com/example/building/common/JwtUtil.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package com.example.building.common;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
* 用于生成和验证JWT Token
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${jwt.secret:building-materials-secret-key-2024}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration:7200000}")
|
||||
private Long expiration;
|
||||
|
||||
@Value("${jwt.refresh-expiration:604800000}")
|
||||
private Long refreshExpiration;
|
||||
|
||||
/**
|
||||
* 生成JWT Token
|
||||
*/
|
||||
public String generateToken(String userId, String username, String role) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
claims.put("role", role);
|
||||
return createToken(claims, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Token
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims, String subject) {
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(key, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成刷新Token
|
||||
*/
|
||||
public String generateRefreshToken(String userId) {
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
return Jwts.builder()
|
||||
.setSubject(userId)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + refreshExpiration))
|
||||
.signWith(key, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Token
|
||||
*/
|
||||
public Claims parseToken(String token) {
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token是否有效
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*/
|
||||
public String getUserId(String token) {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.get("userId", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名
|
||||
*/
|
||||
public String getUsername(String token) {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.get("username", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户角色
|
||||
*/
|
||||
public String getRole(String token) {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.get("role", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Token是否过期
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
try {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.getExpiration().before(new Date());
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/main/java/com/example/building/common/Result.java
Normal file
60
src/main/java/com/example/building/common/Result.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.example.building.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结果类
|
||||
*/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 状态码: 0成功, 非0失败
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 业务数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(0);
|
||||
result.setMessage("成功");
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data, String message) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(0);
|
||||
result.setMessage(message);
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String message) {
|
||||
return error(3000, message);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(code);
|
||||
result.setMessage(message);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/example/building/config/CorsConfig.java
Normal file
23
src/main/java/com/example/building/config/CorsConfig.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.example.building.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
* 允许前端应用跨域访问后端API
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.example.building.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus配置
|
||||
* 配置分页插件和自动填充
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* 配置分页插件
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动填充处理器
|
||||
*/
|
||||
@Bean
|
||||
public MetaObjectHandler metaObjectHandler() {
|
||||
return new MetaObjectHandler() {
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/example/building/config/RedisConfig.java
Normal file
44
src/main/java/com/example/building/config/RedisConfig.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.example.building.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis配置
|
||||
* 配置RedisTemplate的序列化方式
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 配置ObjectMapper
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
|
||||
ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
|
||||
// 使用Jackson2JsonRedisSerializer序列化value
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
|
||||
|
||||
// 设置key和value的序列化方式
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.example.building.controller;
|
||||
|
||||
import com.example.building.common.Result;
|
||||
import com.example.building.service.AuthService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
* 支持:手机号验证码登录、微信扫码登录、支付宝扫码登录
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
@PostMapping("/send-code")
|
||||
public Result<Void> sendCode(@RequestParam String phone) {
|
||||
authService.sendCode(phone);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*/
|
||||
@PostMapping("/phone-login")
|
||||
public Result<Map<String, Object>> phoneLogin(@RequestParam String phone, @RequestParam String code) {
|
||||
Map<String, Object> result = authService.phoneLogin(phone, code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信登录
|
||||
*/
|
||||
@PostMapping("/wechat")
|
||||
public Result<Map<String, Object>> wechatLogin(@RequestParam String code) {
|
||||
Map<String, Object> result = authService.wechatLogin(code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝登录
|
||||
*/
|
||||
@PostMapping("/alipay")
|
||||
public Result<Map<String, Object>> alipayLogin(@RequestParam String code) {
|
||||
Map<String, Object> result = authService.alipayLogin(code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
public Result<Map<String, Object>> refresh(@RequestParam String refreshToken) {
|
||||
Map<String, Object> result = authService.refreshToken(refreshToken);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*/
|
||||
@GetMapping("/me")
|
||||
public Result<Map<String, Object>> me(@RequestHeader("X-User-Id") String userId) {
|
||||
Map<String, Object> result = authService.getCurrentUser(userId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(@RequestHeader("Authorization") String token) {
|
||||
String jwtToken = token.replace("Bearer ", "");
|
||||
authService.logout(jwtToken);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.example.building.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.common.Result;
|
||||
import com.example.building.entity.Customer;
|
||||
import com.example.building.service.CustomerService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 客户控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/customers")
|
||||
public class CustomerController {
|
||||
|
||||
@Autowired
|
||||
private CustomerService customerService;
|
||||
|
||||
/**
|
||||
* 获取客户列表
|
||||
*/
|
||||
@GetMapping
|
||||
public Result<Page<Customer>> getCustomers(
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
return Result.success(customerService.getCustomers(keyword, page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public Result<Customer> getCustomer(@PathVariable String id) {
|
||||
return Result.success(customerService.getCustomer(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增客户
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Customer> createCustomer(@RequestBody Customer customer,
|
||||
@RequestHeader("X-User-Id") String userId) {
|
||||
customer.setCreatedBy(userId);
|
||||
return Result.success(customerService.createCustomer(customer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
public Result<Customer> updateCustomer(@PathVariable String id, @RequestBody Customer customer) {
|
||||
return Result.success(customerService.updateCustomer(id, customer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteCustomer(@PathVariable String id) {
|
||||
customerService.deleteCustomer(id);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.example.building.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.common.Result;
|
||||
import com.example.building.dto.CreateOrderRequest;
|
||||
import com.example.building.entity.Order;
|
||||
import com.example.building.service.OrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 订单控制器
|
||||
* 核心业务:
|
||||
* - 订单创建:计算原价(total_amount)、优惠金额(discount_amount)、实付金额(actual_amount)
|
||||
* - 订单原价 = 商品标价 × 数量之和
|
||||
* - 实付金额 = 原价 - 优惠金额
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/orders")
|
||||
public class OrderController {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* 核心逻辑:
|
||||
* 1. 计算订单原价(total_amount) = Σ(item.price × item.quantity)
|
||||
* 2. 计算优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
||||
* 3. 计算实付金额(actual_amount) = total_amount - discount_amount
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Order> createOrder(@RequestBody CreateOrderRequest request,
|
||||
@RequestHeader("X-User-Id") String operatorId,
|
||||
@RequestHeader("X-Username") String operatorName) {
|
||||
return Result.success(orderService.createOrder(request, operatorId, operatorName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
@GetMapping
|
||||
public Result<Page<Order>> getOrders(
|
||||
@RequestParam(required = false) String customerId,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate,
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
return Result.success(orderService.getOrders(customerId, status, startDate, endDate, page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public Result<Map<String, Object>> getOrderDetail(@PathVariable String id) {
|
||||
return Result.success(orderService.getOrderDetail(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
@PutMapping("/{id}/cancel")
|
||||
public Result<Void> cancelOrder(@PathVariable String id,
|
||||
@RequestHeader("X-User-Id") String operatorId) {
|
||||
orderService.cancelOrder(id, operatorId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
@PutMapping("/{id}/refund")
|
||||
public Result<Void> refundOrder(@PathVariable String id,
|
||||
@RequestHeader("X-User-Id") String operatorId) {
|
||||
orderService.refundOrder(id, operatorId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单统计
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
public Result<Map<String, Object>> getStatistics(
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate) {
|
||||
return Result.success(orderService.getStatistics(startDate, endDate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.example.building.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.common.Result;
|
||||
import com.example.building.entity.Category;
|
||||
import com.example.building.entity.Product;
|
||||
import com.example.building.service.ProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 商品控制器
|
||||
* 支持:商品CRUD、分类管理
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/products")
|
||||
public class ProductController {
|
||||
|
||||
@Autowired
|
||||
private ProductService productService;
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
@GetMapping("/categories")
|
||||
public Result<List<Category>> getCategories() {
|
||||
return Result.success(productService.getCategories());
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
@PostMapping("/categories")
|
||||
public Result<Category> createCategory(@RequestBody Category category) {
|
||||
return Result.success(productService.createCategory(category));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分类
|
||||
*/
|
||||
@PutMapping("/categories/{id}")
|
||||
public Result<Category> updateCategory(@PathVariable String id, @RequestBody Category category) {
|
||||
return Result.success(productService.updateCategory(id, category));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@DeleteMapping("/categories/{id}")
|
||||
public Result<Void> deleteCategory(@PathVariable String id) {
|
||||
productService.deleteCategory(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
@GetMapping
|
||||
public Result<Page<Product>> getProducts(
|
||||
@RequestParam(required = false) String categoryId,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
return Result.success(productService.getProducts(categoryId, keyword, page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public Result<Product> getProduct(@PathVariable String id) {
|
||||
return Result.success(productService.getProduct(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增商品
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<Product> createProduct(@RequestBody Product product) {
|
||||
return Result.success(productService.createProduct(product));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商品
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
public Result<Product> updateProduct(@PathVariable String id, @RequestBody Product product) {
|
||||
return Result.success(productService.updateProduct(id, product));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商品
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteProduct(@PathVariable String id) {
|
||||
productService.deleteProduct(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存预警商品
|
||||
*/
|
||||
@GetMapping("/alerts")
|
||||
public Result<List<Map<String, Object>>> getStockAlerts() {
|
||||
return Result.success(productService.getStockAlerts());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.example.building.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.common.Result;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.entity.StockFlow;
|
||||
import com.example.building.service.StockService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 库存控制器
|
||||
* 核心业务:入库、库存查询、库存流水
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/stock")
|
||||
public class StockController {
|
||||
|
||||
@Autowired
|
||||
private StockService stockService;
|
||||
|
||||
/**
|
||||
* 入库
|
||||
*/
|
||||
@PostMapping("/in")
|
||||
public Result<Stock> stockIn(@RequestParam String productId,
|
||||
@RequestParam Integer quantity,
|
||||
@RequestParam(required = false) String remark,
|
||||
@RequestHeader("X-User-Id") String operatorId) {
|
||||
return Result.success(stockService.stockIn(productId, quantity, remark, operatorId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
*/
|
||||
@GetMapping
|
||||
public Result<Page<Stock>> getStockList(
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
return Result.success(stockService.getStockList(keyword, page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 单商品库存
|
||||
*/
|
||||
@GetMapping("/{productId}")
|
||||
public Result<Stock> getStock(@PathVariable String productId) {
|
||||
return Result.success(stockService.getStock(productId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存调整
|
||||
*/
|
||||
@PostMapping("/adjust")
|
||||
public Result<Stock> adjustStock(@RequestParam String productId,
|
||||
@RequestParam Integer quantity,
|
||||
@RequestParam(required = false) String remark,
|
||||
@RequestHeader("X-User-Id") String operatorId) {
|
||||
return Result.success(stockService.adjustStock(productId, quantity, remark, operatorId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存流水
|
||||
*/
|
||||
@GetMapping("/flow")
|
||||
public Result<Page<StockFlow>> getStockFlow(
|
||||
@RequestParam(required = false) String productId,
|
||||
@RequestParam(required = false) Integer type,
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||
return Result.success(stockService.getStockFlow(productId, type, page, pageSize));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.example.building.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建订单请求DTO
|
||||
* 核心业务逻辑:
|
||||
* 1. 计算订单原价(total_amount) = Σ(item.price × item.quantity)
|
||||
* 2. 计算优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
||||
* 3. 计算实付金额(actual_amount) = total_amount - discount_amount
|
||||
*/
|
||||
@Data
|
||||
public class CreateOrderRequest {
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private String customerId;
|
||||
|
||||
/**
|
||||
* 订单明细
|
||||
*/
|
||||
@NotEmpty(message = "订单明细不能为空")
|
||||
private List<OrderItemDTO> items;
|
||||
|
||||
/**
|
||||
* 折扣率(百分比), 默认100
|
||||
*/
|
||||
private BigDecimal discountRate;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
private String paymentMethod;
|
||||
|
||||
/**
|
||||
* 订单明细项DTO
|
||||
*/
|
||||
@Data
|
||||
public static class OrderItemDTO {
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
@NotBlank(message = "商品ID不能为空")
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
@NotNull(message = "数量不能为空")
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 销售单价(用户可自定义)
|
||||
*/
|
||||
private BigDecimal price;
|
||||
}
|
||||
}
|
||||
38
src/main/java/com/example/building/dto/LoginRequest.java
Normal file
38
src/main/java/com/example/building/dto/LoginRequest.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.example.building.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 登录请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 微信授权码
|
||||
*/
|
||||
private String wechatCode;
|
||||
|
||||
/**
|
||||
* 支付宝授权码
|
||||
*/
|
||||
private String alipayCode;
|
||||
|
||||
/**
|
||||
* 登录类型: phone-手机号, wechat-微信, alipay-支付宝
|
||||
*/
|
||||
@NotBlank(message = "登录类型不能为空")
|
||||
private String loginType;
|
||||
}
|
||||
49
src/main/java/com/example/building/entity/Category.java
Normal file
49
src/main/java/com/example/building/entity/Category.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 商品分类实体类
|
||||
* 默认分类:五金建材、板材、木门、地板
|
||||
*/
|
||||
@Data
|
||||
@TableName("categories")
|
||||
public class Category {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String categoryId;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 父分类ID
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 状态: 1启用 0禁用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
59
src/main/java/com/example/building/entity/Customer.java
Normal file
59
src/main/java/com/example/building/entity/Customer.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 客户实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("customers")
|
||||
public class Customer {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String customerId;
|
||||
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 客户微信OpenID(用于推送)
|
||||
*/
|
||||
private String wechatOpenid;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 累计消费金额
|
||||
*/
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
101
src/main/java/com/example/building/entity/Order.java
Normal file
101
src/main/java/com/example/building/entity/Order.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 订单实体类
|
||||
* 核心业务逻辑:
|
||||
* - total_amount: 订单原价(商品标价×数量之和)
|
||||
* - discount_amount: 优惠金额
|
||||
* - actual_amount: 实付金额 = 原价 - 优惠金额
|
||||
*/
|
||||
@Data
|
||||
@TableName("orders")
|
||||
public class Order {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 订单编号(唯一)
|
||||
*/
|
||||
private String orderNo;
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private String customerId;
|
||||
|
||||
/**
|
||||
* 客户名称(冗余)
|
||||
*/
|
||||
private String customerName;
|
||||
|
||||
/**
|
||||
* 客户电话(冗余)
|
||||
*/
|
||||
private String customerPhone;
|
||||
|
||||
/**
|
||||
* 客户微信OpenID
|
||||
*/
|
||||
private String customerWechat;
|
||||
|
||||
/**
|
||||
* 订单原价(标价总和)
|
||||
* 计算公式: Σ(item.price × item.quantity)
|
||||
*/
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
* 计算公式: totalAmount × (100 - discountRate) / 100
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 实收金额
|
||||
* 计算公式: totalAmount - discountAmount
|
||||
*/
|
||||
private BigDecimal actualAmount;
|
||||
|
||||
/**
|
||||
* 折扣率(百分比)
|
||||
*/
|
||||
private BigDecimal discountRate;
|
||||
|
||||
/**
|
||||
* 状态: 1已完成 2已取消 3退款中 4已退款
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
private String paymentMethod;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 操作人ID
|
||||
*/
|
||||
private String operatorId;
|
||||
|
||||
/**
|
||||
* 操作人姓名(冗余)
|
||||
*/
|
||||
private String operatorName;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
61
src/main/java/com/example/building/entity/OrderItem.java
Normal file
61
src/main/java/com/example/building/entity/OrderItem.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 订单明细实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("order_items")
|
||||
public class OrderItem {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String itemId;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 商品名称(冗余)
|
||||
*/
|
||||
private String productName;
|
||||
|
||||
/**
|
||||
* 商品规格(冗余)
|
||||
*/
|
||||
private String productSpec;
|
||||
|
||||
/**
|
||||
* 单位(冗余)
|
||||
*/
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 销售单价(当时标价)
|
||||
*/
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 小计(单价×数量)
|
||||
*/
|
||||
private BigDecimal subtotal;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
79
src/main/java/com/example/building/entity/Product.java
Normal file
79
src/main/java/com/example/building/entity/Product.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 商品实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("products")
|
||||
public class Product {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private String categoryId;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 规格型号
|
||||
*/
|
||||
private String spec;
|
||||
|
||||
/**
|
||||
* 单位
|
||||
*/
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 销售单价(原价)
|
||||
*/
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 成本价
|
||||
*/
|
||||
private BigDecimal costPrice;
|
||||
|
||||
/**
|
||||
* 商品图片URL
|
||||
*/
|
||||
private String imageUrl;
|
||||
|
||||
/**
|
||||
* 商品条码
|
||||
*/
|
||||
private String barcode;
|
||||
|
||||
/**
|
||||
* 库存预警阈值
|
||||
*/
|
||||
private Integer stockAlert;
|
||||
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态: 1正常 0删除
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
40
src/main/java/com/example/building/entity/Stock.java
Normal file
40
src/main/java/com/example/building/entity/Stock.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 库存实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("stock")
|
||||
public class Stock {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String stockId;
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 仓库ID
|
||||
*/
|
||||
private String warehouseId;
|
||||
|
||||
/**
|
||||
* 当前库存数量
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 锁定数量(待发货)
|
||||
*/
|
||||
private Integer lockedQuantity;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
66
src/main/java/com/example/building/entity/StockFlow.java
Normal file
66
src/main/java/com/example/building/entity/StockFlow.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 库存流水实体类
|
||||
* 记录库存变动历史
|
||||
*/
|
||||
@Data
|
||||
@TableName("stock_flow")
|
||||
public class StockFlow {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String flowId;
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 类型: 1入库 2出库 3调整 4盘点
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 变动数量(正数增加/负数减少)
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 变动前数量
|
||||
*/
|
||||
private Integer beforeQuantity;
|
||||
|
||||
/**
|
||||
* 变动后数量
|
||||
*/
|
||||
private Integer afterQuantity;
|
||||
|
||||
/**
|
||||
* 关联单据ID
|
||||
*/
|
||||
private String relatedId;
|
||||
|
||||
/**
|
||||
* 关联单据类型
|
||||
*/
|
||||
private String relatedType;
|
||||
|
||||
/**
|
||||
* 操作人ID
|
||||
*/
|
||||
private String operatorId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
55
src/main/java/com/example/building/entity/User.java
Normal file
55
src/main/java/com/example/building/entity/User.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.example.building.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("users")
|
||||
public class User {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String userId;
|
||||
|
||||
private String username;
|
||||
|
||||
private String phone;
|
||||
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 微信OpenID
|
||||
*/
|
||||
private String wechatOpenid;
|
||||
|
||||
/**
|
||||
* 微信UnionID
|
||||
*/
|
||||
private String wechatUnionid;
|
||||
|
||||
/**
|
||||
* 支付宝OpenID
|
||||
*/
|
||||
private String alipayOpenid;
|
||||
|
||||
/**
|
||||
* 角色: admin-管理员, sales-销售员, warehouse-库管
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 状态: 1正常 0禁用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.Category;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 分类Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface CategoryMapper extends BaseMapper<Category> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.Customer;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 客户Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface CustomerMapper extends BaseMapper<Customer> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.OrderItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单明细Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderItemMapper extends BaseMapper<OrderItem> {
|
||||
}
|
||||
12
src/main/java/com/example/building/mapper/OrderMapper.java
Normal file
12
src/main/java/com/example/building/mapper/OrderMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.Order;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 订单Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface OrderMapper extends BaseMapper<Order> {
|
||||
}
|
||||
12
src/main/java/com/example/building/mapper/ProductMapper.java
Normal file
12
src/main/java/com/example/building/mapper/ProductMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.Product;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 商品Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProductMapper extends BaseMapper<Product> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.StockFlow;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 库存流水Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface StockFlowMapper extends BaseMapper<StockFlow> {
|
||||
}
|
||||
12
src/main/java/com/example/building/mapper/StockMapper.java
Normal file
12
src/main/java/com/example/building/mapper/StockMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.Stock;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 库存Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface StockMapper extends BaseMapper<Stock> {
|
||||
}
|
||||
12
src/main/java/com/example/building/mapper/UserMapper.java
Normal file
12
src/main/java/com/example/building/mapper/UserMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.example.building.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.example.building.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
}
|
||||
44
src/main/java/com/example/building/service/AuthService.java
Normal file
44
src/main/java/com/example/building/service/AuthService.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 认证服务接口
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
void sendCode(String phone);
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*/
|
||||
Map<String, Object> phoneLogin(String phone, String code);
|
||||
|
||||
/**
|
||||
* 微信扫码登录
|
||||
*/
|
||||
Map<String, Object> wechatLogin(String code);
|
||||
|
||||
/**
|
||||
* 支付宝扫码登录
|
||||
*/
|
||||
Map<String, Object> alipayLogin(String code);
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
Map<String, Object> refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
Map<String, Object> getCurrentUser(String userId);
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
void logout(String token);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Customer;
|
||||
|
||||
/**
|
||||
* 客户服务接口
|
||||
*/
|
||||
public interface CustomerService {
|
||||
|
||||
/**
|
||||
* 客户列表
|
||||
*/
|
||||
Page<Customer> getCustomers(String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 客户详情
|
||||
*/
|
||||
Customer getCustomer(String id);
|
||||
|
||||
/**
|
||||
* 新增客户
|
||||
*/
|
||||
Customer createCustomer(Customer customer);
|
||||
|
||||
/**
|
||||
* 修改客户
|
||||
*/
|
||||
Customer updateCustomer(String id, Customer customer);
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
void deleteCustomer(String id);
|
||||
}
|
||||
48
src/main/java/com/example/building/service/OrderService.java
Normal file
48
src/main/java/com/example/building/service/OrderService.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.dto.CreateOrderRequest;
|
||||
import com.example.building.entity.Order;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 订单服务接口
|
||||
* 核心业务:订单创建、价格计算(原价/优惠/实付)
|
||||
*/
|
||||
public interface OrderService {
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* 核心逻辑:
|
||||
* 1. 计算订单原价(total_amount) = Σ(item.price × item.quantity)
|
||||
* 2. 计算优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
||||
* 3. 计算实付金额(actual_amount) = total_amount - discount_amount
|
||||
*/
|
||||
Order createOrder(CreateOrderRequest request, String operatorId, String operatorName);
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
Page<Order> getOrders(String customerId, Integer status, String startDate, String endDate, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 获取订单详情(含明细)
|
||||
*/
|
||||
Map<String, Object> getOrderDetail(String orderId);
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
void cancelOrder(String orderId, String operatorId);
|
||||
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
void refundOrder(String orderId, String operatorId);
|
||||
|
||||
/**
|
||||
* 订单统计
|
||||
*/
|
||||
Map<String, Object> getStatistics(String startDate, String endDate);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Category;
|
||||
import com.example.building.entity.Product;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 商品服务接口
|
||||
*/
|
||||
public interface ProductService {
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
List<Category> getCategories();
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
Category createCategory(Category category);
|
||||
|
||||
/**
|
||||
* 修改分类
|
||||
*/
|
||||
Category updateCategory(String id, Category category);
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
void deleteCategory(String id);
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
Page<Product> getProducts(String categoryId, String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
Product getProduct(String id);
|
||||
|
||||
/**
|
||||
* 新增商品
|
||||
*/
|
||||
Product createProduct(Product product);
|
||||
|
||||
/**
|
||||
* 修改商品
|
||||
*/
|
||||
Product updateProduct(String id, Product product);
|
||||
|
||||
/**
|
||||
* 删除商品
|
||||
*/
|
||||
void deleteProduct(String id);
|
||||
|
||||
/**
|
||||
* 获取库存预警商品
|
||||
*/
|
||||
List<Map<String, Object>> getStockAlerts();
|
||||
}
|
||||
40
src/main/java/com/example/building/service/StockService.java
Normal file
40
src/main/java/com/example/building/service/StockService.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.example.building.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.entity.StockFlow;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 库存服务接口
|
||||
* 核心业务:入库、库存查询、库存流水
|
||||
*/
|
||||
public interface StockService {
|
||||
|
||||
/**
|
||||
* 入库
|
||||
*/
|
||||
Stock stockIn(String productId, Integer quantity, String remark, String operatorId);
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
*/
|
||||
Page<Stock> getStockList(String keyword, Integer page, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 单商品库存
|
||||
*/
|
||||
Stock getStock(String productId);
|
||||
|
||||
/**
|
||||
* 库存调整
|
||||
*/
|
||||
Stock adjustStock(String productId, Integer quantity, String remark, String operatorId);
|
||||
|
||||
/**
|
||||
* 库存流水
|
||||
*/
|
||||
Page<StockFlow> getStockFlow(String productId, Integer type, Integer page, Integer pageSize);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.example.building.common.JwtUtil;
|
||||
import com.example.building.entity.User;
|
||||
import com.example.building.mapper.UserMapper;
|
||||
import com.example.building.service.AuthService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 认证服务实现类
|
||||
* 支持:手机号验证码登录、微信扫码登录、支付宝扫码登录
|
||||
*/
|
||||
@Service
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${jwt.expiration:7200000}")
|
||||
private Long expiration;
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* 实际生产中应调用阿里云短信服务
|
||||
*/
|
||||
@Override
|
||||
public void sendCode(String phone) {
|
||||
// 生成6位随机验证码
|
||||
String code = String.format("%06d", (int) (Math.random() * 1000000));
|
||||
// 存入Redis,5分钟有效
|
||||
redisTemplate.opsForValue().set("sms:code:" + phone, code, 5, TimeUnit.MINUTES);
|
||||
// TODO: 调用短信服务发送验证码
|
||||
System.out.println("验证码已发送: " + phone + " - " + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> phoneLogin(String phone, String code) {
|
||||
// 验证验证码
|
||||
String savedCode = (String) redisTemplate.opsForValue().get("sms:code:" + phone);
|
||||
if (savedCode == null || !savedCode.equals(code)) {
|
||||
throw new RuntimeException("验证码错误或已过期");
|
||||
}
|
||||
|
||||
// 查询用户,不存在则创建
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getPhone, phone));
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setUserId(UUID.randomUUID().toString());
|
||||
user.setPhone(phone);
|
||||
user.setUsername("用户" + phone.substring(7));
|
||||
user.setRole("sales");
|
||||
user.setStatus(1);
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信扫码登录
|
||||
* 实际生产中需要调用微信API获取openid
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> wechatLogin(String code) {
|
||||
// TODO: 调用微信API获取openid
|
||||
// String openid = wechatService.getOpenId(code);
|
||||
String openid = "wechat_" + code;
|
||||
|
||||
// 查询用户,不存在则创建
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getWechatOpenid, openid));
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setUserId(UUID.randomUUID().toString());
|
||||
user.setWechatOpenid(openid);
|
||||
user.setUsername("微信用户");
|
||||
user.setRole("sales");
|
||||
user.setStatus(1);
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝扫码登录
|
||||
* 实际生产中需要调用支付宝API获取openid
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> alipayLogin(String code) {
|
||||
// TODO: 调用支付宝API获取openid
|
||||
// String openid = alipayService.getOpenId(code);
|
||||
String openid = "alipay_" + code;
|
||||
|
||||
// 查询用户,不存在则创建
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getAlipayOpenid, openid));
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setUserId(UUID.randomUUID().toString());
|
||||
user.setAlipayOpenid(openid);
|
||||
user.setUsername("支付宝用户");
|
||||
user.setRole("sales");
|
||||
user.setStatus(1);
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> refreshToken(String refreshToken) {
|
||||
if (!jwtUtil.validateToken(refreshToken)) {
|
||||
throw new RuntimeException("刷新Token无效");
|
||||
}
|
||||
String userId = jwtUtil.getUserId(refreshToken);
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
return generateTokens(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getCurrentUser(String userId) {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("userId", user.getUserId());
|
||||
result.put("username", user.getUsername());
|
||||
result.put("phone", user.getPhone());
|
||||
result.put("role", user.getRole());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
// 将token加入黑名单
|
||||
redisTemplate.opsForValue().set("blacklist:" + token, "1", 2, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Token和RefreshToken
|
||||
*/
|
||||
private Map<String, Object> generateTokens(User user) {
|
||||
String token = jwtUtil.generateToken(user.getUserId(), user.getUsername(), user.getRole());
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getUserId());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
result.put("refreshToken", refreshToken);
|
||||
result.put("userId", user.getUserId());
|
||||
result.put("username", user.getUsername());
|
||||
result.put("role", user.getRole());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Customer;
|
||||
import com.example.building.mapper.CustomerMapper;
|
||||
import com.example.building.service.CustomerService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 客户服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class CustomerServiceImpl implements CustomerService {
|
||||
|
||||
@Autowired
|
||||
private CustomerMapper customerMapper;
|
||||
|
||||
/**
|
||||
* 客户列表
|
||||
*/
|
||||
@Override
|
||||
public Page<Customer> getCustomers(String keyword, Integer page, Integer pageSize) {
|
||||
Page<Customer> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.like(Customer::getName, keyword)
|
||||
.or()
|
||||
.like(Customer::getPhone, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(Customer::getCreatedAt);
|
||||
return customerMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户详情
|
||||
*/
|
||||
@Override
|
||||
public Customer getCustomer(String id) {
|
||||
Customer customer = customerMapper.selectById(id);
|
||||
if (customer == null) {
|
||||
throw new RuntimeException("客户不存在");
|
||||
}
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增客户
|
||||
*/
|
||||
@Override
|
||||
public Customer createCustomer(Customer customer) {
|
||||
customer.setCustomerId(UUID.randomUUID().toString());
|
||||
customer.setTotalAmount(BigDecimal.ZERO);
|
||||
customerMapper.insert(customer);
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户
|
||||
*/
|
||||
@Override
|
||||
public Customer updateCustomer(String id, Customer customer) {
|
||||
Customer existing = customerMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("客户不存在");
|
||||
}
|
||||
customer.setCustomerId(id);
|
||||
customerMapper.updateById(customer);
|
||||
return customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
@Override
|
||||
public void deleteCustomer(String id) {
|
||||
customerMapper.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.dto.CreateOrderRequest;
|
||||
import com.example.building.entity.*;
|
||||
import com.example.building.mapper.*;
|
||||
import com.example.building.service.OrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 订单服务实现类
|
||||
* 核心业务逻辑:
|
||||
* 1. 订单原价(total_amount) = Σ(item.price × item.quantity)
|
||||
* 2. 优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
||||
* 3. 实付金额(actual_amount) = total_amount - discount_amount
|
||||
*/
|
||||
@Service
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private StockMapper stockMapper;
|
||||
|
||||
@Autowired
|
||||
private StockFlowMapper stockFlowMapper;
|
||||
|
||||
@Autowired
|
||||
private CustomerMapper customerMapper;
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* 核心:价格计算、库存扣减、客户累计消费更新
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Order createOrder(CreateOrderRequest request, String operatorId, String operatorName) {
|
||||
// 1. 准备订单数据
|
||||
Order order = new Order();
|
||||
order.setOrderId(UUID.randomUUID().toString());
|
||||
order.setOrderNo(generateOrderNo());
|
||||
order.setOperatorId(operatorId);
|
||||
order.setOperatorName(operatorName);
|
||||
order.setDiscountRate(request.getDiscountRate() != null ? request.getDiscountRate() : new BigDecimal("100"));
|
||||
order.setRemark(request.getRemark());
|
||||
order.setPaymentMethod(request.getPaymentMethod());
|
||||
order.setStatus(1); // 已完成
|
||||
|
||||
// 2. 查询客户信息(如果指定了客户)
|
||||
if (request.getCustomerId() != null) {
|
||||
Customer customer = customerMapper.selectById(request.getCustomerId());
|
||||
if (customer != null) {
|
||||
order.setCustomerId(customer.getCustomerId());
|
||||
order.setCustomerName(customer.getName());
|
||||
order.setCustomerPhone(customer.getPhone());
|
||||
order.setCustomerWechat(customer.getWechatOpenid());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 计算订单金额
|
||||
BigDecimal totalAmount = BigDecimal.ZERO; // 原价
|
||||
List<OrderItem> orderItems = new ArrayList<>();
|
||||
|
||||
for (CreateOrderRequest.OrderItemDTO itemDTO : request.getItems()) {
|
||||
// 查询商品信息
|
||||
Product product = productMapper.selectById(itemDTO.getProductId());
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在: " + itemDTO.getProductId());
|
||||
}
|
||||
|
||||
// 使用用户指定价格或商品标价
|
||||
BigDecimal price = itemDTO.getPrice() != null ? itemDTO.getPrice() : product.getPrice();
|
||||
|
||||
// 计算小计
|
||||
BigDecimal subtotal = price.multiply(new BigDecimal(itemDTO.getQuantity()))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
// 累加原价
|
||||
totalAmount = totalAmount.add(subtotal);
|
||||
|
||||
// 构建订单明细
|
||||
OrderItem item = new OrderItem();
|
||||
item.setItemId(UUID.randomUUID().toString());
|
||||
item.setOrderId(order.getOrderId());
|
||||
item.setProductId(product.getProductId());
|
||||
item.setProductName(product.getName());
|
||||
item.setProductSpec(product.getSpec());
|
||||
item.setUnit(product.getUnit());
|
||||
item.setPrice(price);
|
||||
item.setQuantity(itemDTO.getQuantity());
|
||||
item.setSubtotal(subtotal);
|
||||
orderItems.add(item);
|
||||
|
||||
// 4. 扣减库存
|
||||
decreaseStock(product.getProductId(), itemDTO.getQuantity(), order.getOrderId(), operatorId);
|
||||
}
|
||||
|
||||
// 设置订单原价
|
||||
order.setTotalAmount(totalAmount);
|
||||
|
||||
// 5. 计算优惠金额和实付金额
|
||||
BigDecimal discountRate = order.getDiscountRate();
|
||||
BigDecimal discountAmount = totalAmount.multiply(new BigDecimal("100").subtract(discountRate))
|
||||
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
||||
BigDecimal actualAmount = totalAmount.subtract(discountAmount);
|
||||
|
||||
order.setDiscountAmount(discountAmount);
|
||||
order.setActualAmount(actualAmount);
|
||||
|
||||
// 6. 保存订单
|
||||
orderMapper.insert(order);
|
||||
|
||||
// 7. 保存订单明细
|
||||
for (OrderItem item : orderItems) {
|
||||
orderItemMapper.insert(item);
|
||||
}
|
||||
|
||||
// 8. 更新客户累计消费金额
|
||||
if (request.getCustomerId() != null) {
|
||||
Customer customer = customerMapper.selectById(request.getCustomerId());
|
||||
if (customer != null) {
|
||||
BigDecimal newTotal = customer.getTotalAmount().add(actualAmount);
|
||||
customer.setTotalAmount(newTotal);
|
||||
customerMapper.updateById(customer);
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
@Override
|
||||
public Page<Order> getOrders(String customerId, Integer status, String startDate, String endDate, Integer page, Integer pageSize) {
|
||||
Page<Order> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
||||
if (customerId != null) {
|
||||
wrapper.eq(Order::getCustomerId, customerId);
|
||||
}
|
||||
if (status != null) {
|
||||
wrapper.eq(Order::getStatus, status);
|
||||
}
|
||||
if (startDate != null) {
|
||||
wrapper.ge(Order::getCreatedAt, startDate);
|
||||
}
|
||||
if (endDate != null) {
|
||||
wrapper.le(Order::getCreatedAt, endDate);
|
||||
}
|
||||
wrapper.orderByDesc(Order::getCreatedAt);
|
||||
return orderMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情(含明细)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getOrderDetail(String orderId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
|
||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||
.eq(OrderItem::getOrderId, orderId));
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("order", order);
|
||||
result.put("items", items);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void cancelOrder(String orderId, String operatorId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
if (order.getStatus() != 1) {
|
||||
throw new RuntimeException("订单状态不允许取消");
|
||||
}
|
||||
|
||||
// 恢复库存
|
||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||
.eq(OrderItem::getOrderId, orderId));
|
||||
for (OrderItem item : items) {
|
||||
increaseStock(item.getProductId(), item.getQuantity(), orderId, operatorId);
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.setStatus(2); // 已取消
|
||||
orderMapper.updateById(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void refundOrder(String orderId, String operatorId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
if (order.getStatus() != 1) {
|
||||
throw new RuntimeException("订单状态不允许退款");
|
||||
}
|
||||
|
||||
// 恢复库存
|
||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||
.eq(OrderItem::getOrderId, orderId));
|
||||
for (OrderItem item : items) {
|
||||
increaseStock(item.getProductId(), item.getQuantity(), orderId, operatorId);
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
order.setStatus(4); // 已退款
|
||||
orderMapper.updateById(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单统计
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getStatistics(String startDate, String endDate) {
|
||||
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Order::getStatus, 1);
|
||||
if (startDate != null) {
|
||||
wrapper.ge(Order::getCreatedAt, startDate);
|
||||
}
|
||||
if (endDate != null) {
|
||||
wrapper.le(Order::getCreatedAt, endDate);
|
||||
}
|
||||
|
||||
List<Order> orders = orderMapper.selectList(wrapper);
|
||||
|
||||
BigDecimal totalAmount = BigDecimal.ZERO; // 原价合计
|
||||
BigDecimal actualAmount = BigDecimal.ZERO; // 实付合计
|
||||
|
||||
for (Order order : orders) {
|
||||
totalAmount = totalAmount.add(order.getTotalAmount());
|
||||
actualAmount = actualAmount.add(order.getActualAmount());
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orderCount", orders.size());
|
||||
result.put("totalAmount", totalAmount);
|
||||
result.put("actualAmount", actualAmount);
|
||||
result.put("discountAmount", totalAmount.subtract(actualAmount));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单编号
|
||||
* 规则: ORD + 年月日 + 6位序号
|
||||
*/
|
||||
private String generateOrderNo() {
|
||||
String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
String key = "order:no:" + date;
|
||||
// 这里简化处理,实际应使用Redis自增
|
||||
int seq = (int) (Math.random() * 1000000);
|
||||
return "ORD" + date + String.format("%06d", seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扣减库存
|
||||
*/
|
||||
private void decreaseStock(String productId, Integer quantity, String relatedId, String operatorId) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
if (stock == null) {
|
||||
throw new RuntimeException("库存记录不存在");
|
||||
}
|
||||
if (stock.getQuantity() < quantity) {
|
||||
throw new RuntimeException("库存不足");
|
||||
}
|
||||
|
||||
int beforeQty = stock.getQuantity();
|
||||
stock.setQuantity(beforeQty - quantity);
|
||||
stockMapper.updateById(stock);
|
||||
|
||||
// 记录库存流水
|
||||
saveStockFlow(productId, 2, -quantity, beforeQty, beforeQty - quantity, relatedId, "sale", operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加库存
|
||||
*/
|
||||
private void increaseStock(String productId, Integer quantity, String relatedId, String operatorId) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setStockId(UUID.randomUUID().toString());
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(0);
|
||||
stock.setLockedQuantity(0);
|
||||
stockMapper.insert(stock);
|
||||
}
|
||||
|
||||
int beforeQty = stock.getQuantity();
|
||||
stock.setQuantity(beforeQty + quantity);
|
||||
stockMapper.updateById(stock);
|
||||
|
||||
// 记录库存流水
|
||||
saveStockFlow(productId, 1, quantity, beforeQty, beforeQty + quantity, relatedId, "cancel", operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存库存流水
|
||||
*/
|
||||
private void saveStockFlow(String productId, Integer type, Integer quantity,
|
||||
Integer beforeQty, Integer afterQty, String relatedId,
|
||||
String relatedType, String operatorId) {
|
||||
StockFlow flow = new StockFlow();
|
||||
flow.setFlowId(UUID.randomUUID().toString());
|
||||
flow.setProductId(productId);
|
||||
flow.setType(type);
|
||||
flow.setQuantity(quantity);
|
||||
flow.setBeforeQuantity(beforeQty);
|
||||
flow.setAfterQuantity(afterQty);
|
||||
flow.setRelatedId(relatedId);
|
||||
flow.setRelatedType(relatedType);
|
||||
flow.setOperatorId(operatorId);
|
||||
stockFlowMapper.insert(flow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Category;
|
||||
import com.example.building.entity.Product;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.mapper.CategoryMapper;
|
||||
import com.example.building.mapper.ProductMapper;
|
||||
import com.example.building.mapper.StockMapper;
|
||||
import com.example.building.service.ProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 商品服务实现类
|
||||
* 支持商品CRUD和分类管理
|
||||
*/
|
||||
@Service
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private CategoryMapper categoryMapper;
|
||||
|
||||
@Autowired
|
||||
private StockMapper stockMapper;
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
@Override
|
||||
public List<Category> getCategories() {
|
||||
return categoryMapper.selectList(new LambdaQueryWrapper<Category>()
|
||||
.eq(Category::getStatus, 1)
|
||||
.orderByAsc(Category::getSortOrder));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分类
|
||||
*/
|
||||
@Override
|
||||
public Category createCategory(Category category) {
|
||||
category.setCategoryId(UUID.randomUUID().toString());
|
||||
category.setStatus(1);
|
||||
categoryMapper.insert(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分类
|
||||
*/
|
||||
@Override
|
||||
public Category updateCategory(String id, Category category) {
|
||||
Category existing = categoryMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("分类不存在");
|
||||
}
|
||||
category.setCategoryId(id);
|
||||
categoryMapper.updateById(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@Override
|
||||
public void deleteCategory(String id) {
|
||||
categoryMapper.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
@Override
|
||||
public Page<Product> getProducts(String categoryId, String keyword, Integer page, Integer pageSize) {
|
||||
Page<Product> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Product::getStatus, 1);
|
||||
if (StringUtils.hasText(categoryId)) {
|
||||
wrapper.eq(Product::getCategoryId, categoryId);
|
||||
}
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.like(Product::getName, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(Product::getCreatedAt);
|
||||
return productMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
@Override
|
||||
public Product getProduct(String id) {
|
||||
Product product = productMapper.selectById(id);
|
||||
if (product == null || product.getStatus() == 0) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增商品
|
||||
*/
|
||||
@Override
|
||||
public Product createProduct(Product product) {
|
||||
product.setProductId(UUID.randomUUID().toString());
|
||||
product.setStatus(1);
|
||||
productMapper.insert(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商品
|
||||
*/
|
||||
@Override
|
||||
public Product updateProduct(String id, Product product) {
|
||||
Product existing = productMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
product.setProductId(id);
|
||||
productMapper.updateById(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商品(软删)
|
||||
*/
|
||||
@Override
|
||||
public void deleteProduct(String id) {
|
||||
Product product = new Product();
|
||||
product.setProductId(id);
|
||||
product.setStatus(0);
|
||||
productMapper.updateById(product);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存预警商品
|
||||
* 查询库存低于预警值的商品
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getStockAlerts() {
|
||||
// 查询所有商品及其库存
|
||||
List<Product> products = productMapper.selectList(new LambdaQueryWrapper<Product>()
|
||||
.eq(Product::getStatus, 1));
|
||||
|
||||
List<Map<String, Object>> alerts = new ArrayList<>();
|
||||
for (Product product : products) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, product.getProductId()));
|
||||
int quantity = stock != null ? stock.getQuantity() : 0;
|
||||
if (quantity < product.getStockAlert()) {
|
||||
Map<String, Object> alert = new HashMap<>();
|
||||
alert.put("productId", product.getProductId());
|
||||
alert.put("productName", product.getName());
|
||||
alert.put("stockAlert", product.getStockAlert());
|
||||
alert.put("currentStock", quantity);
|
||||
alerts.add(alert);
|
||||
}
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.example.building.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.example.building.entity.Product;
|
||||
import com.example.building.entity.Stock;
|
||||
import com.example.building.entity.StockFlow;
|
||||
import com.example.building.mapper.ProductMapper;
|
||||
import com.example.building.mapper.StockFlowMapper;
|
||||
import com.example.building.mapper.StockMapper;
|
||||
import com.example.building.service.StockService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 库存服务实现类
|
||||
* 核心业务:入库、库存查询、库存流水
|
||||
*/
|
||||
@Service
|
||||
public class StockServiceImpl implements StockService {
|
||||
|
||||
@Autowired
|
||||
private StockMapper stockMapper;
|
||||
|
||||
@Autowired
|
||||
private StockFlowMapper stockFlowMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
/**
|
||||
* 入库
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Stock stockIn(String productId, Integer quantity, String remark, String operatorId) {
|
||||
// 验证商品存在
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
|
||||
// 查询或创建库存记录
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
|
||||
int beforeQty = 0;
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setStockId(UUID.randomUUID().toString());
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(0);
|
||||
stock.setLockedQuantity(0);
|
||||
stockMapper.insert(stock);
|
||||
} else {
|
||||
beforeQty = stock.getQuantity();
|
||||
}
|
||||
|
||||
// 更新库存
|
||||
stock.setQuantity(beforeQty + quantity);
|
||||
stockMapper.updateById(stock);
|
||||
|
||||
// 记录库存流水 (类型1: 入库)
|
||||
saveStockFlow(productId, 1, quantity, beforeQty, beforeQty + quantity,
|
||||
null, "stock_in", operatorId, remark);
|
||||
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
*/
|
||||
@Override
|
||||
public Page<Stock> getStockList(String keyword, Integer page, Integer pageSize) {
|
||||
Page<Stock> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<Stock> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.like(Stock::getProductId, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(Stock::getUpdatedAt);
|
||||
return stockMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单商品库存
|
||||
*/
|
||||
@Override
|
||||
public Stock getStock(String productId) {
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(0);
|
||||
stock.setLockedQuantity(0);
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存调整
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Stock adjustStock(String productId, Integer quantity, String remark, String operatorId) {
|
||||
// 验证商品存在
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
|
||||
// 查询或创建库存记录
|
||||
Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getProductId, productId));
|
||||
|
||||
int beforeQty = 0;
|
||||
if (stock == null) {
|
||||
stock = new Stock();
|
||||
stock.setStockId(UUID.randomUUID().toString());
|
||||
stock.setProductId(productId);
|
||||
stock.setQuantity(quantity);
|
||||
stock.setLockedQuantity(0);
|
||||
stockMapper.insert(stock);
|
||||
beforeQty = 0;
|
||||
} else {
|
||||
beforeQty = stock.getQuantity();
|
||||
stock.setQuantity(quantity);
|
||||
stockMapper.updateById(stock);
|
||||
}
|
||||
|
||||
// 记录库存流水 (类型3: 调整)
|
||||
int changeQty = quantity - beforeQty;
|
||||
saveStockFlow(productId, 3, changeQty, beforeQty, quantity,
|
||||
null, "adjust", operatorId, remark);
|
||||
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存流水
|
||||
*/
|
||||
@Override
|
||||
public Page<StockFlow> getStockFlow(String productId, Integer type, Integer page, Integer pageSize) {
|
||||
Page<StockFlow> pageParam = new Page<>(page, pageSize);
|
||||
LambdaQueryWrapper<StockFlow> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(productId)) {
|
||||
wrapper.eq(StockFlow::getProductId, productId);
|
||||
}
|
||||
if (type != null) {
|
||||
wrapper.eq(StockFlow::getType, type);
|
||||
}
|
||||
wrapper.orderByDesc(StockFlow::getCreatedAt);
|
||||
return stockFlowMapper.selectPage(pageParam, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存库存流水
|
||||
*/
|
||||
private void saveStockFlow(String productId, Integer type, Integer quantity,
|
||||
Integer beforeQty, Integer afterQty, String relatedId,
|
||||
String relatedType, String operatorId, String remark) {
|
||||
StockFlow flow = new StockFlow();
|
||||
flow.setFlowId(UUID.randomUUID().toString());
|
||||
flow.setProductId(productId);
|
||||
flow.setType(type);
|
||||
flow.setQuantity(quantity);
|
||||
flow.setBeforeQuantity(beforeQty);
|
||||
flow.setAfterQuantity(afterQty);
|
||||
flow.setRelatedId(relatedId);
|
||||
flow.setRelatedType(relatedType);
|
||||
flow.setOperatorId(operatorId);
|
||||
flow.setRemark(remark);
|
||||
stockFlowMapper.insert(flow);
|
||||
}
|
||||
}
|
||||
51
src/main/resources/application.yml
Normal file
51
src/main/resources/application.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: building
|
||||
|
||||
# PostgreSQL数据库配置
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:5432/building_materials
|
||||
username: postgres
|
||||
password: postgres
|
||||
|
||||
# Redis配置
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
database: 0
|
||||
timeout: 3000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-wait: -1ms
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
|
||||
# MyBatis-Plus配置
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: assign_uuid
|
||||
logic-delete-field: status
|
||||
logic-delete-value: 0
|
||||
logic-not-delete-value: 1
|
||||
|
||||
# JWT配置
|
||||
jwt:
|
||||
secret: building-materials-secret-key-2024
|
||||
expiration: 7200000 # 2小时
|
||||
refresh-expiration: 604800000 # 7天
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.example.building: debug
|
||||
org.springframework: info
|
||||
Reference in New Issue
Block a user