技术架构设计.md
37.6 KB
Apple经销商ERP系统对接项目 - 技术架构设计
1. 项目概述
1.1 项目背景
Apple经销商ERP系统对接项目旨在构建全链路返利监控体系,实现下游渠道精细化管理。系统通过标准化接口实时获取经销商ERP关键业务数据,包括订单基础信息、库存流转状态、财务结算凭证等,同时追踪每笔订单返利使用明细,形成"订单-出库-开票-返利使用"全流程数据闭环。
1.2 核心目标
- 构建全链路返利监控体系:精准掌握返利政策落地执行情况
- 实现下游渠道精细化管理:通过系统对接实现与经销商ERP平台的业务数据联通
- 建立多维度校验机制:防范数据篡改或异常申报风险
- 提供可靠数据支撑:为返利政策落地与渠道决策提供依据
1.3 技术选型原则
- 参考若依架构:借鉴若依成熟的技术架构和设计模式
- 从0开始开发:基于若依架构思想,重新设计和实现
- 技术栈现代化:选择主流、稳定的技术栈
- 可扩展性强:支持业务快速发展和功能扩展
2. 整体架构设计
2.1 系统架构图
┌─────────────────────────────────────────────────────────────────┐
│ 用户层 (User Layer) │
├─────────────────────────────────────────────────────────────────┤
│ Web浏览器 │ 第三方系统 │ 经销商ERP系统 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
├─────────────────────────────────────────────────────────────────┤
│ 订单管理服务 │ 出库查询服务 │ 发票管理服务 │ 返利看板服务 │
│ 数据验证服务 │ 异常工单服务 │ 产品管理服务 │ 经销商管理服务 │
│ 系统管理服务 │ 首页统计服务 │ 文件管理服务 │ 消息通知服务 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 业务层 (Business Layer) │
├─────────────────────────────────────────────────────────────────┤
│ 数据验证引擎 │ 返利计算引擎 │ 异常处理引擎 │ 报表生成引擎 │
│ 权限控制引擎 │ 工作流引擎 │ 缓存管理引擎 │ 日志记录引擎 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 数据层 (Data Layer) │
├─────────────────────────────────────────────────────────────────┤
│ MySQL数据库 │ Redis缓存 │
└─────────────────────────────────────────────────────────────────┘
2.2 技术架构分层
2.2.1 表现层 (Presentation Layer)
- 前端技术栈:Vue 3 + TypeScript + Element Plus
- 构建工具:Vite
- 状态管理:Pinia
- 路由管理:Vue Router 4
- HTTP客户端:Axios
- 图表组件:ECharts 5
- UI组件库:Element Plus
- 目标平台:Web浏览器(PC端)
2.2.2 控制层 (Controller Layer)
- Web框架:Spring Boot 2.7.x
- MVC框架:Spring MVC
- 参数校验:Spring Validation
- 异常处理:全局异常处理器
- 跨域处理:CORS配置
2.2.3 业务层 (Service Layer)
- 业务逻辑:Spring Service
- 事务管理:Spring Transaction
- 缓存管理:Spring Cache + Redis
- 异步处理:Spring Async
- 定时任务:Spring Task
2.2.4 数据访问层 (Data Access Layer)
- ORM框架:MyBatis Plus 3.5.x
- 数据库连接池:HikariCP
- 分页插件:MyBatis Plus分页插件
- 代码生成器:MyBatis Plus代码生成器
2.2.5 数据存储层 (Data Storage Layer)
- 关系型数据库:MySQL 8.0
- 缓存数据库:Redis 6.0
3. 技术栈详细设计
3.1 后端技术栈
3.1.1 核心框架
<!-- Spring Boot 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.18</version>
</dependency>
<!-- Spring Security 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.18</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.18</version>
</dependency>
3.1.2 数据访问层
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- HikariCP 连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
3.1.3 工具类库
<!-- Hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
<!-- Jackson JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
3.2 前端技术栈
3.2.1 核心框架
{
"dependencies": {
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"pinia": "^2.1.6",
"element-plus": "^2.3.8",
"axios": "^1.4.0",
"echarts": "^5.4.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.4.5",
"typescript": "^5.1.6",
"@types/node": "^20.4.5"
}
}
3.2.2 项目结构
src/
├── api/ # API接口定义
├── assets/ # 静态资源
├── components/ # 公共组件
├── layouts/ # 布局组件(PC端布局)
├── router/ # 路由配置
├── stores/ # 状态管理
├── styles/ # 样式文件
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── dashboard/ # 首页
│ ├── order/ # 订单管理
│ ├── delivery/ # 出库查询
│ ├── invoice/ # 发票管理
│ ├── rebate/ # 返利看板
│ ├── validation/ # 数据验证
│ ├── workorder/ # 异常工单
│ ├── product/ # 产品管理
│ ├── dealer/ # 经销商管理
│ └── system/ # 系统管理
└── main.ts # 入口文件
4. 核心模块设计
4.1 订单管理模块
4.1.1 模块架构
@RestController
@RequestMapping("/api/order")
@Api(tags = "订单管理")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/list")
@ApiOperation("订单查询")
public R<PageResult<OrderVO>> list(OrderQueryDTO query) {
return R.ok(orderService.selectOrderList(query));
}
@PostMapping("/export")
@ApiOperation("订单导出")
public void export(OrderQueryDTO query, HttpServletResponse response) {
orderService.exportOrder(query, response);
}
}
4.1.2 业务服务层
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Override
public PageResult<OrderVO> selectOrderList(OrderQueryDTO query) {
Page<Order> page = new Page<>(query.getPageNum(), query.getPageSize());
List<Order> orders = orderMapper.selectOrderList(page, query);
List<OrderVO> orderVOs = BeanUtil.copyToList(orders, OrderVO.class);
return new PageResult<>(orderVOs, page.getTotal());
}
}
4.2 数据验证模块
4.2.1 验证引擎设计
@Component
public class DataValidationEngine {
@Autowired
private List<ValidationRule> validationRules;
/**
* 执行数据验证
*/
public ValidationResult validate(ValidationContext context) {
ValidationResult result = new ValidationResult();
for (ValidationRule rule : validationRules) {
if (rule.supports(context)) {
ValidationResult ruleResult = rule.validate(context);
result.merge(ruleResult);
}
}
return result;
}
}
4.2.2 验证规则实现
@Component
public class TimeSequenceValidationRule implements ValidationRule {
@Override
public boolean supports(ValidationContext context) {
return context.getDataType() == DataType.ORDER;
}
@Override
public ValidationResult validate(ValidationContext context) {
ValidationResult result = new ValidationResult();
OrderData orderData = context.getOrderData();
// 验证开票时间不得早于出库时间
if (orderData.getInvoiceDate() != null &&
orderData.getDeliveryDate() != null &&
orderData.getInvoiceDate().before(orderData.getDeliveryDate())) {
result.addError("开票时间不得早于出库时间");
}
return result;
}
}
4.3 异常工单模块
4.3.1 工单服务设计
@Service
@Transactional
public class WorkorderServiceImpl implements WorkorderService {
@Autowired
private WorkorderMapper workorderMapper;
@Autowired
private WorkorderLogMapper workorderLogMapper;
@Autowired
private NotificationService notificationService;
/**
* 创建异常工单
*/
@Override
public void createWorkorder(ValidationResult validationResult) {
Workorder workorder = new Workorder();
workorder.setWorkorderNo(generateWorkorderNo());
workorder.setExceptionType(validationResult.getExceptionType());
workorder.setSeverityLevel(validationResult.getSeverityLevel());
workorder.setExceptionDesc(validationResult.getErrorMessage());
workorder.setWorkorderStatus(WorkorderStatus.PENDING);
workorderMapper.insert(workorder);
// 发送通知
if (workorder.getSeverityLevel() == SeverityLevel.HIGH) {
notificationService.sendHighPriorityNotification(workorder);
}
}
}
4.4 返利计算模块
4.4.1 返利计算引擎
@Component
public class RebateCalculationEngine {
@Autowired
private RebateRuleService rebateRuleService;
@Autowired
private ProductService productService;
/**
* 计算订单返利
*/
public BigDecimal calculateOrderRebate(OrderData orderData) {
BigDecimal totalRebate = BigDecimal.ZERO;
for (OrderItemData item : orderData.getItems()) {
ProductInfo product = productService.getByCode(item.getProductCode());
if (product.getRebateFlag() == 1) {
RebateRule rule = rebateRuleService.getRule(product, orderData.getDealerLevel());
BigDecimal itemRebate = rule.calculateRebate(item);
totalRebate = totalRebate.add(itemRebate);
}
}
return totalRebate;
}
}
5. 数据库设计
5.1 数据库架构
-- 创建数据库
CREATE DATABASE apple_erp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE apple_erp;
5.2 核心表结构
5.2.1 订单主表
CREATE TABLE t_order_main (
order_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
order_no VARCHAR(30) NOT NULL UNIQUE COMMENT '订单编号',
dealer_code VARCHAR(20) NOT NULL COMMENT '经销商编码',
dealer_name VARCHAR(100) NOT NULL COMMENT '经销商名称',
order_date DATETIME NOT NULL COMMENT '订单创建日期',
total_amount DECIMAL(18,2) NOT NULL COMMENT '订单总金额',
rebate_amount DECIMAL(18,2) DEFAULT 0 COMMENT '订单返利金额',
delivery_status TINYINT DEFAULT 0 COMMENT '出库状态',
invoice_status TINYINT DEFAULT 0 COMMENT '开票状态',
rebate_calc_flag TINYINT DEFAULT 0 COMMENT '返利计算状态',
data_source VARCHAR(20) COMMENT '数据来源',
verify_status TINYINT DEFAULT 0 COMMENT '数据验证状态',
upload_time DATETIME COMMENT '数据上传时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单主表';
5.2.2 系统用户表
CREATE TABLE t_sys_user (
user_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
real_name VARCHAR(50) NOT NULL COMMENT '真实姓名',
password VARCHAR(100) NOT NULL COMMENT '密码',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(100) COMMENT '邮箱',
status TINYINT DEFAULT 1 COMMENT '状态',
last_login_time DATETIME COMMENT '最后登录时间',
last_login_ip VARCHAR(50) COMMENT '最后登录IP',
remark VARCHAR(500) COMMENT '备注',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户表';
6. 安全架构设计
6.1 认证授权
6.1.1 Spring Security配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
6.1.2 JWT Token管理
@Component
public class JwtTokenProvider {
private static final String SECRET_KEY = "apple-erp-secret-key";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
}
6.2 数据安全
6.2.1 敏感数据加密
@Component
public class DataEncryption {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY = "AppleERP2025Key";
/**
* 加密敏感数据
*/
public String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("数据加密失败", e);
}
}
/**
* 解密敏感数据
*/
public String decrypt(String encryptedData) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(encryptedData);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted);
} catch (Exception e) {
throw new RuntimeException("数据解密失败", e);
}
}
}
7. 缓存架构设计
7.1 Redis配置
7.1.1 Redis配置类
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
7.1.2 缓存策略
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 经销商信息缓存
*/
@Cacheable(value = "dealer", key = "#dealerCode")
public DealerInfo getDealerInfo(String dealerCode) {
return dealerMapper.selectByCode(dealerCode);
}
/**
* 产品信息缓存
*/
@Cacheable(value = "product", key = "#productCode")
public ProductInfo getProductInfo(String productCode) {
return productMapper.selectByCode(productCode);
}
/**
* 字典数据缓存
*/
@Cacheable(value = "dict", key = "#dictType")
public List<DictItem> getDictItems(String dictType) {
return dictMapper.selectByType(dictType);
}
}
8. 接口设计规范
8.1 RESTful API设计
8.1.1 统一返回格式
@Data
@ApiModel("统一返回结果")
public class R<T> {
@ApiModelProperty("状态码")
private Integer code;
@ApiModelProperty("返回消息")
private String msg;
@ApiModelProperty("数据对象")
private T data;
public static <T> R<T> ok() {
return restResult(null, 200, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, 200, "操作成功");
}
public static <T> R<T> error(String msg) {
return restResult(null, 500, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
}
8.1.2 分页查询结果
@Data
@ApiModel("分页查询结果")
public class PageResult<T> {
@ApiModelProperty("数据列表")
private List<T> records;
@ApiModelProperty("总记录数")
private Long total;
@ApiModelProperty("当前页码")
private Long current;
@ApiModelProperty("每页大小")
private Long size;
public PageResult(List<T> records, Long total) {
this.records = records;
this.total = total;
}
}
8.2 接口文档
8.2.1 Swagger配置
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.apple.erp.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Apple经销商ERP系统API文档")
.description("Apple经销商ERP系统对接项目接口文档")
.version("1.0.0")
.build();
}
}
9. 异常处理设计
9.1 全局异常处理
9.1.1 异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public R<Void> handleServiceException(ServiceException e) {
log.error("业务异常:{}", e.getMessage());
return R.error(e.getMessage());
}
/**
* 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.error("参数校验异常:{}", message);
return R.error("参数错误: " + message);
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e) {
log.error("系统异常", e);
return R.error("系统异常,请联系管理员");
}
}
9.1.2 自定义异常
public class ServiceException extends RuntimeException {
private Integer code;
public ServiceException(String message) {
super(message);
this.code = 500;
}
public ServiceException(Integer code, String message) {
super(message);
this.code = code;
}
public Integer getCode() {
return code;
}
}
10. 日志设计
10.1 日志配置
10.1.1 Logback配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/apple-erp.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/apple-erp.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 异步输出 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC"/>
</root>
</configuration>
10.1.2 操作日志切面
@Aspect
@Component
@Slf4j
public class LogAspect {
@Autowired
private SysLogService sysLogService;
@Around("@annotation(log)")
public Object around(ProceedingJoinPoint point, Log log) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = point.proceed();
long endTime = System.currentTimeMillis();
// 记录操作日志
saveLog(point, log, endTime - startTime, null);
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
saveLog(point, log, endTime - startTime, e);
throw e;
}
}
private void saveLog(ProceedingJoinPoint point, Log log, long time, Exception e) {
SysLog sysLog = new SysLog();
sysLog.setModule(log.module());
sysLog.setOperation(log.operation());
sysLog.setLogTime(new Date());
sysLog.setStatus(e == null ? 1 : 0);
sysLog.setErrorMsg(e != null ? e.getMessage() : null);
sysLogService.save(sysLog);
}
}
11. 性能优化设计
11.1 数据库优化
11.1.1 索引设计
-- 订单表索引
CREATE INDEX idx_order_dealer_code ON t_order_main(dealer_code);
CREATE INDEX idx_order_date ON t_order_main(order_date);
CREATE INDEX idx_order_status ON t_order_main(delivery_status, invoice_status);
-- 出库表索引
CREATE INDEX idx_delivery_order_no ON t_delivery_main(order_no);
CREATE INDEX idx_delivery_date ON t_delivery_main(delivery_date);
-- 发票表索引
CREATE INDEX idx_invoice_order_no ON t_invoice_main(order_no);
CREATE INDEX idx_invoice_date ON t_invoice_main(invoice_date);
11.1.2 查询优化
@Service
public class OrderServiceImpl implements OrderService {
/**
* 分页查询优化
*/
@Override
public PageResult<OrderVO> selectOrderList(OrderQueryDTO query) {
// 使用覆盖索引优化查询
Page<Order> page = new Page<>(query.getPageNum(), query.getPageSize());
// 构建查询条件
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(query.getDealerCode()), Order::getDealerCode, query.getDealerCode())
.ge(query.getStartDate() != null, Order::getOrderDate, query.getStartDate())
.le(query.getEndDate() != null, Order::getOrderDate, query.getEndDate())
.eq(query.getDeliveryStatus() != null, Order::getDeliveryStatus, query.getDeliveryStatus())
.orderByDesc(Order::getOrderDate);
List<Order> orders = orderMapper.selectList(page, wrapper);
List<OrderVO> orderVOs = BeanUtil.copyToList(orders, OrderVO.class);
return new PageResult<>(orderVOs, page.getTotal());
}
}
11.2 前端优化
11.2.1 组件懒加载
// 路由懒加载
const routes = [
{
path: '/order',
name: 'Order',
component: () => import('@/views/order/index.vue')
},
{
path: '/delivery',
name: 'Delivery',
component: () => import('@/views/delivery/index.vue')
}
];
11.2.2 虚拟滚动
<template>
<el-table-v2
:columns="columns"
:data="tableData"
:width="700"
:height="400"
fixed
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const columns = ref([
{ key: 'orderNo', title: '订单编号', width: 150 },
{ key: 'dealerName', title: '经销商名称', width: 200 },
{ key: 'totalAmount', title: '订单金额', width: 120 },
{ key: 'orderDate', title: '订单日期', width: 150 }
]);
const tableData = ref([]);
</script>
12. 监控设计
12.1 应用监控
12.1.1 Actuator配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
12.1.2 自定义监控指标
@Component
public class BusinessMetrics {
private final MeterRegistry meterRegistry;
public BusinessMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
/**
* 订单创建计数器
*/
public void incrementOrderCreated() {
meterRegistry.counter("order.created").increment();
}
/**
* 异常工单生成计数器
*/
public void incrementExceptionGenerated() {
meterRegistry.counter("exception.generated").increment();
}
/**
* 数据验证失败计数器
*/
public void incrementValidationFailed() {
meterRegistry.counter("validation.failed").increment();
}
}
13. 部署架构
13.1 容器化部署
13.1.1 Dockerfile
# 后端应用Dockerfile
FROM openjdk:8-jre-alpine
VOLUME /tmp
COPY target/apple-erp.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
# 前端应用Dockerfile
FROM node:16-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
13.1.2 Docker Compose
version: '3.8'
services:
# 后端应用
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=mysql
- REDIS_HOST=redis
networks:
- apple-erp-network
# 前端应用(可选,开发时可直接访问后端)
# frontend:
# build: ./frontend
# ports:
# - "80:80"
# depends_on:
# - app
# networks:
# - apple-erp-network
# MySQL数据库
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: apple_erp
MYSQL_USER: apple_erp
MYSQL_PASSWORD: apple_erp123
volumes:
- mysql_data:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
ports:
- "3306:3306"
networks:
- apple-erp-network
# Redis缓存
redis:
image: redis:6.0-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- apple-erp-network
volumes:
mysql_data:
redis_data:
networks:
apple-erp-network:
driver: bridge
13.2 简化部署配置
13.2.1 开发环境配置
# application-dev.yml
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 10000ms
13.2.2 生产环境配置
# application-prod.yml
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:apple_erp}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: ${REDIS_DATABASE:0}
timeout: 10000ms
14. 开发规范
14.1 代码规范
14.1.1 包结构规范
com.apple.erp
├── common/ # 公共模块
│ ├── config/ # 配置类
│ ├── constant/ # 常量定义
│ ├── enums/ # 枚举类
│ ├── exception/ # 异常类
│ ├── utils/ # 工具类
│ └── result/ # 返回结果类
├── modules/ # 业务模块
│ ├── order/ # 订单管理
│ ├── delivery/ # 出库查询
│ ├── invoice/ # 发票管理
│ ├── rebate/ # 返利看板
│ ├── validation/ # 数据验证
│ ├── workorder/ # 异常工单
│ ├── product/ # 产品管理
│ ├── dealer/ # 经销商管理
│ └── system/ # 系统管理
└── Application.java # 启动类
14.1.2 命名规范
// 类命名:大驼峰命名法
public class OrderController {}
public class OrderServiceImpl {}
// 方法命名:小驼峰命名法
public void selectOrderList() {}
public void createOrder() {}
// 常量命名:全大写,下划线分隔
public static final String ORDER_STATUS_PENDING = "PENDING";
// 包命名:全小写,点分隔
com.apple.erp.modules.order.controller
14.2 接口规范
14.2.1 Controller规范
@RestController
@RequestMapping("/api/order")
@Api(tags = "订单管理")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/list")
@ApiOperation("订单查询")
@PreAuthorize("hasAuthority('order:list')")
public R<PageResult<OrderVO>> list(OrderQueryDTO query) {
try {
PageResult<OrderVO> result = orderService.selectOrderList(query);
return R.ok(result);
} catch (Exception e) {
log.error("订单查询失败", e);
return R.error("查询失败");
}
}
}
15. 总结
15.1 架构特点
- 参考若依架构:借鉴若依成熟的技术架构和设计模式
- 从0开始开发:基于若依架构思想,重新设计和实现
- 模块化设计:清晰的模块划分,便于维护和扩展
- 标准化开发:统一的开发规范和接口设计
15.2 技术优势
- 技术栈现代化:使用主流、稳定的技术栈
- 性能优化:数据库优化、缓存策略、前端优化
- 安全可靠:完善的认证授权和数据安全机制
- 监控完善:应用监控、业务监控、日志监控
15.3 扩展性
- 功能扩展:模块化设计,便于新增功能模块
- 数据扩展:支持分库分表和读写分离
- 接口扩展:RESTful API设计,便于第三方集成
- 架构扩展:后续可引入网关、负载均衡等组件
- 平台扩展:后续可开发移动端H5、小程序等
- 存储扩展:后续可引入文件存储、消息队列等组件
文档版本: v1.0
创建时间: 2025-01-27
最后更新: 2025-01-27
维护人员: 开发团队