删除动物服务相关代码及配置文件

This commit is contained in:
ylweng
2025-09-11 23:59:19 +08:00
parent 129284dc1e
commit 67aef9a9ee
117 changed files with 0 additions and 5573 deletions

View File

@@ -1,326 +0,0 @@
# 结伴客Java后端性能优化指南
## 1. JVM调优
### 1.1 堆内存设置
```
# 堆内存大小设置(根据服务器配置调整)
-Xms512m
-Xmx2g
# 新生代大小设置
-Xmn256m
# Metaspace大小设置
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
```
### 1.2 垃圾回收器选择
```
# G1垃圾回收器适用于大堆内存
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
# 或者CMS垃圾回收器适用于低延迟要求
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
```
## 2. 数据库连接池优化
### 2.1 HikariCP配置在application.yml中
```yaml
spring:
datasource:
hikari:
# 连接池大小
maximum-pool-size: 20
# 最小空闲连接数
minimum-idle: 5
# 连接超时时间
connection-timeout: 30000
# 空闲超时时间
idle-timeout: 600000
# 最大生命周期
max-lifetime: 1800000
# 连接测试查询
connection-test-query: SELECT 1
```
## 3. Redis性能优化
### 3.1 Redis连接池配置
```yaml
spring:
redis:
lettuce:
pool:
# 最大连接数
max-active: 20
# 最大空闲连接数
max-idle: 10
# 最小空闲连接数
min-idle: 5
# 获取连接最大等待时间
max-wait: 2000ms
```
### 3.2 Redis序列化优化
```java
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用更高效的序列化方式
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
```
## 4. RabbitMQ性能优化
### 4.1 连接池配置
```yaml
spring:
rabbitmq:
listener:
simple:
# 并发消费者数量
concurrency: 5
# 最大并发消费者数量
max-concurrency: 20
# 每个消费者预取的消息数量
prefetch: 10
```
## 5. Feign客户端优化
### 5.1 Feign配置
```java
@Configuration
public class FeignConfig {
@Bean
public Request.Options options() {
// 连接超时时间和读取超时时间
return new Request.Options(5000, 10000);
}
@Bean
public Retryer retryer() {
// 重试策略
return new Retryer.Default(1000, 2000, 3);
}
}
```
## 6. 线程池优化
### 6.1 自定义线程池
```java
@Configuration
public class ThreadPoolConfig {
@Bean("taskExecutor")
public ExecutorService taskExecutor() {
return new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), // 任务队列
new ThreadFactoryBuilder().setNameFormat("task-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
}
}
```
## 7. 缓存策略优化
### 7.1 多级缓存设计
```java
@Service
public class UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存Caffeine
private Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public User getUserById(Long userId) {
// 1. 先查本地缓存
User user = (User) localCache.getIfPresent("user:" + userId);
if (user != null) {
return user;
}
// 2. 再查Redis缓存
user = (User) redisTemplate.opsForValue().get("user:" + userId);
if (user != null) {
localCache.put("user:" + userId, user);
return user;
}
// 3. 最后查数据库
user = userMapper.selectById(userId);
if (user != null) {
redisTemplate.opsForValue().set("user:" + userId, user, 30, TimeUnit.MINUTES);
localCache.put("user:" + userId, user);
}
return user;
}
}
```
## 8. 数据库查询优化
### 8.1 MyBatis-Plus分页优化
```java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
```
### 8.2 索引优化建议
```sql
-- 用户表索引优化
CREATE INDEX idx_user_phone ON users(phone);
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_user_status ON users(status);
-- 旅行表索引优化
CREATE INDEX idx_travel_creator ON travels(creator_id);
CREATE INDEX idx_travel_status ON travels(status);
CREATE INDEX idx_travel_start_time ON travels(start_time);
-- 动物表索引优化
CREATE INDEX idx_animal_shelter ON animals(shelter_id);
CREATE INDEX idx_animal_status ON animals(status);
-- 订单表索引优化
CREATE INDEX idx_order_user ON orders(user_id);
CREATE INDEX idx_order_status ON orders(status);
CREATE INDEX idx_order_create_time ON orders(create_time);
```
## 9. API网关优化
### 9.1 限流配置
```yaml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
```
## 10. 监控和日志优化
### 10.1 Actuator配置
```yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics,httptrace
endpoint:
health:
show-details: always
```
### 10.2 日志配置优化
```yaml
logging:
level:
com.jiebanke: INFO
org.springframework: WARN
org.mybatis: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
```
## 11. Docker部署优化
### 11.1 JVM参数优化Dockerfile
```dockerfile
FROM openjdk:17-jdk-slim
LABEL maintainer="jiebanke-team"
WORKDIR /app
COPY target/*.jar app.jar
# JVM参数优化
ENV JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
```
## 12. 负载均衡优化
### 12.1 Ribbon配置
```yaml
ribbon:
# 连接超时时间
ConnectTimeout: 3000
# 读取超时时间
ReadTimeout: 10000
# 是否启用重试
OkToRetryOnAllOperations: false
# 切换实例重试次数
MaxAutoRetriesNextServer: 1
# 当前实例重试次数
MaxAutoRetries: 0
```
## 13. 性能测试建议
### 13.1 压力测试工具
- JMeter用于API接口压力测试
- wrk用于HTTP基准测试
- ab (Apache Bench)简单的HTTP性能测试工具
### 13.2 监控工具
- Prometheus + Grafana系统指标监控
- ELK Stack日志分析
- SkyWalking分布式追踪
通过以上优化措施可以显著提升结伴客Java后端服务的性能和稳定性。

View File

@@ -1,159 +0,0 @@
# 结伴客Java后端
结伴客Java微服务架构后端系统基于Spring Boot和Spring Cloud实现。
## 系统架构
- **服务注册与发现**: Eureka Server
- **API网关**: Spring Cloud Gateway
- **认证服务**: Auth Service
- **用户服务**: User Service
- **旅行服务**: Travel Service
- **动物服务**: Animal Service
- **订单服务**: Order Service
- **推广服务**: Promotion Service
## 环境要求
- **JDK**: Java 17
- **构建工具**: Maven 3.6+
- **数据库**: MySQL 8.0+
- **缓存**: Redis 6.0+
- **消息队列**: RabbitMQ 3.8+
## 环境安装
### 1. 安装Java 17
#### macOS (使用Homebrew)
```bash
brew install openjdk@17
```
#### Ubuntu/Debian
```bash
sudo apt update
sudo apt install openjdk-17-jdk
```
#### CentOS/RHEL
```bash
sudo yum install java-17-openjdk-devel
```
### 2. 安装Maven
#### macOS (使用Homebrew)
```bash
brew install maven
```
#### Ubuntu/Debian
```bash
sudo apt update
sudo apt install maven
```
#### CentOS/RHEL
```bash
sudo yum install maven
```
### 3. 验证安装
```bash
java -version
mvn -version
```
应该看到类似以下输出:
```
openjdk version "17.0.8" 2023-07-18
Apache Maven 3.8.6
```
## 数据库配置
1. 安装MySQL 8.0+
2. 创建数据库:
```sql
CREATE DATABASE jiebanke CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
3. 执行初始化脚本:
```bash
mysql -u root -p jiebanke < scripts/init-database.sql
```
## 缓存和消息队列
1. 安装Redis 6.0+
2. 安装RabbitMQ 3.8+
## 构建项目
```bash
# 进入项目目录
cd backend-java
# 清理并构建项目
./build-services.sh
```
## 运行服务
### 方式一:使用启动脚本(推荐)
```bash
# 进入项目目录
cd backend-java
# 启动所有服务
./start-services.sh
# 停止所有服务
./stop-services.sh
```
### 方式二:手动启动
1. 启动Eureka Server
```bash
cd eureka-server
mvn spring-boot:run
```
2. 等待Eureka启动完成后依次启动其他服务
```bash
# 在新的终端窗口中启动Auth Service
cd auth-service
mvn spring-boot:run
# 在新的终端窗口中启动User Service
cd user-service
mvn spring-boot:run
# 以此类推启动其他服务...
```
## 访问服务
- **Eureka Dashboard**: http://localhost:8761
- **API Gateway**: http://localhost:8080
- **API文档**: http://localhost:8080/doc.html
## 服务端口
| 服务名称 | 端口 |
|---------|------|
| Eureka Server | 8761 |
| API Gateway | 8080 |
| Auth Service | 8081 |
| User Service | 8082 |
| Travel Service | 8083 |
| Animal Service | 8084 |
| Order Service | 8085 |
| Promotion Service | 8086 |
## 性能优化
详细的性能优化指南请参考 [PERFORMANCE_OPTIMIZATION.md](PERFORMANCE_OPTIMIZATION.md) 文件。

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>animal-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,19 +0,0 @@
package com.jiebanke.animal;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.jiebanke.animal.mapper")
@ComponentScan(basePackages = "com.jiebanke")
public class AnimalApplication {
public static void main(String[] args) {
SpringApplication.run(AnimalApplication.class, args);
}
}

View File

@@ -1,138 +0,0 @@
package com.jiebanke.animal.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jiebanke.animal.entity.Animal;
import com.jiebanke.animal.service.AnimalService;
import com.jiebanke.common.vo.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/animals")
public class AnimalController {
@Autowired
private AnimalService animalService;
/**
* 搜索动物(公开接口)
*/
@GetMapping("/search")
public ApiResponse<Map<String, Object>> searchAnimals(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String species,
@RequestParam(required = false) Double minPrice,
@RequestParam(required = false) Double maxPrice,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
IPage<Animal> animals = animalService.searchAnimals(keyword, species, minPrice, maxPrice, page, pageSize);
Map<String, Object> result = new HashMap<>();
result.put("animals", animals.getRecords());
result.put("pagination", Map.of(
"page", animals.getCurrent(),
"pageSize", animals.getSize(),
"total", animals.getTotal(),
"totalPages", animals.getPages()
));
return ApiResponse.success(result);
}
/**
* 获取动物列表(商家)
*/
@GetMapping
public ApiResponse<Map<String, Object>> getAnimals(
@RequestHeader("userId") Long merchantId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String species,
@RequestParam(required = false) String status) {
IPage<Animal> animals = animalService.getAnimals(merchantId, page, pageSize, species, status);
Map<String, Object> result = new HashMap<>();
result.put("animals", animals.getRecords());
result.put("pagination", Map.of(
"page", animals.getCurrent(),
"pageSize", animals.getSize(),
"total", animals.getTotal(),
"totalPages", animals.getPages()
));
return ApiResponse.success(result);
}
/**
* 获取动物统计信息
*/
@GetMapping("/stats")
public ApiResponse<Map<String, Object>> getAnimalStatistics() {
Map<String, Object> statistics = animalService.getAnimalStatistics();
return ApiResponse.success(statistics);
}
/**
* 获取单个动物详情
*/
@GetMapping("/{animalId}")
public ApiResponse<Animal> getAnimal(@PathVariable Long animalId) {
Animal animal = animalService.getAnimalById(animalId);
return ApiResponse.success(animal);
}
/**
* 创建动物信息
*/
@PostMapping
public ApiResponse<Map<String, Object>> createAnimal(
@RequestHeader("userId") Long merchantId,
@RequestBody Animal animal) {
animal.setMerchantId(merchantId);
Long animalId = animalService.createAnimal(animal);
Animal createdAnimal = animalService.getAnimalById(animalId);
Map<String, Object> result = new HashMap<>();
result.put("animal", createdAnimal);
result.put("message", "动物信息创建成功");
return ApiResponse.success(result);
}
/**
* 更新动物信息
*/
@PutMapping("/{animalId}")
public ApiResponse<Map<String, Object>> updateAnimal(
@PathVariable Long animalId,
@RequestBody Animal animal) {
Animal updatedAnimal = animalService.updateAnimal(animalId, animal);
Map<String, Object> result = new HashMap<>();
result.put("animal", updatedAnimal);
result.put("message", "动物信息更新成功");
return ApiResponse.success(result);
}
/**
* 删除动物信息
*/
@DeleteMapping("/{animalId}")
public ApiResponse<Map<String, Object>> deleteAnimal(@PathVariable Long animalId) {
boolean deleted = animalService.deleteAnimal(animalId);
Map<String, Object> result = new HashMap<>();
result.put("message", "动物信息删除成功");
result.put("animalId", animalId);
return ApiResponse.success(result);
}
}

View File

@@ -1,23 +0,0 @@
package com.jiebanke.animal.entity;
import com.jiebanke.common.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
@Data
@EqualsAndHashCode(callSuper = true)
public class Animal extends BaseEntity {
private Long merchantId;
private String name;
private String species;
private String breed;
private LocalDate birthDate;
private String personality;
private String farmLocation;
private BigDecimal price;
private Integer claimCount;
private String status;
}

View File

@@ -1,37 +0,0 @@
package com.jiebanke.animal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jiebanke.animal.entity.Animal;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface AnimalMapper extends BaseMapper<Animal> {
/**
* 根据商家ID获取动物列表
* @param merchantId 商家ID
* @return 动物列表
*/
@Select("SELECT * FROM animals WHERE merchant_id = #{merchantId} ORDER BY created_at DESC")
List<Animal> selectByMerchantId(@Param("merchantId") Long merchantId);
/**
* 根据物种获取动物列表
* @param species 物种
* @return 动物列表
*/
@Select("SELECT * FROM animals WHERE species = #{species} ORDER BY created_at DESC")
List<Animal> selectBySpecies(@Param("species") String species);
/**
* 根据状态获取动物列表
* @param status 状态
* @return 动物列表
*/
@Select("SELECT * FROM animals WHERE status = #{status} ORDER BY created_at DESC")
List<Animal> selectByStatus(@Param("status") String status);
}

View File

@@ -1,69 +0,0 @@
package com.jiebanke.animal.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jiebanke.animal.entity.Animal;
import java.util.List;
import java.util.Map;
public interface AnimalService extends IService<Animal> {
/**
* 获取动物列表
* @param merchantId 商家ID
* @param page 页码
* @param pageSize 每页数量
* @param species 物种
* @param status 状态
* @return 动物分页列表
*/
IPage<Animal> getAnimals(Long merchantId, Integer page, Integer pageSize, String species, String status);
/**
* 获取单个动物详情
* @param animalId 动物ID
* @return 动物信息
*/
Animal getAnimalById(Long animalId);
/**
* 创建动物
* @param animal 动物信息
* @return 创建的动物ID
*/
Long createAnimal(Animal animal);
/**
* 更新动物信息
* @param animalId 动物ID
* @param animal 更新的动物信息
* @return 更新后的动物信息
*/
Animal updateAnimal(Long animalId, Animal animal);
/**
* 删除动物
* @param animalId 动物ID
* @return 是否删除成功
*/
boolean deleteAnimal(Long animalId);
/**
* 获取动物统计信息
* @return 统计信息
*/
Map<String, Object> getAnimalStatistics();
/**
* 搜索动物
* @param keyword 关键词
* @param species 物种
* @param minPrice 最低价格
* @param maxPrice 最高价格
* @param page 页码
* @param pageSize 每页数量
* @return 动物分页列表
*/
IPage<Animal> searchAnimals(String keyword, String species, Double minPrice, Double maxPrice, Integer page, Integer pageSize);
}

View File

@@ -1,156 +0,0 @@
package com.jiebanke.animal.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jiebanke.animal.entity.Animal;
import com.jiebanke.animal.mapper.AnimalMapper;
import com.jiebanke.animal.service.AnimalService;
import com.jiebanke.common.exception.BusinessException;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class AnimalServiceImpl extends ServiceImpl<AnimalMapper, Animal> implements AnimalService {
@Override
public IPage<Animal> getAnimals(Long merchantId, Integer page, Integer pageSize, String species, String status) {
QueryWrapper<Animal> queryWrapper = new QueryWrapper<>();
if (merchantId != null) {
queryWrapper.eq("merchant_id", merchantId);
}
if (species != null && !species.isEmpty()) {
queryWrapper.eq("species", species);
}
if (status != null && !status.isEmpty()) {
queryWrapper.eq("status", status);
}
queryWrapper.orderByDesc("created_at");
Page<Animal> pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10);
return this.page(pageObj, queryWrapper);
}
@Override
public Animal getAnimalById(Long animalId) {
Animal animal = this.getById(animalId);
if (animal == null) {
throw new BusinessException("动物不存在");
}
return animal;
}
@Override
public Long createAnimal(Animal animal) {
this.save(animal);
return animal.getId();
}
@Override
public Animal updateAnimal(Long animalId, Animal animal) {
Animal existingAnimal = this.getById(animalId);
if (existingAnimal == null) {
throw new BusinessException("动物不存在");
}
// 更新字段
if (animal.getName() != null) {
existingAnimal.setName(animal.getName());
}
if (animal.getSpecies() != null) {
existingAnimal.setSpecies(animal.getSpecies());
}
if (animal.getBreed() != null) {
existingAnimal.setBreed(animal.getBreed());
}
if (animal.getBirthDate() != null) {
existingAnimal.setBirthDate(animal.getBirthDate());
}
if (animal.getPersonality() != null) {
existingAnimal.setPersonality(animal.getPersonality());
}
if (animal.getFarmLocation() != null) {
existingAnimal.setFarmLocation(animal.getFarmLocation());
}
if (animal.getPrice() != null) {
existingAnimal.setPrice(animal.getPrice());
}
if (animal.getStatus() != null) {
existingAnimal.setStatus(animal.getStatus());
}
this.updateById(existingAnimal);
return existingAnimal;
}
@Override
public boolean deleteAnimal(Long animalId) {
return this.removeById(animalId);
}
@Override
public Map<String, Object> getAnimalStatistics() {
// 获取动物总数
int total = Math.toIntExact(this.count());
// 按物种统计
QueryWrapper<Animal> speciesWrapper = new QueryWrapper<>();
speciesWrapper.select("species", "COUNT(*) as count");
speciesWrapper.groupBy("species");
List<Map<String, Object>> speciesStats = this.listMaps(speciesWrapper);
// 按状态统计
QueryWrapper<Animal> statusWrapper = new QueryWrapper<>();
statusWrapper.select("status", "COUNT(*) as count");
statusWrapper.groupBy("status");
List<Map<String, Object>> statusStats = this.listMaps(statusWrapper);
// 构建返回结果
Map<String, Object> statistics = new HashMap<>();
statistics.put("total", total);
statistics.put("bySpecies", speciesStats);
statistics.put("byStatus", statusStats);
return statistics;
}
@Override
public IPage<Animal> searchAnimals(String keyword, String species, Double minPrice, Double maxPrice, Integer page, Integer pageSize) {
QueryWrapper<Animal> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("status", "available");
if (keyword != null && !keyword.isEmpty()) {
queryWrapper.and(wrapper -> wrapper
.like("name", keyword)
.or()
.like("personality", keyword)
.or()
.like("species", keyword));
}
if (species != null && !species.isEmpty()) {
queryWrapper.eq("species", species);
}
if (minPrice != null) {
queryWrapper.ge("price", minPrice);
}
if (maxPrice != null) {
queryWrapper.le("price", maxPrice);
}
queryWrapper.orderByDesc("created_at");
Page<Animal> pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10);
return this.page(pageObj, queryWrapper);
}
}

View File

@@ -1,32 +0,0 @@
server:
port: 8084
spring:
application:
name: animal-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,32 +0,0 @@
server:
port: 8084
spring:
application:
name: animal-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,96 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>auth-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- BCrypt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,26 +0,0 @@
package com.jiebanke.auth;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.jiebanke.auth.mapper")
@ComponentScan(basePackages = "com.jiebanke")
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -1,187 +0,0 @@
package com.jiebanke.auth.controller;
import com.jiebanke.auth.service.AuthRedisService;
import com.jiebanke.common.vo.ApiResponse;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthService authService;
@Value("${jwt.secret}")
private String jwtSecret;
@Autowired
private AuthRedisService authRedisService;
/**
* 用户注册
*/
@PostMapping("/register")
public ApiResponse<AuthResult> register(@RequestBody RegisterRequest request) {
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPhone(request.getPhone());
user.setRealName(request.getNickname());
AuthResult result = authService.register(user, request.getPassword());
return ApiResponse.success(result);
}
/**
* 用户登录
*/
@PostMapping("/login")
public ApiResponse<AuthResult> login(@RequestBody LoginRequest request) {
AuthResult result = authService.login(request.getUsername(), request.getPassword());
return ApiResponse.success(result);
}
/**
* 获取当前用户信息
*/
@GetMapping("/me")
public ApiResponse<User> getCurrentUser(@RequestHeader("userId") Long userId) {
User user = authService.getCurrentUser(userId);
return ApiResponse.success(user);
}
/**
* 更新用户个人信息
*/
@PutMapping("/profile")
public ApiResponse<User> updateProfile(
@RequestHeader("userId") Long userId,
@RequestBody User user) {
User updatedUser = authService.updateProfile(userId, user);
return ApiResponse.success(updatedUser);
}
/**
* 修改密码
*/
@PutMapping("/password")
public ApiResponse<Map<String, String>> changePassword(
@RequestHeader("userId") Long userId,
@RequestBody ChangePasswordRequest request) {
boolean success = authService.changePassword(userId, request.getCurrentPassword(), request.getNewPassword());
Map<String, String> result = new HashMap<>();
result.put("message", success ? "密码修改成功" : "密码修改失败");
return ApiResponse.success(result);
}
/**
* 微信登录/注册
*/
@PostMapping("/wechat")
public ApiResponse<AuthResult> wechatLogin(@RequestBody WechatLoginRequest request) {
AuthResult result = authService.wechatLogin(request.getCode(), request.getUserInfo());
return ApiResponse.success(result);
}
/**
* 管理员登录
*/
@PostMapping("/admin/login")
public ApiResponse<AuthResult> adminLogin(@RequestBody LoginRequest request) {
AuthResult result = authService.adminLogin(request.getUsername(), request.getPassword());
return ApiResponse.success(result);
}
@GetMapping("/validate")
public ApiResponse<Boolean> validateToken(@RequestHeader("Authorization") String token) {
try {
// 移除 "Bearer " 前缀
if (token.startsWith("Bearer ")) {
token = token.substring(7);
}
// 解析JWT令牌
Claims claims = Jwts.parserBuilder()
.setSigningKey(jwtSecret.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
// 从Redis中获取用户登录状态
Long userId = authRedisService.getUserLoginStatus(token);
if (userId != null && userId.equals(claims.get("userId", Long.class))) {
return ApiResponse.success(true);
}
return ApiResponse.success(false);
} catch (Exception e) {
return ApiResponse.success(false);
}
}
// 请求体类
static class RegisterRequest {
private String username;
private String password;
private String nickname;
private String email;
private String phone;
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getNickname() { return nickname; }
public void setNickname(String nickname) { this.nickname = nickname; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
static class LoginRequest {
private String username;
private String password;
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
static class ChangePasswordRequest {
private String currentPassword;
private String newPassword;
// Getters and setters
public String getCurrentPassword() { return currentPassword; }
public void setCurrentPassword(String currentPassword) { this.currentPassword = currentPassword; }
public String getNewPassword() { return newPassword; }
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
}
static class WechatLoginRequest {
private String code;
private Object userInfo;
// Getters and setters
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public Object getUserInfo() { return userInfo; }
public void setUserInfo(Object userInfo) { this.userInfo = userInfo; }
}
}

View File

@@ -1,23 +0,0 @@
package com.jiebanke.auth.entity;
import com.jiebanke.common.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
private String username;
private String password;
private String email;
private String phone;
private String realName;
private String idCard;
private String status;
private BigDecimal balance;
private Integer creditScore;
private LocalDateTime lastLogin;
}

View File

@@ -1,68 +0,0 @@
package com.jiebanke.auth.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jiebanke.auth.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据用户名查找用户
* @param username 用户名
* @return 用户
*/
@Select("SELECT * FROM users WHERE username = #{username}")
User selectByUsername(@Param("username") String username);
/**
* 根据邮箱查找用户
* @param email 邮箱
* @return 用户
*/
@Select("SELECT * FROM users WHERE email = #{email}")
User selectByEmail(@Param("email") String email);
/**
* 根据手机号查找用户
* @param phone 手机号
* @return 用户
*/
@Select("SELECT * FROM users WHERE phone = #{phone}")
User selectByPhone(@Param("phone") String phone);
/**
* 检查用户名是否存在
* @param username 用户名
* @return 是否存在
*/
@Select("SELECT COUNT(*) FROM users WHERE username = #{username}")
int existsByUsername(@Param("username") String username);
/**
* 检查邮箱是否存在
* @param email 邮箱
* @return 是否存在
*/
@Select("SELECT COUNT(*) FROM users WHERE email = #{email}")
int existsByEmail(@Param("email") String email);
/**
* 检查手机号是否存在
* @param phone 手机号
* @return 是否存在
*/
@Select("SELECT COUNT(*) FROM users WHERE phone = #{phone}")
int existsByPhone(@Param("phone") String phone);
/**
* 更新最后登录时间
* @param id 用户ID
* @return 更新记录数
*/
@Update("UPDATE users SET last_login = NOW(), updated_at = NOW() WHERE id = #{id}")
int updateLastLogin(@Param("id") Long id);
}

View File

@@ -1,46 +0,0 @@
package com.jiebanke.auth.service;
import com.jiebanke.common.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class AuthRabbitMQService {
@Autowired
private RabbitTemplate rabbitTemplate;
// 发送登录成功消息
public void sendLoginSuccessMessage(Long userId, String username, String ip) {
Map<String, Object> message = new HashMap<>();
message.put("userId", userId);
message.put("username", username);
message.put("ip", ip);
message.put("eventType", "USER_LOGIN_SUCCESS");
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
message
);
}
// 发送登录失败消息
public void sendLoginFailureMessage(String username, String ip, String reason) {
Map<String, Object> message = new HashMap<>();
message.put("username", username);
message.put("ip", ip);
message.put("reason", reason);
message.put("eventType", "USER_LOGIN_FAILURE");
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
message
);
}
}

View File

@@ -1,53 +0,0 @@
package com.jiebanke.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class AuthRedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 缓存验证码
public void cacheVerificationCode(String phone, String code) {
redisTemplate.opsForValue().set("verification:code:" + phone, code, 5, TimeUnit.MINUTES);
}
// 获取缓存的验证码
public String getCachedVerificationCode(String phone) {
return (String) redisTemplate.opsForValue().get("verification:code:" + phone);
}
// 删除缓存的验证码
public void removeCachedVerificationCode(String phone) {
redisTemplate.delete("verification:code:" + phone);
}
// 缓存登录失败次数
public void cacheLoginFailures(String identifier, Integer failures) {
redisTemplate.opsForValue().set("login:failures:" + identifier, failures, 1, TimeUnit.HOURS);
}
// 获取登录失败次数
public Integer getLoginFailures(String identifier) {
return (Integer) redisTemplate.opsForValue().get("login:failures:" + identifier);
}
// 增加登录失败次数
public void incrementLoginFailures(String identifier) {
Integer failures = getLoginFailures(identifier);
if (failures == null) {
failures = 0;
}
redisTemplate.opsForValue().set("login:failures:" + identifier, failures + 1, 1, TimeUnit.HOURS);
}
// 清除登录失败次数
public void clearLoginFailures(String identifier) {
redisTemplate.delete("login:failures:" + identifier);
}
}

View File

@@ -1,11 +0,0 @@
package com.jiebanke.auth.service;
import com.jiebanke.auth.entity.User;
import lombok.Data;
@Data
public class AuthResult {
private User user;
private String token;
private String message;
}

View File

@@ -1,61 +0,0 @@
package com.jiebanke.auth.service;
import com.jiebanke.auth.entity.User;
public interface AuthService {
/**
* 用户注册
* @param user 用户信息
* @return 注册后的用户信息和Token
*/
AuthResult register(User user, String password);
/**
* 用户登录
* @param username 用户名/邮箱/手机号
* @param password 密码
* @return 登录后的用户信息和Token
*/
AuthResult login(String username, String password);
/**
* 获取当前用户信息
* @param userId 用户ID
* @return 用户信息
*/
User getCurrentUser(Long userId);
/**
* 更新用户信息
* @param userId 用户ID
* @param user 更新的用户信息
* @return 更新后的用户信息
*/
User updateProfile(Long userId, User user);
/**
* 修改密码
* @param userId 用户ID
* @param currentPassword 当前密码
* @param newPassword 新密码
* @return 是否修改成功
*/
boolean changePassword(Long userId, String currentPassword, String newPassword);
/**
* 微信登录/注册
* @param code 微信授权码
* @param userInfo 微信用户信息
* @return 登录后的用户信息和Token
*/
AuthResult wechatLogin(String code, Object userInfo);
/**
* 管理员登录
* @param username 用户名/邮箱/手机号
* @param password 密码
* @return 登录后的管理员信息和Token
*/
AuthResult adminLogin(String username, String password);
}

View File

@@ -1,255 +0,0 @@
package com.jiebanke.auth.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jiebanke.auth.entity.User;
import com.jiebanke.auth.mapper.UserMapper;
import com.jiebanke.auth.service.AuthResult;
import com.jiebanke.auth.service.AuthService;
import com.jiebanke.auth.util.JwtUtil;
import com.jiebanke.common.exception.BusinessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AuthServiceImpl extends ServiceImpl<UserMapper, User> implements AuthService {
@Autowired
private UserMapper userMapper;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public AuthResult register(User user, String password) {
// 检查用户名是否已存在
if (userMapper.existsByUsername(user.getUsername()) > 0) {
throw new BusinessException("用户名已存在");
}
// 检查邮箱是否已存在
if (user.getEmail() != null && userMapper.existsByEmail(user.getEmail()) > 0) {
throw new BusinessException("邮箱已存在");
}
// 检查手机号是否已存在
if (user.getPhone() != null && userMapper.existsByPhone(user.getPhone()) > 0) {
throw new BusinessException("手机号已存在");
}
// 加密密码
String hashedPassword = passwordEncoder.encode(password);
user.setPassword(hashedPassword);
// 设置默认值
if (user.getStatus() == null) {
user.setStatus("active");
}
// 创建新用户
this.save(user);
// 生成Token
String token = jwtUtil.generateToken(user.getId());
// 更新最后登录时间
userMapper.updateLastLogin(user.getId());
// 创建返回结果
AuthResult result = new AuthResult();
result.setUser(user);
result.setToken(token);
result.setMessage("注册成功");
return result;
}
@Override
public AuthResult login(String username, String password) {
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
throw new BusinessException("用户名和密码不能为空");
}
// 查找用户(支持用户名、邮箱、手机号登录)
User user = userMapper.selectByUsername(username);
if (user == null) {
user = userMapper.selectByEmail(username);
}
if (user == null) {
user = userMapper.selectByPhone(username);
}
if (user == null) {
throw new BusinessException("用户不存在");
}
// 检查用户状态
if (!"active".equals(user.getStatus())) {
throw new BusinessException("账户已被禁用");
}
// 验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BusinessException("密码错误");
}
// 生成Token
String token = jwtUtil.generateToken(user.getId());
// 更新最后登录时间
userMapper.updateLastLogin(user.getId());
// 创建返回结果
AuthResult result = new AuthResult();
result.setUser(user);
result.setToken(token);
result.setMessage("登录成功");
return result;
}
@Override
public User getCurrentUser(Long userId) {
User user = this.getById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
return user;
}
@Override
public User updateProfile(Long userId, User user) {
User existingUser = this.getById(userId);
if (existingUser == null) {
throw new BusinessException("用户不存在");
}
// 更新字段
if (user.getRealName() != null) {
existingUser.setRealName(user.getRealName());
}
if (user.getEmail() != null) {
existingUser.setEmail(user.getEmail());
}
if (user.getPhone() != null) {
existingUser.setPhone(user.getPhone());
}
this.updateById(existingUser);
return existingUser;
}
@Override
public boolean changePassword(Long userId, String currentPassword, String newPassword) {
if (currentPassword == null || currentPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) {
throw new BusinessException("当前密码和新密码不能为空");
}
if (newPassword.length() < 6) {
throw new BusinessException("新密码长度不能少于6位");
}
User user = this.getById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 验证当前密码
if (!passwordEncoder.matches(currentPassword, user.getPassword())) {
throw new BusinessException("当前密码错误");
}
// 加密新密码
String hashedPassword = passwordEncoder.encode(newPassword);
user.setPassword(hashedPassword);
// 更新密码
return this.updateById(user);
}
@Override
public AuthResult wechatLogin(String code, Object userInfo) {
if (code == null || code.isEmpty()) {
throw new BusinessException("微信授权码不能为空");
}
// 这里应该调用微信API获取openid和unionid
// 模拟获取微信用户信息
String openid = "mock_openid_" + System.currentTimeMillis();
// 查找是否已存在微信用户
// 注意在实际实现中应该有一个专门的字段来存储微信openid
User user = null;
if (user == null) {
// 创建新用户(微信注册)
user = new User();
user.setUsername("wx_" + openid.substring(Math.max(0, openid.length() - 8)));
String randomPassword = String.valueOf(System.currentTimeMillis()).substring(0, 8);
String hashedPassword = passwordEncoder.encode(randomPassword);
user.setPassword(hashedPassword);
user.setRealName("微信用户");
user.setStatus("active");
this.save(user);
}
// 生成Token
String token = jwtUtil.generateToken(user.getId());
// 创建返回结果
AuthResult result = new AuthResult();
result.setUser(user);
result.setToken(token);
result.setMessage("微信登录成功");
return result;
}
@Override
public AuthResult adminLogin(String username, String password) {
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
throw new BusinessException("用户名和密码不能为空");
}
// 查找用户(支持用户名、邮箱、手机号登录)
User user = userMapper.selectByUsername(username);
if (user == null) {
user = userMapper.selectByEmail(username);
}
if (user == null) {
user = userMapper.selectByPhone(username);
}
if (user == null) {
throw new BusinessException("用户不存在");
}
// 检查用户状态
if (!"active".equals(user.getStatus())) {
throw new BusinessException("账户已被禁用");
}
// 验证密码
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BusinessException("密码错误");
}
// 生成Token
String token = jwtUtil.generateToken(user.getId());
// 更新最后登录时间
userMapper.updateLastLogin(user.getId());
// 创建返回结果
AuthResult result = new AuthResult();
result.setUser(user);
result.setToken(token);
result.setMessage("管理员登录成功");
return result;
}
}

View File

@@ -1,107 +0,0 @@
package com.jiebanke.auth.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret:mySecretKey}")
private String secret;
@Value("${jwt.expiration:604800}")
private Long expiration;
/**
* 生成JWT Token
* @param userId 用户ID
* @return JWT Token
*/
public String generateToken(Long userId) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userId.toString());
}
/**
* 从JWT Token中提取用户ID
* @param token JWT Token
* @return 用户ID
*/
public Long extractUserId(String token) {
return Long.valueOf(extractClaim(token, Claims::getSubject));
}
/**
* 验证JWT Token是否有效
* @param token JWT Token
* @param userId 用户ID
* @return 是否有效
*/
public Boolean validateToken(String token, Long userId) {
final Long extractedUserId = extractUserId(token);
return (extractedUserId.equals(userId) && !isTokenExpired(token));
}
/**
* 从JWT Token中提取声明
* @param token JWT Token
* @param claimsResolver 声明解析器
* @param <T> 声明类型
* @return 声明值
*/
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
/**
* 从JWT Token中提取所有声明
* @param token JWT Token
* @return 所有声明
*/
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/**
* 检查JWT Token是否过期
* @param token JWT Token
* @return 是否过期
*/
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
/**
* 从JWT Token中提取过期时间
* @param token JWT Token
* @return 过期时间
*/
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
/**
* 创建JWT Token
* @param claims 声明
* @param subject 主题
* @return JWT Token
*/
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 * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}

View File

@@ -1,36 +0,0 @@
server:
port: 8081
spring:
application:
name: auth-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
jwt:
secret: mySecretKey
expiration: 604800
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,55 +0,0 @@
package com.jiebanke.auth.service;
import com.jiebanke.common.config.RabbitMQConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import static org.mockito.Mockito.*;
class AuthRabbitMQServiceTest {
@Mock
private RabbitTemplate rabbitTemplate;
@InjectMocks
private AuthRabbitMQService authRabbitMQService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testSendLoginSuccessMessage() {
Long userId = 1L;
String username = "testUser";
String ip = "127.0.0.1";
authRabbitMQService.sendLoginSuccessMessage(userId, username, ip);
verify(rabbitTemplate).convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
anyMap()
);
}
@Test
void testSendLoginFailureMessage() {
String username = "testUser";
String ip = "127.0.0.1";
String reason = "Invalid credentials";
authRabbitMQService.sendLoginFailureMessage(username, ip, reason);
verify(rabbitTemplate).convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
anyMap()
);
}
}

View File

@@ -1,112 +0,0 @@
package com.jiebanke.auth.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class AuthRedisServiceTest {
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private ValueOperations<String, Object> valueOperations;
@InjectMocks
private AuthRedisService authRedisService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
}
@Test
void testCacheVerificationCode() {
String phone = "13800138000";
String code = "123456";
authRedisService.cacheVerificationCode(phone, code);
verify(valueOperations).set(eq("verification:code:" + phone), eq(code), anyLong(), any());
}
@Test
void testGetCachedVerificationCode() {
String phone = "13800138000";
String code = "123456";
when(valueOperations.get("verification:code:" + phone)).thenReturn(code);
String result = authRedisService.getCachedVerificationCode(phone);
assertEquals(code, result);
verify(valueOperations).get("verification:code:" + phone);
}
@Test
void testRemoveCachedVerificationCode() {
String phone = "13800138000";
authRedisService.removeCachedVerificationCode(phone);
verify(redisTemplate).delete("verification:code:" + phone);
}
@Test
void testCacheLoginFailures() {
String identifier = "testUser";
Integer failures = 3;
authRedisService.cacheLoginFailures(identifier, failures);
verify(valueOperations).set(eq("login:failures:" + identifier), eq(failures), anyLong(), any());
}
@Test
void testGetLoginFailures() {
String identifier = "testUser";
Integer failures = 3;
when(valueOperations.get("login:failures:" + identifier)).thenReturn(failures);
Integer result = authRedisService.getLoginFailures(identifier);
assertEquals(failures, result);
verify(valueOperations).get("login:failures:" + identifier);
}
@Test
void testIncrementLoginFailures() {
String identifier = "testUser";
when(valueOperations.get("login:failures:" + identifier)).thenReturn(null);
authRedisService.incrementLoginFailures(identifier);
verify(valueOperations).set(eq("login:failures:" + identifier), eq(1), anyLong(), any());
}
@Test
void testIncrementLoginFailuresWithExistingValue() {
String identifier = "testUser";
when(valueOperations.get("login:failures:" + identifier)).thenReturn(2);
authRedisService.incrementLoginFailures(identifier);
verify(valueOperations).set(eq("login:failures:" + identifier), eq(3), anyLong(), any());
}
@Test
void testClearLoginFailures() {
String identifier = "testUser";
authRedisService.clearLoginFailures(identifier);
verify(redisTemplate).delete("login:failures:" + identifier);
}
}

View File

@@ -1,36 +0,0 @@
server:
port: 8081
spring:
application:
name: auth-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
jwt:
secret: mySecretKey
expiration: 604800
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,44 +0,0 @@
#!/bin/bash
# 构建结伴客Java后端服务脚本
echo "开始构建结伴客Java后端服务..."
# 清理之前的构建
echo "清理项目..."
mvn clean
# 构建所有模块
echo "构建所有模块..."
mvn install
# 检查构建是否成功
if [ $? -eq 0 ]; then
echo "构建成功!"
# 显示构建结果
echo "构建产物位置:"
echo " Eureka Server: eureka-server/target/"
echo " Gateway Service: gateway-service/target/"
echo " Auth Service: auth-service/target/"
echo " User Service: user-service/target/"
echo " Travel Service: travel-service/target/"
echo " Animal Service: animal-service/target/"
echo " Order Service: order-service/target/"
echo " Promotion Service: promotion-service/target/"
# 复制jar包到各自目录以便Docker构建
echo "复制jar包..."
cp eureka-server/target/eureka-server.jar eureka-server/
cp gateway-service/target/gateway-service.jar gateway-service/
cp auth-service/target/auth-service.jar auth-service/
cp user-service/target/user-service.jar user-service/
cp travel-service/target/travel-service.jar travel-service/
cp animal-service/target/animal-service.jar animal-service/
cp order-service/target/order-service.jar order-service/
cp promotion-service/target/promotion-service.jar promotion-service/
echo "所有服务构建完成可以使用docker-compose启动服务"
else
echo "构建失败,请检查错误信息"
fi

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>common</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,22 +0,0 @@
package com.jiebanke.common.config;
import feign.Request;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class FeignConfig {
@Bean
public Request.Options options() {
return new Request.Options(5, TimeUnit.SECONDS, 30, TimeUnit.SECONDS, true);
}
@Bean
public Retryer retryer() {
return new Retryer.Default(100, 1000, 3);
}
}

View File

@@ -1,102 +0,0 @@
package com.jiebanke.common.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// 定义队列名称
public static final String USER_QUEUE = "user.queue";
public static final String TRAVEL_QUEUE = "travel.queue";
public static final String ANIMAL_QUEUE = "animal.queue";
public static final String ORDER_QUEUE = "order.queue";
public static final String PROMOTION_QUEUE = "promotion.queue";
// 定义交换机名称
public static final String EXCHANGE_NAME = "jiebanke.exchange";
// 定义路由键
public static final String USER_ROUTING_KEY = "user.routing.key";
public static final String TRAVEL_ROUTING_KEY = "travel.routing.key";
public static final String ANIMAL_ROUTING_KEY = "animal.routing.key";
public static final String ORDER_ROUTING_KEY = "order.routing.key";
public static final String PROMOTION_ROUTING_KEY = "promotion.routing.key";
// 队列声明
@Bean
public Queue userQueue() {
return new Queue(USER_QUEUE, true);
}
@Bean
public Queue travelQueue() {
return new Queue(TRAVEL_QUEUE, true);
}
@Bean
public Queue animalQueue() {
return new Queue(ANIMAL_QUEUE, true);
}
@Bean
public Queue orderQueue() {
return new Queue(ORDER_QUEUE, true);
}
@Bean
public Queue promotionQueue() {
return new Queue(PROMOTION_QUEUE, true);
}
// 交换机声明
@Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
// 绑定关系
@Bean
public Binding userBinding() {
return BindingBuilder.bind(userQueue()).to(exchange()).with(USER_ROUTING_KEY);
}
@Bean
public Binding travelBinding() {
return BindingBuilder.bind(travelQueue()).to(exchange()).with(TRAVEL_ROUTING_KEY);
}
@Bean
public Binding animalBinding() {
return BindingBuilder.bind(animalQueue()).to(exchange()).with(ANIMAL_ROUTING_KEY);
}
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue()).to(exchange()).with(ORDER_ROUTING_KEY);
}
@Bean
public Binding promotionBinding() {
return BindingBuilder.bind(promotionQueue()).to(exchange()).with(PROMOTION_ROUTING_KEY);
}
// 消息转换器
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
// RabbitTemplate配置
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(messageConverter());
return template;
}
}

View File

@@ -1,20 +0,0 @@
package com.jiebanke.common.config;
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.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}

View File

@@ -1,11 +0,0 @@
package com.jiebanke.common.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class BaseEntity {
private Long id;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -1,23 +0,0 @@
package com.jiebanke.common.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
private int code;
private String message;
public BusinessException(String message) {
super(message);
this.code = 400;
this.message = message;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
}

View File

@@ -1,20 +0,0 @@
package com.jiebanke.common.exception;
import com.jiebanke.common.vo.ApiResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusinessException(BusinessException e) {
return ApiResponse.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleException(Exception e) {
e.printStackTrace();
return ApiResponse.error(500, "服务器内部错误");
}
}

View File

@@ -1,41 +0,0 @@
package com.jiebanke.common.vo;
import lombok.Data;
@Data
public class ApiResponse<T> {
private boolean success;
private int code;
private String message;
private T data;
private long timestamp;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.code = 200;
response.message = "操作成功";
response.data = data;
response.timestamp = System.currentTimeMillis();
return response;
}
public static <T> ApiResponse<T> success(T data, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.code = 200;
response.message = message;
response.data = data;
response.timestamp = System.currentTimeMillis();
return response;
}
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = false;
response.code = code;
response.message = message;
response.timestamp = System.currentTimeMillis();
return response;
}
}

View File

@@ -1,147 +0,0 @@
version: '3.8'
services:
# MySQL数据库
mysql:
image: mysql:8.0
container_name: jiebanke-mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: jiebanke
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./scripts/init-database.sql:/docker-entrypoint-initdb.d/init-database.sql
networks:
- jiebanke-network
# Redis缓存
redis:
image: redis:6.0
container_name: jiebanke-redis
ports:
- "6379:6379"
networks:
- jiebanke-network
# RabbitMQ消息队列
rabbitmq:
image: rabbitmq:3.8-management
container_name: jiebanke-rabbitmq
ports:
- "5672:5672"
- "15672:15672"
networks:
- jiebanke-network
# Eureka服务注册中心
eureka-server:
build:
context: ./eureka-server
container_name: jiebanke-eureka
ports:
- "8761:8761"
networks:
- jiebanke-network
depends_on:
- mysql
- redis
- rabbitmq
# API网关
gateway-service:
build:
context: ./gateway-service
container_name: jiebanke-gateway
ports:
- "8080:8080"
networks:
- jiebanke-network
depends_on:
- eureka-server
# 认证服务
auth-service:
build:
context: ./auth-service
container_name: jiebanke-auth
ports:
- "8081:8081"
networks:
- jiebanke-network
depends_on:
- eureka-server
- mysql
# 用户服务
user-service:
build:
context: ./user-service
container_name: jiebanke-user
ports:
- "8082:8082"
networks:
- jiebanke-network
depends_on:
- eureka-server
- mysql
# 旅行服务
travel-service:
build:
context: ./travel-service
container_name: jiebanke-travel
ports:
- "8083:8083"
networks:
- jiebanke-network
depends_on:
- eureka-server
- mysql
# 动物服务
animal-service:
build:
context: ./animal-service
container_name: jiebanke-animal
ports:
- "8084:8084"
networks:
- jiebanke-network
depends_on:
- eureka-server
- mysql
# 订单服务
order-service:
build:
context: ./order-service
container_name: jiebanke-order
ports:
- "8085:8085"
networks:
- jiebanke-network
depends_on:
- eureka-server
- mysql
# 推广服务
promotion-service:
build:
context: ./promotion-service
container_name: jiebanke-promotion
ports:
- "8086:8086"
networks:
- jiebanke-network
depends_on:
- eureka-server
- mysql
volumes:
mysql_data:
networks:
jiebanke-network:
driver: bridge

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>eureka-server</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,13 +0,0 @@
package com.jiebanke.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

View File

@@ -1,17 +0,0 @@
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false

View File

@@ -1,17 +0,0 @@
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false

View File

@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>gateway-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,34 +0,0 @@
package com.jiebanke.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("auth-service", r -> r.path("/api/auth/**")
.uri("lb://auth-service"))
.route("user-service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.route("travel-service", r -> r.path("/api/travel/**")
.uri("lb://travel-service"))
.route("animal-service", r -> r.path("/api/animals/**")
.uri("lb://animal-service"))
.route("order-service", r -> r.path("/api/orders/**")
.uri("lb://order-service"))
.route("promotion-service", r -> r.path("/api/promotion/**")
.uri("lb://promotion-service"))
.build();
}
}

View File

@@ -1,38 +0,0 @@
server:
port: 8080
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/api/auth/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
- id: travel-service
uri: lb://travel-service
predicates:
- Path=/api/travel/**
- id: animal-service
uri: lb://animal-service
predicates:
- Path=/api/animals/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
- id: promotion-service
uri: lb://promotion-service
predicates:
- Path=/api/promotion/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

View File

@@ -1,38 +0,0 @@
server:
port: 8080
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/api/auth/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
- id: travel-service
uri: lb://travel-service
predicates:
- Path=/api/travel/**
- id: animal-service
uri: lb://animal-service
predicates:
- Path=/api/animals/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
- id: promotion-service
uri: lb://promotion-service
predicates:
- Path=/api/promotion/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>order-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,19 +0,0 @@
package com.jiebanke.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.jiebanke.order.mapper")
@ComponentScan(basePackages = "com.jiebanke")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}

View File

@@ -1,140 +0,0 @@
package com.jiebanke.order.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jiebanke.common.vo.ApiResponse;
import com.jiebanke.order.entity.Order;
import com.jiebanke.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 创建订单
*/
@PostMapping
public ApiResponse<Map<String, Object>> createOrder(
@RequestHeader("userId") Long userId,
@RequestBody Order order) {
Long orderId = orderService.createOrder(order, userId);
Order createdOrder = orderService.getOrderById(orderId);
Map<String, Object> result = new HashMap<>();
result.put("order", createdOrder);
result.put("message", "订单创建成功");
return ApiResponse.success(result);
}
/**
* 获取订单详情
*/
@GetMapping("/{orderId}")
public ApiResponse<Order> getOrder(@PathVariable Long orderId) {
Order order = orderService.getOrderById(orderId);
return ApiResponse.success(order);
}
/**
* 获取用户订单列表
*/
@GetMapping
public ApiResponse<Map<String, Object>> getUserOrders(
@RequestHeader("userId") Long userId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status) {
IPage<Order> orders = orderService.getUserOrders(userId, page, pageSize, status);
Map<String, Object> result = new HashMap<>();
result.put("orders", orders.getRecords());
result.put("pagination", Map.of(
"page", orders.getCurrent(),
"pageSize", orders.getSize(),
"total", orders.getTotal(),
"totalPages", orders.getPages()
));
return ApiResponse.success(result);
}
/**
* 更新订单状态
*/
@PutMapping("/{orderId}/status")
public ApiResponse<Map<String, Object>> updateOrderStatus(
@PathVariable Long orderId,
@RequestBody Map<String, String> requestBody,
@RequestHeader("userId") Long userId) {
String status = requestBody.get("status");
Order updatedOrder = orderService.updateOrderStatus(orderId, status, userId);
Map<String, Object> result = new HashMap<>();
result.put("order", updatedOrder);
result.put("message", "订单状态更新成功");
return ApiResponse.success(result);
}
/**
* 删除订单
*/
@DeleteMapping("/{orderId}")
public ApiResponse<Map<String, Object>> deleteOrder(
@PathVariable Long orderId,
@RequestHeader("userId") Long userId) {
boolean deleted = orderService.deleteOrder(orderId, userId);
Map<String, Object> result = new HashMap<>();
result.put("message", "订单删除成功");
result.put("orderId", orderId);
return ApiResponse.success(result);
}
/**
* 获取订单统计信息
*/
@GetMapping("/statistics")
public ApiResponse<Map<String, Object>> getOrderStatistics(@RequestHeader("merchantId") Long merchantId) {
Map<String, Object> statistics = orderService.getOrderStats(merchantId);
return ApiResponse.success(statistics);
}
/**
* 管理员获取所有订单
*/
@GetMapping("/admin")
public ApiResponse<Map<String, Object>> getAllOrders(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status,
@RequestParam(required = false) Long merchantId,
@RequestParam(required = false) Long userId) {
IPage<Order> orders = orderService.getAllOrders(page, pageSize, status, merchantId, userId);
Map<String, Object> result = new HashMap<>();
result.put("orders", orders.getRecords());
result.put("pagination", Map.of(
"page", orders.getCurrent(),
"pageSize", orders.getSize(),
"total", orders.getTotal(),
"totalPages", orders.getPages()
));
return ApiResponse.success(result);
}
}

View File

@@ -1,18 +0,0 @@
package com.jiebanke.order.entity;
import com.jiebanke.common.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
public class Order extends BaseEntity {
private Long userId;
private String orderNo;
private BigDecimal amount;
private String status;
private String type;
private String description;
}

View File

@@ -1,37 +0,0 @@
package com.jiebanke.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jiebanke.order.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
/**
* 根据用户ID获取订单列表
* @param userId 用户ID
* @return 订单列表
*/
@Select("SELECT * FROM orders WHERE user_id = #{userId} ORDER BY created_at DESC")
List<Order> selectByUserId(@Param("userId") Long userId);
/**
* 根据状态获取订单列表
* @param status 状态
* @return 订单列表
*/
@Select("SELECT * FROM orders WHERE status = #{status} ORDER BY created_at DESC")
List<Order> selectByStatus(@Param("status") String status);
/**
* 根据订单号获取订单
* @param orderNo 订单号
* @return 订单
*/
@Select("SELECT * FROM orders WHERE order_no = #{orderNo}")
Order selectByOrderNo(@Param("orderNo") String orderNo);
}

View File

@@ -1,70 +0,0 @@
package com.jiebanke.order.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jiebanke.order.entity.Order;
import java.util.Map;
public interface OrderService extends IService<Order> {
/**
* 创建订单
* @param order 订单信息
* @param userId 用户ID
* @return 创建的订单ID
*/
Long createOrder(Order order, Long userId);
/**
* 根据ID获取订单
* @param orderId 订单ID
* @return 订单信息
*/
Order getOrderById(Long orderId);
/**
* 获取用户订单列表
* @param userId 用户ID
* @param page 页码
* @param pageSize 每页数量
* @param status 状态
* @return 订单分页列表
*/
IPage<Order> getUserOrders(Long userId, Integer page, Integer pageSize, String status);
/**
* 更新订单状态
* @param orderId 订单ID
* @param status 新状态
* @param userId 操作人ID
* @return 更新后的订单
*/
Order updateOrderStatus(Long orderId, String status, Long userId);
/**
* 删除订单(软删除)
* @param orderId 订单ID
* @param userId 用户ID
* @return 是否删除成功
*/
boolean deleteOrder(Long orderId, Long userId);
/**
* 获取订单统计信息
* @param merchantId 商家ID
* @return 统计信息
*/
Map<String, Object> getOrderStats(Long merchantId);
/**
* 获取所有订单(管理员)
* @param page 页码
* @param pageSize 每页数量
* @param status 状态
* @param merchantId 商家ID
* @param userId 用户ID
* @return 订单分页列表
*/
IPage<Order> getAllOrders(Integer page, Integer pageSize, String status, Long merchantId, Long userId);
}

View File

@@ -1,159 +0,0 @@
package com.jiebanke.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jiebanke.common.exception.BusinessException;
import com.jiebanke.order.entity.Order;
import com.jiebanke.order.mapper.OrderMapper;
import com.jiebanke.order.service.OrderService;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Override
public Long createOrder(Order order, Long userId) {
// 生成订单号
String orderNo = "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
order.setOrderNo(orderNo);
order.setUserId(userId);
// 设置默认状态
if (order.getStatus() == null) {
order.setStatus("pending");
}
this.save(order);
return order.getId();
}
@Override
public Order getOrderById(Long orderId) {
Order order = this.getById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
return order;
}
@Override
public IPage<Order> getUserOrders(Long userId, Integer page, Integer pageSize, String status) {
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
if (status != null && !status.isEmpty()) {
queryWrapper.eq("status", status);
}
queryWrapper.orderByDesc("created_at");
Page<Order> pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10);
return this.page(pageObj, queryWrapper);
}
@Override
public Order updateOrderStatus(Long orderId, String status, Long userId) {
Order existingOrder = this.getById(orderId);
if (existingOrder == null) {
throw new BusinessException("订单不存在");
}
// 验证状态是否有效
String[] validStatuses = {"pending", "processing", "completed", "cancelled", "failed"};
boolean isValidStatus = false;
for (String validStatus : validStatuses) {
if (validStatus.equals(status)) {
isValidStatus = true;
break;
}
}
if (!isValidStatus) {
throw new BusinessException("无效的订单状态");
}
existingOrder.setStatus(status);
this.updateById(existingOrder);
return existingOrder;
}
@Override
public boolean deleteOrder(Long orderId, Long userId) {
return this.removeById(orderId);
}
@Override
public Map<String, Object> getOrderStats(Long merchantId) {
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("merchant_id", merchantId);
int totalOrders = Math.toIntExact(this.count(queryWrapper));
QueryWrapper<Order> pendingWrapper = new QueryWrapper<>();
pendingWrapper.eq("merchant_id", merchantId).eq("status", "pending");
int pendingOrders = Math.toIntExact(this.count(pendingWrapper));
QueryWrapper<Order> processingWrapper = new QueryWrapper<>();
processingWrapper.eq("merchant_id", merchantId).eq("status", "processing");
int processingOrders = Math.toIntExact(this.count(processingWrapper));
QueryWrapper<Order> completedWrapper = new QueryWrapper<>();
completedWrapper.eq("merchant_id", merchantId).eq("status", "completed");
int completedOrders = Math.toIntExact(this.count(completedWrapper));
QueryWrapper<Order> cancelledWrapper = new QueryWrapper<>();
cancelledWrapper.eq("merchant_id", merchantId).eq("status", "cancelled");
int cancelledOrders = Math.toIntExact(this.count(cancelledWrapper));
QueryWrapper<Order> failedWrapper = new QueryWrapper<>();
failedWrapper.eq("merchant_id", merchantId).eq("status", "failed");
int failedOrders = Math.toIntExact(this.count(failedWrapper));
// 计算总收入
QueryWrapper<Order> revenueWrapper = new QueryWrapper<>();
revenueWrapper.eq("merchant_id", merchantId).select("SUM(amount) as totalRevenue");
Map<String, Object> revenueMap = this.getMap(revenueWrapper);
BigDecimal totalRevenue = revenueMap != null && revenueMap.get("totalRevenue") != null ?
new BigDecimal(revenueMap.get("totalRevenue").toString()) : BigDecimal.ZERO;
Map<String, Object> stats = new HashMap<>();
stats.put("totalOrders", totalOrders);
stats.put("pendingOrders", pendingOrders);
stats.put("processingOrders", processingOrders);
stats.put("completedOrders", completedOrders);
stats.put("cancelledOrders", cancelledOrders);
stats.put("failedOrders", failedOrders);
stats.put("totalRevenue", totalRevenue);
return stats;
}
@Override
public IPage<Order> getAllOrders(Integer page, Integer pageSize, String status, Long merchantId, Long userId) {
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
if (status != null && !status.isEmpty()) {
queryWrapper.eq("status", status);
}
if (merchantId != null) {
queryWrapper.eq("merchant_id", merchantId);
}
if (userId != null) {
queryWrapper.eq("user_id", userId);
}
queryWrapper.orderByDesc("created_at");
Page<Order> pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10);
return this.page(pageObj, queryWrapper);
}
}

View File

@@ -1,32 +0,0 @@
server:
port: 8085
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,32 +0,0 @@
server:
port: 8085
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>结伴客Java后端</name>
<description>结伴客Java微服务架构后端系统</description>
<modules>
<module>eureka-server</module>
<module>gateway-service</module>
<module>auth-service</module>
<module>user-service</module>
<module>travel-service</module>
<module>animal-service</module>
<module>order-service</module>
<module>promotion-service</module>
<module>common</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>3.1.0</spring.boot.version>
<spring.cloud.version>2022.0.3</spring.cloud.version>
<mysql.version>8.0.33</mysql.version>
<mybatis.plus.version>3.5.3.1</mybatis.plus.version>
<junit.version>5.9.2</junit.version>
<mockito.version>5.2.0</mockito.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring.cloud.version}</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>promotion-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,19 +0,0 @@
package com.jiebanke.promotion;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan({"com.jiebanke.promotion.mapper"})
@ComponentScan(basePackages = "com.jiebanke")
public class PromotionApplication {
public static void main(String[] args) {
SpringApplication.run(PromotionApplication.class, args);
}
}

View File

@@ -1,175 +0,0 @@
package com.jiebanke.promotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jiebanke.common.vo.ApiResponse;
import com.jiebanke.promotion.entity.PromotionActivity;
import com.jiebanke.promotion.entity.RewardRecord;
import com.jiebanke.promotion.service.PromotionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/promotion")
public class PromotionController {
@Autowired
private PromotionService promotionService;
/**
* 获取推广活动列表
*/
@GetMapping("/activities")
public ApiResponse<Map<String, Object>> getPromotionActivities(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String name,
@RequestParam(required = false) String status) {
IPage<PromotionActivity> activities = promotionService.getPromotionActivities(page, pageSize, name, status);
Map<String, Object> result = new HashMap<>();
result.put("activities", activities.getRecords());
result.put("pagination", Map.of(
"current", activities.getCurrent(),
"pageSize", activities.getSize(),
"total", activities.getTotal(),
"totalPages", activities.getPages()
));
return ApiResponse.success(result);
}
/**
* 获取推广活动详情
*/
@GetMapping("/activities/{id}")
public ApiResponse<PromotionActivity> getPromotionActivity(@PathVariable Long id) {
PromotionActivity activity = promotionService.getPromotionActivityById(id);
return ApiResponse.success(activity);
}
/**
* 创建推广活动
*/
@PostMapping("/activities")
public ApiResponse<Map<String, Object>> createPromotionActivity(@RequestBody PromotionActivity activity) {
Long activityId = promotionService.createPromotionActivity(activity);
PromotionActivity createdActivity = promotionService.getPromotionActivityById(activityId);
Map<String, Object> result = new HashMap<>();
result.put("activity", createdActivity);
result.put("message", "推广活动创建成功");
return ApiResponse.success(result);
}
/**
* 更新推广活动
*/
@PutMapping("/activities/{id}")
public ApiResponse<Map<String, Object>> updatePromotionActivity(
@PathVariable Long id,
@RequestBody PromotionActivity activity) {
PromotionActivity updatedActivity = promotionService.updatePromotionActivity(id, activity);
Map<String, Object> result = new HashMap<>();
result.put("activity", updatedActivity);
result.put("message", "推广活动更新成功");
return ApiResponse.success(result);
}
/**
* 删除推广活动
*/
@DeleteMapping("/activities/{id}")
public ApiResponse<Map<String, Object>> deletePromotionActivity(@PathVariable Long id) {
boolean deleted = promotionService.deletePromotionActivity(id);
Map<String, Object> result = new HashMap<>();
result.put("message", "推广活动删除成功");
result.put("id", id);
return ApiResponse.success(result);
}
/**
* 暂停推广活动
*/
@PostMapping("/activities/{id}/pause")
public ApiResponse<Map<String, Object>> pausePromotionActivity(@PathVariable Long id) {
boolean paused = promotionService.pausePromotionActivity(id);
Map<String, Object> result = new HashMap<>();
result.put("message", "推广活动已暂停");
result.put("id", id);
return ApiResponse.success(result);
}
/**
* 恢复推广活动
*/
@PostMapping("/activities/{id}/resume")
public ApiResponse<Map<String, Object>> resumePromotionActivity(@PathVariable Long id) {
boolean resumed = promotionService.resumePromotionActivity(id);
Map<String, Object> result = new HashMap<>();
result.put("message", "推广活动已恢复");
result.put("id", id);
return ApiResponse.success(result);
}
/**
* 获取奖励记录列表
*/
@GetMapping("/rewards")
public ApiResponse<Map<String, Object>> getRewardRecords(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String user,
@RequestParam(required = false) String rewardType,
@RequestParam(required = false) String status) {
IPage<RewardRecord> rewards = promotionService.getRewardRecords(page, pageSize, user, rewardType, status);
Map<String, Object> result = new HashMap<>();
result.put("rewards", rewards.getRecords());
result.put("pagination", Map.of(
"current", rewards.getCurrent(),
"pageSize", rewards.getSize(),
"total", rewards.getTotal(),
"totalPages", rewards.getPages()
));
return ApiResponse.success(result);
}
/**
* 发放奖励
*/
@PostMapping("/rewards/{id}/issue")
public ApiResponse<Map<String, Object>> issueReward(@PathVariable Long id) {
boolean issued = promotionService.issueReward(id);
Map<String, Object> result = new HashMap<>();
result.put("message", "奖励已发放");
result.put("id", id);
return ApiResponse.success(result);
}
/**
* 获取推广统计数据
*/
@GetMapping("/statistics")
public ApiResponse<Map<String, Object>> getPromotionStatistics() {
Map<String, Object> statistics = promotionService.getPromotionStatistics();
return ApiResponse.success(statistics);
}
}

View File

@@ -1,22 +0,0 @@
package com.jiebanke.promotion.entity;
import com.jiebanke.common.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
public class PromotionActivity extends BaseEntity {
private String name;
private String description;
private String rewardType;
private BigDecimal rewardAmount;
private String status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Integer maxParticipants;
private Integer currentParticipants;
}

View File

@@ -1,22 +0,0 @@
package com.jiebanke.promotion.entity;
import com.jiebanke.common.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
public class RewardRecord extends BaseEntity {
private Long userId;
private String userName;
private String userPhone;
private Long activityId;
private String activityName;
private String rewardType;
private BigDecimal rewardAmount;
private String status;
private LocalDateTime issuedAt;
}

View File

@@ -1,46 +0,0 @@
package com.jiebanke.promotion.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jiebanke.promotion.entity.PromotionActivity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface PromotionActivityMapper extends BaseMapper<PromotionActivity> {
/**
* 根据状态获取推广活动列表
* @param status 状态
* @return 推广活动列表
*/
@Select("SELECT * FROM promotion_activities WHERE status = #{status} ORDER BY created_at DESC")
List<PromotionActivity> selectByStatus(@Param("status") String status);
/**
* 根据名称模糊查询推广活动
* @param name 名称
* @return 推广活动列表
*/
@Select("SELECT * FROM promotion_activities WHERE name LIKE CONCAT('%', #{name}, '%') ORDER BY created_at DESC")
List<PromotionActivity> selectByName(@Param("name") String name);
/**
* 暂停推广活动
* @param id 活动ID
* @return 更新记录数
*/
@Update("UPDATE promotion_activities SET status = 'paused', updated_at = NOW() WHERE id = #{id}")
int pauseActivity(@Param("id") Long id);
/**
* 恢复推广活动
* @param id 活动ID
* @return 更新记录数
*/
@Update("UPDATE promotion_activities SET status = 'active', updated_at = NOW() WHERE id = #{id}")
int resumeActivity(@Param("id") Long id);
}

View File

@@ -1,38 +0,0 @@
package com.jiebanke.promotion.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jiebanke.promotion.entity.RewardRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface RewardRecordMapper extends BaseMapper<RewardRecord> {
/**
* 根据用户ID获取奖励记录列表
* @param userId 用户ID
* @return 奖励记录列表
*/
@Select("SELECT * FROM reward_records WHERE user_id = #{userId} ORDER BY created_at DESC")
List<RewardRecord> selectByUserId(@Param("userId") Long userId);
/**
* 根据状态获取奖励记录列表
* @param status 状态
* @return 奖励记录列表
*/
@Select("SELECT * FROM reward_records WHERE status = #{status} ORDER BY created_at DESC")
List<RewardRecord> selectByStatus(@Param("status") String status);
/**
* 发放奖励
* @param id 奖励记录ID
* @return 更新记录数
*/
@Update("UPDATE reward_records SET status = 'issued', issued_at = NOW() WHERE id = #{id}")
int issueReward(@Param("id") Long id);
}

View File

@@ -1,88 +0,0 @@
package com.jiebanke.promotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jiebanke.promotion.entity.PromotionActivity;
import com.jiebanke.promotion.entity.RewardRecord;
import java.util.Map;
public interface PromotionService extends IService<PromotionActivity> {
/**
* 获取推广活动列表
* @param page 页码
* @param pageSize 每页数量
* @param name 活动名称
* @param status 状态
* @return 推广活动分页列表
*/
IPage<PromotionActivity> getPromotionActivities(Integer page, Integer pageSize, String name, String status);
/**
* 获取推广活动详情
* @param id 活动ID
* @return 推广活动
*/
PromotionActivity getPromotionActivityById(Long id);
/**
* 创建推广活动
* @param activity 活动信息
* @return 创建的活动ID
*/
Long createPromotionActivity(PromotionActivity activity);
/**
* 更新推广活动
* @param id 活动ID
* @param activity 更新的活动信息
* @return 更新后的活动
*/
PromotionActivity updatePromotionActivity(Long id, PromotionActivity activity);
/**
* 删除推广活动
* @param id 活动ID
* @return 是否删除成功
*/
boolean deletePromotionActivity(Long id);
/**
* 暂停推广活动
* @param id 活动ID
* @return 是否暂停成功
*/
boolean pausePromotionActivity(Long id);
/**
* 恢复推广活动
* @param id 活动ID
* @return 是否恢复成功
*/
boolean resumePromotionActivity(Long id);
/**
* 获取奖励记录列表
* @param page 页码
* @param pageSize 每页数量
* @param user 用户
* @param rewardType 奖励类型
* @param status 状态
* @return 奖励记录分页列表
*/
IPage<RewardRecord> getRewardRecords(Integer page, Integer pageSize, String user, String rewardType, String status);
/**
* 发放奖励
* @param id 奖励记录ID
* @return 是否发放成功
*/
boolean issueReward(Long id);
/**
* 获取推广统计数据
* @return 统计数据
*/
Map<String, Object> getPromotionStatistics();
}

View File

@@ -1,181 +0,0 @@
package com.jiebanke.promotion.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jiebanke.common.exception.BusinessException;
import com.jiebanke.promotion.entity.PromotionActivity;
import com.jiebanke.promotion.entity.RewardRecord;
import com.jiebanke.promotion.mapper.PromotionActivityMapper;
import com.jiebanke.promotion.mapper.RewardRecordMapper;
import com.jiebanke.promotion.service.PromotionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@Service
public class PromotionServiceImpl extends ServiceImpl<PromotionActivityMapper, PromotionActivity> implements PromotionService {
@Autowired
private PromotionActivityMapper promotionActivityMapper;
@Autowired
private RewardRecordMapper rewardRecordMapper;
@Override
public IPage<PromotionActivity> getPromotionActivities(Integer page, Integer pageSize, String name, String status) {
QueryWrapper<PromotionActivity> queryWrapper = new QueryWrapper<>();
if (name != null && !name.isEmpty()) {
queryWrapper.like("name", name);
}
if (status != null && !status.isEmpty()) {
queryWrapper.eq("status", status);
}
queryWrapper.orderByDesc("created_at");
Page<PromotionActivity> pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10);
return this.page(pageObj, queryWrapper);
}
@Override
public PromotionActivity getPromotionActivityById(Long id) {
PromotionActivity activity = this.getById(id);
if (activity == null) {
throw new BusinessException("推广活动不存在");
}
return activity;
}
@Override
public Long createPromotionActivity(PromotionActivity activity) {
this.save(activity);
return activity.getId();
}
@Override
public PromotionActivity updatePromotionActivity(Long id, PromotionActivity activity) {
PromotionActivity existingActivity = this.getById(id);
if (existingActivity == null) {
throw new BusinessException("推广活动不存在");
}
// 更新字段
if (activity.getName() != null) {
existingActivity.setName(activity.getName());
}
if (activity.getDescription() != null) {
existingActivity.setDescription(activity.getDescription());
}
if (activity.getRewardType() != null) {
existingActivity.setRewardType(activity.getRewardType());
}
if (activity.getRewardAmount() != null) {
existingActivity.setRewardAmount(activity.getRewardAmount());
}
if (activity.getStatus() != null) {
existingActivity.setStatus(activity.getStatus());
}
if (activity.getStartTime() != null) {
existingActivity.setStartTime(activity.getStartTime());
}
if (activity.getEndTime() != null) {
existingActivity.setEndTime(activity.getEndTime());
}
if (activity.getMaxParticipants() != null) {
existingActivity.setMaxParticipants(activity.getMaxParticipants());
}
if (activity.getCurrentParticipants() != null) {
existingActivity.setCurrentParticipants(activity.getCurrentParticipants());
}
this.updateById(existingActivity);
return existingActivity;
}
@Override
public boolean deletePromotionActivity(Long id) {
return this.removeById(id);
}
@Override
public boolean pausePromotionActivity(Long id) {
int result = promotionActivityMapper.pauseActivity(id);
return result > 0;
}
@Override
public boolean resumePromotionActivity(Long id) {
int result = promotionActivityMapper.resumeActivity(id);
return result > 0;
}
@Override
public IPage<RewardRecord> getRewardRecords(Integer page, Integer pageSize, String user, String rewardType, String status) {
QueryWrapper<RewardRecord> queryWrapper = new QueryWrapper<>();
if (user != null && !user.isEmpty()) {
queryWrapper.like("user_name", user).or().like("user_phone", user);
}
if (rewardType != null && !rewardType.isEmpty()) {
queryWrapper.eq("reward_type", rewardType);
}
if (status != null && !status.isEmpty()) {
queryWrapper.eq("status", status);
}
queryWrapper.orderByDesc("created_at");
Page<RewardRecord> pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10);
return rewardRecordMapper.selectPage(pageObj, queryWrapper);
}
@Override
public boolean issueReward(Long id) {
int result = rewardRecordMapper.issueReward(id);
return result > 0;
}
@Override
public Map<String, Object> getPromotionStatistics() {
// 获取活动总数
int totalActivities = Math.toIntExact(this.count());
// 获取进行中的活动数
QueryWrapper<PromotionActivity> activeWrapper = new QueryWrapper<>();
activeWrapper.eq("status", "active");
int activeActivities = Math.toIntExact(this.count(activeWrapper));
// 获取奖励记录总数
int totalRewards = Math.toIntExact(rewardRecordMapper.selectCount(null));
// 获取已发放的奖励数
QueryWrapper<RewardRecord> issuedWrapper = new QueryWrapper<>();
issuedWrapper.eq("status", "issued");
int issuedRewards = Math.toIntExact(rewardRecordMapper.selectCount(issuedWrapper));
// 计算总奖励金额
QueryWrapper<RewardRecord> amountWrapper = new QueryWrapper<>();
amountWrapper.eq("status", "issued").select("SUM(reward_amount) as totalAmount");
Map<String, Object> amountMap = rewardRecordMapper.selectMap(amountWrapper);
BigDecimal totalAmount = amountMap != null && amountMap.get("totalAmount") != null ?
new BigDecimal(amountMap.get("totalAmount").toString()) : BigDecimal.ZERO;
Map<String, Object> statistics = new HashMap<>();
statistics.put("totalActivities", totalActivities);
statistics.put("activeActivities", activeActivities);
statistics.put("totalRewards", totalRewards);
statistics.put("issuedRewards", issuedRewards);
statistics.put("totalAmount", totalAmount);
return statistics;
}
}

View File

@@ -1,32 +0,0 @@
server:
port: 8086
spring:
application:
name: promotion-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,32 +0,0 @@
server:
port: 8086
spring:
application:
name: promotion-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto

View File

@@ -1,137 +0,0 @@
-- 创建数据库
CREATE DATABASE IF NOT EXISTS jiebanke CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE jiebanke;
-- 创建管理员表
CREATE TABLE IF NOT EXISTS admins (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
role VARCHAR(20) DEFAULT 'admin',
status VARCHAR(20) DEFAULT 'active',
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
real_name VARCHAR(100),
id_card VARCHAR(20),
status VARCHAR(20) DEFAULT 'active',
balance DECIMAL(15,2) DEFAULT 0.00,
credit_score INT DEFAULT 100,
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建订单表
CREATE TABLE IF NOT EXISTS orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
order_no VARCHAR(50) NOT NULL UNIQUE,
amount DECIMAL(15,2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
type VARCHAR(20) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建旅行计划表
CREATE TABLE IF NOT EXISTS travel_plans (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
destination VARCHAR(100) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
budget DECIMAL(10,2),
interests TEXT,
description TEXT,
visibility VARCHAR(20) DEFAULT 'public',
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建动物表
CREATE TABLE IF NOT EXISTS animals (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
merchant_id BIGINT NOT NULL,
name VARCHAR(50) NOT NULL,
species VARCHAR(50) NOT NULL,
breed VARCHAR(50),
birth_date DATE,
personality TEXT,
farm_location VARCHAR(255),
price DECIMAL(10,2) NOT NULL,
claim_count INT DEFAULT 0,
status VARCHAR(20) DEFAULT 'available',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建推广活动表
CREATE TABLE IF NOT EXISTS promotion_activities (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
reward_type VARCHAR(20),
reward_amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'active',
start_time TIMESTAMP,
end_time TIMESTAMP,
max_participants INT,
current_participants INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建奖励记录表
CREATE TABLE IF NOT EXISTS reward_records (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
user_name VARCHAR(50),
user_phone VARCHAR(20),
activity_id BIGINT,
activity_name VARCHAR(100),
reward_type VARCHAR(20),
reward_amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'pending',
issued_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入默认管理员账号
INSERT INTO admins (username, password, email, role) VALUES
('admin', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin@jiebanke.com', 'super_admin'),
('manager', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'manager@jiebanke.com', 'admin');
-- 插入测试用户账号
INSERT INTO users (username, password, email, phone, real_name, id_card, balance, credit_score) VALUES
('user1', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'user1@jiebanke.com', '13800138001', '张三', '110101199001011234', 1000.00, 95),
('user2', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'user2@jiebanke.com', '13800138002', '李四', '110101199002022345', 500.00, 85);
-- 创建索引
CREATE INDEX idx_admins_username ON admins(username);
CREATE INDEX idx_admins_email ON admins(email);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_phone ON users(phone);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_order_no ON orders(order_no);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_travel_plans_user_id ON travel_plans(user_id);
CREATE INDEX idx_travel_plans_destination ON travel_plans(destination);
CREATE INDEX idx_animals_species ON animals(species);
CREATE INDEX idx_animals_status ON animals(status);
CREATE INDEX idx_promotion_activities_status ON promotion_activities(status);
CREATE INDEX idx_reward_records_user_id ON reward_records(user_id);
CREATE INDEX idx_reward_records_activity_id ON reward_records(activity_id);

View File

@@ -1,92 +0,0 @@
#!/bin/bash
# 启动结伴客Java后端服务脚本
# 启动顺序Eureka Server -> 其他服务 -> Gateway
echo "开始启动结伴客Java后端服务..."
# 启动Eureka Server
echo "正在启动Eureka Server..."
cd eureka-server
mvn spring-boot:run > eureka.log 2>&1 &
EUREKA_PID=$!
cd ..
sleep 10
# 启动Auth Service
echo "正在启动Auth Service..."
cd auth-service
mvn spring-boot:run > auth.log 2>&1 &
AUTH_PID=$!
cd ..
sleep 5
# 启动User Service
echo "正在启动User Service..."
cd user-service
mvn spring-boot:run > user.log 2>&1 &
USER_PID=$!
cd ..
sleep 5
# 启动Travel Service
echo "正在启动Travel Service..."
cd travel-service
mvn spring-boot:run > travel.log 2>&1 &
TRAVEL_PID=$!
cd ..
sleep 5
# 启动Animal Service
echo "正在启动Animal Service..."
cd animal-service
mvn spring-boot:run > animal.log 2>&1 &
ANIMAL_PID=$!
cd ..
sleep 5
# 启动Order Service
echo "正在启动Order Service..."
cd order-service
mvn spring-boot:run > order.log 2>&1 &
ORDER_PID=$!
cd ..
sleep 5
# 启动Promotion Service
echo "正在启动Promotion Service..."
cd promotion-service
mvn spring-boot:run > promotion.log 2>&1 &
PROMOTION_PID=$!
cd ..
sleep 5
# 启动Gateway Service
echo "正在启动Gateway Service..."
cd gateway-service
mvn spring-boot:run > gateway.log 2>&1 &
GATEWAY_PID=$!
cd ..
echo "所有服务已启动!"
echo "Eureka Server PID: $EUREKA_PID"
echo "Auth Service PID: $AUTH_PID"
echo "User Service PID: $USER_PID"
echo "Travel Service PID: $TRAVEL_PID"
echo "Animal Service PID: $ANIMAL_PID"
echo "Order Service PID: $ORDER_PID"
echo "Promotion Service PID: $PROMOTION_PID"
echo "Gateway Service PID: $GATEWAY_PID"
echo "服务访问地址:"
echo "Eureka Dashboard: http://localhost:8761"
echo "API Gateway: http://localhost:8080"
echo "API文档: http://localhost:8080/doc.html"

View File

@@ -1,29 +0,0 @@
#!/bin/bash
# 停止结伴客Java后端服务脚本
echo "开始停止结伴客Java后端服务..."
# 查找并停止所有相关的Java进程
PIDS=$(ps aux | grep "com.jiebanke" | grep -v grep | awk '{print $2}')
if [ -z "$PIDS" ]; then
echo "没有找到正在运行的结伴客服务"
else
echo "正在停止以下进程: $PIDS"
kill $PIDS
echo "服务已停止"
fi
# 清理日志文件
echo "清理日志文件..."
rm -f eureka-server/eureka.log
rm -f auth-service/auth.log
rm -f user-service/user.log
rm -f travel-service/travel.log
rm -f animal-service/animal.log
rm -f order-service/order.log
rm -f promotion-service/promotion.log
rm -f gateway-service/gateway.log
echo "清理完成"

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>travel-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,19 +0,0 @@
package com.jiebanke.travel;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.jiebanke.travel.mapper")
@ComponentScan(basePackages = "com.jiebanke")
public class TravelApplication {
public static void main(String[] args) {
SpringApplication.run(TravelApplication.class, args);
}
}

View File

@@ -1,115 +0,0 @@
package com.jiebanke.travel.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jiebanke.common.vo.ApiResponse;
import com.jiebanke.travel.entity.TravelPlan;
import com.jiebanke.travel.service.TravelPlanService;
import com.jiebanke.travel.service.TravelStats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/travel")
public class TravelPlanController {
@Autowired
private TravelPlanService travelPlanService;
/**
* 获取旅行计划列表
*/
@GetMapping("/plans")
public ApiResponse<Map<String, Object>> getTravelPlans(
@RequestHeader("userId") Long userId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status) {
IPage<TravelPlan> plans = travelPlanService.getTravelPlans(userId, page, pageSize, status);
Map<String, Object> result = new HashMap<>();
result.put("plans", plans.getRecords());
result.put("pagination", Map.of(
"page", plans.getCurrent(),
"pageSize", plans.getSize(),
"total", plans.getTotal(),
"totalPages", plans.getPages()
));
return ApiResponse.success(result);
}
/**
* 获取单个旅行计划详情
*/
@GetMapping("/plans/{planId}")
public ApiResponse<TravelPlan> getTravelPlan(@PathVariable Long planId) {
TravelPlan plan = travelPlanService.getTravelPlanById(planId);
return ApiResponse.success(plan);
}
/**
* 创建旅行计划
*/
@PostMapping("/plans")
public ApiResponse<Map<String, Object>> createTravelPlan(
@RequestHeader("userId") Long userId,
@RequestBody TravelPlan travelPlan) {
Long planId = travelPlanService.createTravelPlan(userId, travelPlan);
TravelPlan plan = travelPlanService.getTravelPlanById(planId);
Map<String, Object> result = new HashMap<>();
result.put("plan", plan);
result.put("message", "旅行计划创建成功");
return ApiResponse.success(result);
}
/**
* 更新旅行计划
*/
@PutMapping("/plans/{planId}")
public ApiResponse<Map<String, Object>> updateTravelPlan(
@PathVariable Long planId,
@RequestHeader("userId") Long userId,
@RequestBody TravelPlan travelPlan) {
TravelPlan updatedPlan = travelPlanService.updateTravelPlan(planId, userId, travelPlan);
Map<String, Object> result = new HashMap<>();
result.put("plan", updatedPlan);
result.put("message", "旅行计划更新成功");
return ApiResponse.success(result);
}
/**
* 删除旅行计划
*/
@DeleteMapping("/plans/{planId}")
public ApiResponse<Map<String, Object>> deleteTravelPlan(
@PathVariable Long planId,
@RequestHeader("userId") Long userId) {
boolean deleted = travelPlanService.deleteTravelPlan(planId, userId);
Map<String, Object> result = new HashMap<>();
result.put("message", "旅行计划删除成功");
result.put("planId", planId);
return ApiResponse.success(result);
}
/**
* 获取用户旅行统计
*/
@GetMapping("/stats")
public ApiResponse<TravelStats> getTravelStats(@RequestHeader("userId") Long userId) {
TravelStats stats = travelPlanService.getUserTravelStats(userId);
return ApiResponse.success(stats);
}
}

Some files were not shown because too many files have changed in this diff Show More