1. 农业设备租赁系统的技术选型与架构设计
农业设备租赁系统作为连接农户与设备供应商的数字化平台,其技术架构需要兼顾业务复杂性和用户体验。我们采用前后端分离架构,主要基于以下技术栈:
后端技术栈:
- Spring Boot 2.7.x(兼顾稳定性和社区支持)
- MyBatis-Plus 3.5.x(增强CRUD操作)
- MySQL 8.0(事务型业务的首选)
- Redis 6.x(缓存与会话管理)
- Swagger 3.0(API文档自动化)
前端技术栈:
- Vue 3.2 + Composition API
- Element Plus(UI组件库)
- Axios(HTTP客户端)
- Vue Router 4.x(前端路由)
- ECharts 5.x(数据可视化)
技术选型心得:农业设备租赁具有明显的季节性特征,系统需要应对突发流量。Spring Boot的自动配置和内置Tomcat简化了部署,而Vue的响应式特性非常适合频繁更新的租赁状态展示。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计与业务模型
2.1 核心表结构设计
sql复制CREATE TABLE `equipment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '设备名称',
`type` varchar(50) NOT NULL COMMENT '设备类型',
`specification` json DEFAULT NULL COMMENT '技术参数(JSON格式)',
`daily_price` decimal(10,2) NOT NULL COMMENT '日租金',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-可租 1-已租 2-维修中',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `lease_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`equipment_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`total_amount` decimal(12,2) NOT NULL,
`payment_status` tinyint NOT NULL DEFAULT '0' COMMENT '0-未支付 1-已支付',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_equipment` (`equipment_id`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 业务逻辑实现要点
- 设备库存校验:采用乐观锁解决超租问题
java复制@Transactional
public boolean placeOrder(LeaseOrder order) {
Equipment equipment = equipmentMapper.selectById(order.getEquipmentId());
if (equipment.getStatus() != 0) {
throw new BusinessException("设备不可用");
}
// 更新设备状态(version字段用于乐观锁)
int updateCount = equipmentMapper.updateStatusWithVersion(
order.getEquipmentId(),
1,
equipment.getVersion());
if (updateCount == 0) {
throw new ConcurrentLeaseException("设备已被其他用户预定");
}
// 创建订单
return orderMapper.insert(order) > 0;
}
- 价格计算策略:采用策略模式实现不同设备的计价规则
java复制public interface PriceStrategy {
BigDecimal calculate(LeasePeriod period);
}
// 拖拉机按天计价
public class TractorStrategy implements PriceStrategy {
@Override
public BigDecimal calculate(LeasePeriod period) {
long days = ChronoUnit.DAYS.between(period.getStart(), period.getEnd());
return dailyRate.multiply(BigDecimal.valueOf(days));
}
}
// 灌溉设备按季节计价
public class IrrigationStrategy implements PriceStrategy {
@Override
public BigDecimal calculate(LeasePeriod period) {
// 季节系数计算逻辑
}
}
3. 前后端交互关键实现
3.1 API设计规范
采用RESTful风格设计,主要接口示例:
| 端点 | 方法 | 描述 | 参数示例 |
|---|---|---|---|
| /api/equipment | GET | 分页查询设备 | page=1&size=10&type=tractor |
| /api/equipment/ | GET | 获取设备详情 | - |
| /api/orders | POST | 创建租赁订单 | JSON订单数据 |
| /api/orders/{id}/payment | PUT | 更新支付状态 |
3.2 前端数据交互实现
- Axios实例配置:
javascript复制const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
// 请求拦截器
service.interceptors.request.use(config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
})
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.msg || 'Error')
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
}
)
- 设备列表分页查询:
javascript复制const queryEquipment = async (params) => {
try {
const { data } = await getEquipmentList({
page: params.pageIndex,
size: params.pageSize,
...params.query
})
return {
list: data.records,
total: data.total
}
} catch (error) {
console.error('获取设备列表失败:', error)
return {
list: [],
total: 0
}
}
}
4. 系统部署与运维实践
4.1 后端部署要点
- 多环境配置:
yaml复制# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/agri_lease?useSSL=false
username: dev_user
password: dev123
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/agri_lease?useSSL=true
username: ${DB_USER}
password: ${DB_PASSWORD}
redis:
host: redis-master
- Docker化部署:
dockerfile复制FROM openjdk:11-jre
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
启动命令:
bash复制docker build -t agri-lease-backend .
docker run -d -p 8080:8080 \
-e "SPRING_PROFILES_ACTIVE=prod" \
-e "DB_USER=admin" \
-e "DB_PASSWORD=securepwd" \
agri-lease-backend
4.2 前端部署方案
- Nginx配置示例:
nginx复制server {
listen 80;
server_name agri-lease.example.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
- CI/CD流程:
yaml复制# .github/workflows/deploy.yml
name: Deploy Frontend
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Build production
run: npm run build
- name: Deploy to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
source: "dist/"
target: "/var/www/agri-lease"
5. 典型业务场景实现
5.1 设备预约冲突检测
java复制public boolean checkAvailability(Long equipmentId, LocalDate start, LocalDate end) {
return orderMapper.countOverlappingOrders(equipmentId, start, end) == 0;
}
<!-- MyBatis映射文件 -->
<select id="countOverlappingOrders" resultType="int">
SELECT COUNT(1)
FROM lease_order
WHERE equipment_id = #{equipmentId}
AND (
(#{start} BETWEEN start_date AND end_date)
OR (#{end} BETWEEN start_date AND end_date)
OR (start_date BETWEEN #{start} AND #{end})
)
AND payment_status = 1
</select>
5.2 支付结果异步通知
java复制@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@PostMapping("/notify")
public String handleNotify(@RequestBody PaymentNotify notify) {
// 验证签名
if (!paymentService.verifySignature(notify)) {
return "FAIL";
}
// 更新订单状态
orderService.updatePaymentStatus(
notify.getOrderId(),
notify.getStatus(),
notify.getPaymentTime());
return "SUCCESS";
}
}
6. 性能优化实践
6.1 缓存策略实施
- Spring Cache配置:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
// 业务层使用
@Service
public class EquipmentServiceImpl implements EquipmentService {
@Cacheable(value = "equipment", key = "#id")
public Equipment getById(Long id) {
return equipmentMapper.selectById(id);
}
@CacheEvict(value = "equipment", key = "#equipment.id")
public void updateEquipment(Equipment equipment) {
equipmentMapper.updateById(equipment);
}
}
6.2 数据库查询优化
- MyBatis二级缓存配置:
xml复制<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 在Mapper.xml中启用 -->
<mapper namespace="com.agrilease.mapper.EquipmentMapper">
<cache eviction="LRU" flushInterval="60000" size="512"/>
</mapper>
- 复杂查询优化示例:
sql复制-- 优化前(全表扫描)
EXPLAIN SELECT * FROM equipment WHERE status = 0;
-- 优化后(添加索引后)
ALTER TABLE equipment ADD INDEX idx_status (status);
EXPLAIN SELECT id,name FROM equipment WHERE status = 0;
7. 安全防护措施
7.1 接口安全防护
- Spring Security配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/payment/notify").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
- JWT工具类:
java复制public class JwtTokenUtil {
private static final String SECRET = "your-256-bit-secret";
private static final long EXPIRATION = 86400000L; // 24小时
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public static boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (Exception e) {
log.error("JWT验证失败: {}", e.getMessage());
return false;
}
}
}
7.2 数据安全保护
- 敏感数据加密:
java复制public class AesUtil {
private static final String KEY = "your-32-byte-key";
private static final String IV = "your-16-byte-iv";
public static String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(KEY.getBytes(), "AES"),
new IvParameterSpec(IV.getBytes()));
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
}
- SQL注入防护:
xml复制<!-- MyBatis使用#{}防止注入 -->
<select id="findByCondition" resultType="Equipment">
SELECT * FROM equipment
WHERE type = #{type}
<if test="minPrice != null">
AND daily_price >= #{minPrice}
</if>
</select>
8. 监控与日志管理
8.1 Spring Boot Actuator集成
yaml复制# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
metrics:
enabled: true
8.2 日志收集方案
- Logback配置:
xml复制<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
- ELK日志收集架构:
code复制Filebeat -> Logstash -> Elasticsearch -> Kibana
9. 测试策略与实施
9.1 单元测试示例
java复制@SpringBootTest
public class EquipmentServiceTest {
@Autowired
private EquipmentService equipmentService;
@Test
@Transactional
@Rollback
public void testEquipmentRental() {
Equipment equipment = new Equipment();
equipment.setName("拖拉机-X100");
equipment.setDailyPrice(new BigDecimal("500.00"));
equipmentService.save(equipment);
LeaseOrder order = new LeaseOrder();
order.setEquipmentId(equipment.getId());
order.setStartDate(LocalDate.now());
order.setEndDate(LocalDate.now().plusDays(3));
boolean result = equipmentService.placeOrder(order);
assertTrue(result);
Equipment updated = equipmentService.getById(equipment.getId());
assertEquals(1, updated.getStatus());
}
}
9.2 API测试方案
- Postman测试集合:
json复制{
"info": {
"name": "农业设备租赁API测试",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "获取设备列表",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/api/equipment?page=1&size=10",
"host": ["{{base_url}}"],
"path": ["api","equipment"],
"query": [
{"key": "page","value": "1"},
{"key": "size","value": "10"}
]
}
},
"response": []
}
]
}
- JMeter压力测试配置:
code复制线程组:100并发用户
循环次数:无限
持续时间:5分钟
采样器:HTTP请求到关键API
监听器:聚合报告、响应时间图
10. 项目扩展与演进
10.1 微服务化改造
- 服务拆分方案:
code复制agri-lease-gateway # API网关
agri-lease-auth # 认证服务
agri-lease-equipment # 设备管理服务
agri-lease-order # 订单服务
agri-lease-payment # 支付服务
- Spring Cloud集成:
java复制// 服务注册与发现
@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
// Feign客户端示例
@FeignClient(name = "equipment-service")
public interface EquipmentClient {
@GetMapping("/api/internal/equipment/{id}")
Equipment getEquipmentById(@PathVariable Long id);
}
10.2 移动端适配方案
- 响应式布局调整:
vue复制<template>
<el-container :class="{ 'mobile-layout': isMobile }">
<el-header v-if="!isMobile">PC端导航</el-header>
<el-main>
<component :is="mobileComponent" v-if="isMobile" />
<component :is="desktopComponent" v-else />
</el-main>
</el-container>
</template>
<script>
export default {
computed: {
isMobile() {
return window.innerWidth < 768
}
}
}
</script>
- PWA支持:
javascript复制// vue.config.js
module.exports = {
pwa: {
name: '农业设备租赁',
themeColor: '#4DBA87',
workboxOptions: {
skipWaiting: true
}
}
}
在实际部署中,我们遇到的最棘手问题是农忙季节的突发流量导致数据库连接池耗尽。最终通过以下措施解决:
- 使用HikariCP替代默认连接池,优化配置参数
- 对查询接口增加二级缓存
- 实施读写分离架构
- 引入Sentinel进行流量控制
系统上线后,平均响应时间从最初的1200ms降低到350ms,高峰期订单处理能力提升3倍。这个案例让我深刻体会到:农业信息化系统必须特别关注季节性流量特征,在架构设计阶段就要预留弹性扩展能力。
