1. 项目背景与行业痛点
窗帘行业作为家居装饰的重要组成部分,长期以来一直面临着报价流程繁琐、效率低下的问题。传统的手工丈量、计算器计价方式存在三个核心痛点:
-
时间成本高:从客户咨询到最终报价,通常需要经历多次现场测量、反复沟通和手工计算,整个过程可能耗时数天。
-
误差风险大:人工计算容易在尺寸换算、面料价格叠加、工艺费用计算等环节出错,导致报价不准确。
-
管理困难:历史报价难以追溯,无法形成有效的价格数据库,不利于企业进行成本分析和经营决策。
以某中型窗帘门店为例,其日均处理20单报价需求,每单平均耗时45分钟,其中:
- 现场测量:15分钟
- 手工计算:20分钟
- 复核确认:10分钟
年累计耗时约5,850小时,相当于3个全职员工的工作量。而采用自动化报价系统后,这一时间可缩短至5分钟/单,效率提升近90%。
2. 系统架构设计
2.1 技术选型与架构
本系统采用前后端分离的架构设计,主要技术栈如下:
前端技术栈:
- Vue.js 3.x:采用Composition API编写组件,实现响应式界面
- Element Plus:提供丰富的UI组件库
- Axios:处理HTTP请求,与后端API交互
- Vue Router:实现前端路由管理
- ECharts:用于数据可视化展示
后端技术栈:
- Spring Boot 2.7:快速构建企业级应用
- Spring Security:实现权限控制和认证
- MyBatis-Plus:简化数据库操作
- Redis:缓存热点数据,提高系统响应速度
- Swagger:API文档自动生成
数据库设计:
- MySQL 8.0:关系型数据库,存储业务数据
- 主要表结构设计:
- 用户表(sys_user)
- 窗帘分类表(curtain_category)
- 窗帘信息表(curtain_info)
- 报价记录表(quotation_record)
- 审核流程表(approval_flow)
2.2 核心功能模块
系统主要分为四大模块:
-
基础信息管理模块
- 用户权限管理(RBAC模型)
- 窗帘分类与产品库维护
- 门店与销售人员管理
-
智能报价模块
- 尺寸自动换算(厘米↔英寸)
- 面料价格计算(含损耗率)
- 工艺费用叠加(褶皱、拼接等)
- 折扣策略应用(会员价、促销价)
-
工作流引擎模块
- 多级审核流程配置
- 状态机设计(草稿→提交→审核→完成)
- 消息通知机制(站内信+邮件)
-
数据分析模块
- 报价成功率统计
- 产品热度分析
- 销售业绩报表
3. 核心算法与实现细节
3.1 智能报价算法实现
报价计算是系统的核心功能,主要计算公式如下:
code复制总价 = (面料单价 × 宽度 × 高度 × 损耗系数)
+ (轨道/罗马杆单价 × 宽度)
+ 工艺费
- 折扣金额
Java实现代码示例:
java复制public BigDecimal calculateQuotation(QuotationDTO dto) {
// 计算面料费用
BigDecimal fabricCost = dto.getFabricPrice()
.multiply(dto.getWidth())
.multiply(dto.getHeight())
.multiply(dto.getWastageFactor());
// 计算轨道费用
BigDecimal railCost = dto.getRailPrice().multiply(dto.getWidth());
// 计算总价
BigDecimal total = fabricCost
.add(railCost)
.add(dto.getProcessFee())
.subtract(dto.getDiscountAmount());
return total.setScale(2, RoundingMode.HALF_UP);
}
3.2 多级审核流程设计
系统采用状态机模式实现报价审核流程:
mermaid复制stateDiagram
[*] --> 草稿
草稿 --> 已提交: 用户提交
已提交 --> 销售审核: 自动分配
销售审核 --> 店长审核: 销售通过
销售审核 --> 已驳回: 销售拒绝
店长审核 --> 已完成: 店长通过
店长审核 --> 已驳回: 店长拒绝
对应的状态转换代码:
java复制public enum QuotationStatus {
DRAFT("草稿"),
SUBMITTED("已提交"),
SALES_REVIEW("销售审核"),
MANAGER_REVIEW("店长审核"),
REJECTED("已驳回"),
COMPLETED("已完成");
// 状态转换规则
private static final Map<QuotationStatus, Set<QuotationStatus>> transitions = Map.of(
DRAFT, Set.of(SUBMITTED),
SUBMITTED, Set.of(SALES_REVIEW),
SALES_REVIEW, Set.of(MANAGER_REVIEW, REJECTED),
MANAGER_REVIEW, Set.of(COMPLETED, REJECTED)
);
public static boolean isValidTransition(QuotationStatus from, QuotationStatus to) {
return transitions.getOrDefault(from, Collections.emptySet()).contains(to);
}
}
4. 关键业务实现
4.1 权限控制系统
基于Spring Security的权限控制配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/manager/**").hasRole("MANAGER")
.antMatchers("/sales/**").hasRole("SALES")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
4.2 数据库事务管理
使用Spring的声明式事务管理确保数据一致性:
java复制@Service
@Transactional
public class QuotationServiceImpl implements QuotationService {
@Autowired
private QuotationMapper quotationMapper;
@Autowired
private ApprovalFlowMapper flowMapper;
@Override
public void submitQuotation(Quotation quotation) {
// 保存报价单
quotationMapper.insert(quotation);
// 创建审核流程
ApprovalFlow flow = new ApprovalFlow();
flow.setQuotationId(quotation.getId());
flow.setStatus(QuotationStatus.SUBMITTED);
flowMapper.insert(flow);
// 发送通知
notificationService.sendSubmitNotification(quotation);
}
}
5. 性能优化实践
5.1 缓存策略设计
采用多级缓存提升系统响应速度:
- 本地缓存:使用Caffeine缓存热点数据
java复制@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
return cacheManager;
}
}
- 分布式缓存:Redis缓存共享数据
java复制@Repository
public class ProductCacheRepository {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void cacheProductInfo(Product product) {
redisTemplate.opsForValue().set(
"product:" + product.getId(),
product,
1, TimeUnit.HOURS
);
}
}
5.2 数据库优化
- 索引设计:
sql复制CREATE INDEX idx_quotation_status ON quotation_record(status);
CREATE INDEX idx_curtain_category ON curtain_info(category_id);
- 查询优化:
java复制@Mapper
public interface QuotationMapper {
@Select("SELECT * FROM quotation_record WHERE status = #{status} LIMIT #{size}")
List<Quotation> findByStatus(@Param("status") String status, @Param("size") int size);
}
6. 安全防护措施
6.1 数据安全
- 敏感数据加密:
java复制public class DataEncryptor {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final IvParameterSpec iv = new IvParameterSpec(new byte[16]);
public static String encrypt(String input, SecretKey key) {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(cipherText);
}
}
- SQL注入防护:
- 使用MyBatis的参数绑定
- 避免字符串拼接SQL
6.2 接口安全
- 防重放攻击:
java复制@RestController
public class QuotationController {
@PostMapping("/api/quotations")
public ResponseEntity<?> createQuotation(
@RequestBody QuotationDTO dto,
@RequestHeader("X-Nonce") String nonce) {
if (nonceCache.exists(nonce)) {
throw new BadRequestException("Duplicate request");
}
nonceCache.add(nonce, 5, TimeUnit.MINUTES);
// 处理业务逻辑
}
}
- 速率限制:
java复制@Configuration
public class RateLimitConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RateLimitInterceptor(100, 1, TimeUnit.MINUTES))
.addPathPatterns("/api/**");
}
}
7. 部署与监控
7.1 容器化部署
Docker Compose部署配置示例:
yaml复制version: '3.8'
services:
app:
image: curtain-quotation:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
redis:
image: redis:6.2
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=curtain
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
7.2 监控方案
- Spring Boot Actuator:
properties复制# application.properties
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
- Prometheus监控:
java复制@Configuration
public class MetricsConfig {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "curtain-quotation"
);
}
}
8. 测试策略
8.1 单元测试
使用JUnit 5和Mockito编写测试用例:
java复制@ExtendWith(MockitoExtension.class)
class QuotationServiceTest {
@Mock
private QuotationRepository quotationRepo;
@InjectMocks
private QuotationServiceImpl quotationService;
@Test
void shouldCalculateCorrectQuotation() {
QuotationDTO dto = new QuotationDTO();
dto.setFabricPrice(new BigDecimal("50"));
dto.setWidth(new BigDecimal("2"));
dto.setHeight(new BigDecimal("1.5"));
dto.setWastageFactor(new BigDecimal("1.1"));
BigDecimal result = quotationService.calculateQuotation(dto);
assertEquals(new BigDecimal("165.00"), result);
}
}
8.2 集成测试
使用TestContainers进行数据库集成测试:
java复制@Testcontainers
@SpringBootTest
class QuotationIntegrationTest {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
}
@Test
void shouldSaveQuotationToDatabase() {
// 测试数据库操作
}
}
9. 项目总结与改进方向
在实际开发过程中,我们遇到了几个关键挑战并找到了解决方案:
- 价格计算的精度问题:
- 问题:使用double类型导致金额计算出现精度丢失
- 解决方案:全部改用BigDecimal进行金融计算
- 经验:在涉及金额计算的场景中,必须避免使用浮点数类型
- 并发报价冲突:
- 问题:多个销售同时修改同一报价单导致数据不一致
- 解决方案:采用乐观锁机制
java复制@Version
private Integer version;
- 性能瓶颈:
- 问题:热门窗帘品类查询缓慢
- 解决方案:引入多级缓存策略
- 效果:查询响应时间从800ms降低到50ms
未来改进方向:
- 引入机器学习算法,实现智能价格推荐
- 增加移动端APP,支持现场测量拍照上传
- 集成第三方支付系统,实现在线定金支付
在系统上线后,某窗帘门店的实际使用数据显示:
- 平均报价时间从45分钟缩短至5分钟
- 报价准确率从82%提升至99.5%
- 客户转化率提高了30%