diff --git a/backend-java/PERFORMANCE_OPTIMIZATION.md b/backend-java/PERFORMANCE_OPTIMIZATION.md deleted file mode 100644 index 7102712..0000000 --- a/backend-java/PERFORMANCE_OPTIMIZATION.md +++ /dev/null @@ -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 redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate 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 redisTemplate; - - // 本地缓存(Caffeine) - private Cache 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后端服务的性能和稳定性。 \ No newline at end of file diff --git a/backend-java/README.md b/backend-java/README.md deleted file mode 100644 index bddd082..0000000 --- a/backend-java/README.md +++ /dev/null @@ -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) 文件。 \ No newline at end of file diff --git a/backend-java/animal-service/pom.xml b/backend-java/animal-service/pom.xml deleted file mode 100644 index f55fd0c..0000000 --- a/backend-java/animal-service/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - animal-service - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - mysql - mysql-connector-java - - - - - com.jiebanke - common - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/animal-service/src/main/java/com/jiebanke/animal/AnimalApplication.java b/backend-java/animal-service/src/main/java/com/jiebanke/animal/AnimalApplication.java deleted file mode 100644 index e3e4661..0000000 --- a/backend-java/animal-service/src/main/java/com/jiebanke/animal/AnimalApplication.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/backend-java/animal-service/src/main/java/com/jiebanke/animal/controller/AnimalController.java b/backend-java/animal-service/src/main/java/com/jiebanke/animal/controller/AnimalController.java deleted file mode 100644 index 76e8c23..0000000 --- a/backend-java/animal-service/src/main/java/com/jiebanke/animal/controller/AnimalController.java +++ /dev/null @@ -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> 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 animals = animalService.searchAnimals(keyword, species, minPrice, maxPrice, page, pageSize); - - Map 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> 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 animals = animalService.getAnimals(merchantId, page, pageSize, species, status); - - Map 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> getAnimalStatistics() { - Map statistics = animalService.getAnimalStatistics(); - return ApiResponse.success(statistics); - } - - /** - * 获取单个动物详情 - */ - @GetMapping("/{animalId}") - public ApiResponse getAnimal(@PathVariable Long animalId) { - Animal animal = animalService.getAnimalById(animalId); - return ApiResponse.success(animal); - } - - /** - * 创建动物信息 - */ - @PostMapping - public ApiResponse> createAnimal( - @RequestHeader("userId") Long merchantId, - @RequestBody Animal animal) { - - animal.setMerchantId(merchantId); - Long animalId = animalService.createAnimal(animal); - Animal createdAnimal = animalService.getAnimalById(animalId); - - Map result = new HashMap<>(); - result.put("animal", createdAnimal); - result.put("message", "动物信息创建成功"); - - return ApiResponse.success(result); - } - - /** - * 更新动物信息 - */ - @PutMapping("/{animalId}") - public ApiResponse> updateAnimal( - @PathVariable Long animalId, - @RequestBody Animal animal) { - - Animal updatedAnimal = animalService.updateAnimal(animalId, animal); - - Map result = new HashMap<>(); - result.put("animal", updatedAnimal); - result.put("message", "动物信息更新成功"); - - return ApiResponse.success(result); - } - - /** - * 删除动物信息 - */ - @DeleteMapping("/{animalId}") - public ApiResponse> deleteAnimal(@PathVariable Long animalId) { - boolean deleted = animalService.deleteAnimal(animalId); - - Map result = new HashMap<>(); - result.put("message", "动物信息删除成功"); - result.put("animalId", animalId); - - return ApiResponse.success(result); - } -} \ No newline at end of file diff --git a/backend-java/animal-service/src/main/java/com/jiebanke/animal/entity/Animal.java b/backend-java/animal-service/src/main/java/com/jiebanke/animal/entity/Animal.java deleted file mode 100644 index a080efe..0000000 --- a/backend-java/animal-service/src/main/java/com/jiebanke/animal/entity/Animal.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/animal-service/src/main/java/com/jiebanke/animal/mapper/AnimalMapper.java b/backend-java/animal-service/src/main/java/com/jiebanke/animal/mapper/AnimalMapper.java deleted file mode 100644 index 6fe424e..0000000 --- a/backend-java/animal-service/src/main/java/com/jiebanke/animal/mapper/AnimalMapper.java +++ /dev/null @@ -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 { - - /** - * 根据商家ID获取动物列表 - * @param merchantId 商家ID - * @return 动物列表 - */ - @Select("SELECT * FROM animals WHERE merchant_id = #{merchantId} ORDER BY created_at DESC") - List selectByMerchantId(@Param("merchantId") Long merchantId); - - /** - * 根据物种获取动物列表 - * @param species 物种 - * @return 动物列表 - */ - @Select("SELECT * FROM animals WHERE species = #{species} ORDER BY created_at DESC") - List selectBySpecies(@Param("species") String species); - - /** - * 根据状态获取动物列表 - * @param status 状态 - * @return 动物列表 - */ - @Select("SELECT * FROM animals WHERE status = #{status} ORDER BY created_at DESC") - List selectByStatus(@Param("status") String status); -} \ No newline at end of file diff --git a/backend-java/animal-service/src/main/java/com/jiebanke/animal/service/AnimalService.java b/backend-java/animal-service/src/main/java/com/jiebanke/animal/service/AnimalService.java deleted file mode 100644 index 9b9d569..0000000 --- a/backend-java/animal-service/src/main/java/com/jiebanke/animal/service/AnimalService.java +++ /dev/null @@ -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 { - - /** - * 获取动物列表 - * @param merchantId 商家ID - * @param page 页码 - * @param pageSize 每页数量 - * @param species 物种 - * @param status 状态 - * @return 动物分页列表 - */ - IPage 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 getAnimalStatistics(); - - /** - * 搜索动物 - * @param keyword 关键词 - * @param species 物种 - * @param minPrice 最低价格 - * @param maxPrice 最高价格 - * @param page 页码 - * @param pageSize 每页数量 - * @return 动物分页列表 - */ - IPage searchAnimals(String keyword, String species, Double minPrice, Double maxPrice, Integer page, Integer pageSize); -} \ No newline at end of file diff --git a/backend-java/animal-service/src/main/java/com/jiebanke/animal/service/impl/AnimalServiceImpl.java b/backend-java/animal-service/src/main/java/com/jiebanke/animal/service/impl/AnimalServiceImpl.java deleted file mode 100644 index 89b9559..0000000 --- a/backend-java/animal-service/src/main/java/com/jiebanke/animal/service/impl/AnimalServiceImpl.java +++ /dev/null @@ -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 implements AnimalService { - - @Override - public IPage getAnimals(Long merchantId, Integer page, Integer pageSize, String species, String status) { - QueryWrapper 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 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 getAnimalStatistics() { - // 获取动物总数 - int total = Math.toIntExact(this.count()); - - // 按物种统计 - QueryWrapper speciesWrapper = new QueryWrapper<>(); - speciesWrapper.select("species", "COUNT(*) as count"); - speciesWrapper.groupBy("species"); - List> speciesStats = this.listMaps(speciesWrapper); - - // 按状态统计 - QueryWrapper statusWrapper = new QueryWrapper<>(); - statusWrapper.select("status", "COUNT(*) as count"); - statusWrapper.groupBy("status"); - List> statusStats = this.listMaps(statusWrapper); - - // 构建返回结果 - Map statistics = new HashMap<>(); - statistics.put("total", total); - statistics.put("bySpecies", speciesStats); - statistics.put("byStatus", statusStats); - - return statistics; - } - - @Override - public IPage searchAnimals(String keyword, String species, Double minPrice, Double maxPrice, Integer page, Integer pageSize) { - QueryWrapper 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 pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10); - return this.page(pageObj, queryWrapper); - } -} \ No newline at end of file diff --git a/backend-java/animal-service/src/main/resources/application.yml b/backend-java/animal-service/src/main/resources/application.yml deleted file mode 100644 index 4566c3e..0000000 --- a/backend-java/animal-service/src/main/resources/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/animal-service/target/classes/application.yml b/backend-java/animal-service/target/classes/application.yml deleted file mode 100644 index 4566c3e..0000000 --- a/backend-java/animal-service/target/classes/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/animal-service/target/classes/com/jiebanke/animal/AnimalApplication.class b/backend-java/animal-service/target/classes/com/jiebanke/animal/AnimalApplication.class deleted file mode 100644 index 521b0e6..0000000 Binary files a/backend-java/animal-service/target/classes/com/jiebanke/animal/AnimalApplication.class and /dev/null differ diff --git a/backend-java/animal-service/target/classes/com/jiebanke/animal/controller/AnimalController.class b/backend-java/animal-service/target/classes/com/jiebanke/animal/controller/AnimalController.class deleted file mode 100644 index f38264d..0000000 Binary files a/backend-java/animal-service/target/classes/com/jiebanke/animal/controller/AnimalController.class and /dev/null differ diff --git a/backend-java/animal-service/target/classes/com/jiebanke/animal/entity/Animal.class b/backend-java/animal-service/target/classes/com/jiebanke/animal/entity/Animal.class deleted file mode 100644 index 80ad7f8..0000000 Binary files a/backend-java/animal-service/target/classes/com/jiebanke/animal/entity/Animal.class and /dev/null differ diff --git a/backend-java/animal-service/target/classes/com/jiebanke/animal/mapper/AnimalMapper.class b/backend-java/animal-service/target/classes/com/jiebanke/animal/mapper/AnimalMapper.class deleted file mode 100644 index b403f10..0000000 Binary files a/backend-java/animal-service/target/classes/com/jiebanke/animal/mapper/AnimalMapper.class and /dev/null differ diff --git a/backend-java/animal-service/target/classes/com/jiebanke/animal/service/AnimalService.class b/backend-java/animal-service/target/classes/com/jiebanke/animal/service/AnimalService.class deleted file mode 100644 index 51a5bd6..0000000 Binary files a/backend-java/animal-service/target/classes/com/jiebanke/animal/service/AnimalService.class and /dev/null differ diff --git a/backend-java/animal-service/target/classes/com/jiebanke/animal/service/impl/AnimalServiceImpl.class b/backend-java/animal-service/target/classes/com/jiebanke/animal/service/impl/AnimalServiceImpl.class deleted file mode 100644 index afb0a86..0000000 Binary files a/backend-java/animal-service/target/classes/com/jiebanke/animal/service/impl/AnimalServiceImpl.class and /dev/null differ diff --git a/backend-java/auth-service/pom.xml b/backend-java/auth-service/pom.xml deleted file mode 100644 index a885701..0000000 --- a/backend-java/auth-service/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - auth-service - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - mysql - mysql-connector-java - - - - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - runtime - - - io.jsonwebtoken - jjwt-jackson - runtime - - - - - org.springframework.security - spring-security-crypto - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.springframework.boot - spring-boot-starter-amqp - - - - - com.jiebanke - common - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/AuthApplication.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/AuthApplication.java deleted file mode 100644 index 319862b..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/AuthApplication.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/controller/AuthController.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/controller/AuthController.java deleted file mode 100644 index cdda871..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/controller/AuthController.java +++ /dev/null @@ -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 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 login(@RequestBody LoginRequest request) { - AuthResult result = authService.login(request.getUsername(), request.getPassword()); - return ApiResponse.success(result); - } - - /** - * 获取当前用户信息 - */ - @GetMapping("/me") - public ApiResponse getCurrentUser(@RequestHeader("userId") Long userId) { - User user = authService.getCurrentUser(userId); - return ApiResponse.success(user); - } - - /** - * 更新用户个人信息 - */ - @PutMapping("/profile") - public ApiResponse updateProfile( - @RequestHeader("userId") Long userId, - @RequestBody User user) { - User updatedUser = authService.updateProfile(userId, user); - return ApiResponse.success(updatedUser); - } - - /** - * 修改密码 - */ - @PutMapping("/password") - public ApiResponse> changePassword( - @RequestHeader("userId") Long userId, - @RequestBody ChangePasswordRequest request) { - boolean success = authService.changePassword(userId, request.getCurrentPassword(), request.getNewPassword()); - - Map result = new HashMap<>(); - result.put("message", success ? "密码修改成功" : "密码修改失败"); - return ApiResponse.success(result); - } - - /** - * 微信登录/注册 - */ - @PostMapping("/wechat") - public ApiResponse wechatLogin(@RequestBody WechatLoginRequest request) { - AuthResult result = authService.wechatLogin(request.getCode(), request.getUserInfo()); - return ApiResponse.success(result); - } - - /** - * 管理员登录 - */ - @PostMapping("/admin/login") - public ApiResponse adminLogin(@RequestBody LoginRequest request) { - AuthResult result = authService.adminLogin(request.getUsername(), request.getPassword()); - return ApiResponse.success(result); - } - - @GetMapping("/validate") - public ApiResponse 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; } - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/entity/User.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/entity/User.java deleted file mode 100644 index a0258a8..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/entity/User.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/mapper/UserMapper.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/mapper/UserMapper.java deleted file mode 100644 index 6a50446..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/mapper/UserMapper.java +++ /dev/null @@ -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 { - - /** - * 根据用户名查找用户 - * @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); -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthRabbitMQService.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthRabbitMQService.java deleted file mode 100644 index cb55afe..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthRabbitMQService.java +++ /dev/null @@ -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 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 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 - ); - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthRedisService.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthRedisService.java deleted file mode 100644 index 79935f7..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthRedisService.java +++ /dev/null @@ -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 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); - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthResult.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthResult.java deleted file mode 100644 index 3ee6a58..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthResult.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthService.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthService.java deleted file mode 100644 index fa87491..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/AuthService.java +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/impl/AuthServiceImpl.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/impl/AuthServiceImpl.java deleted file mode 100644 index 1e69b31..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/service/impl/AuthServiceImpl.java +++ /dev/null @@ -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 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; - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/java/com/jiebanke/auth/util/JwtUtil.java b/backend-java/auth-service/src/main/java/com/jiebanke/auth/util/JwtUtil.java deleted file mode 100644 index b2c7343..0000000 --- a/backend-java/auth-service/src/main/java/com/jiebanke/auth/util/JwtUtil.java +++ /dev/null @@ -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 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 声明类型 - * @return 声明值 - */ - public T extractClaim(String token, Function 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 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(); - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/main/resources/application.yml b/backend-java/auth-service/src/main/resources/application.yml deleted file mode 100644 index 8a4c2fc..0000000 --- a/backend-java/auth-service/src/main/resources/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/auth-service/src/test/java/com/jiebanke/auth/service/AuthRabbitMQServiceTest.java b/backend-java/auth-service/src/test/java/com/jiebanke/auth/service/AuthRabbitMQServiceTest.java deleted file mode 100644 index 2bf8097..0000000 --- a/backend-java/auth-service/src/test/java/com/jiebanke/auth/service/AuthRabbitMQServiceTest.java +++ /dev/null @@ -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() - ); - } -} \ No newline at end of file diff --git a/backend-java/auth-service/src/test/java/com/jiebanke/auth/service/AuthRedisServiceTest.java b/backend-java/auth-service/src/test/java/com/jiebanke/auth/service/AuthRedisServiceTest.java deleted file mode 100644 index 5486396..0000000 --- a/backend-java/auth-service/src/test/java/com/jiebanke/auth/service/AuthRedisServiceTest.java +++ /dev/null @@ -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 redisTemplate; - - @Mock - private ValueOperations 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); - } -} \ No newline at end of file diff --git a/backend-java/auth-service/target/classes/application.yml b/backend-java/auth-service/target/classes/application.yml deleted file mode 100644 index 8a4c2fc..0000000 --- a/backend-java/auth-service/target/classes/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/build-services.sh b/backend-java/build-services.sh deleted file mode 100755 index 5046eca..0000000 --- a/backend-java/build-services.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/common/pom.xml b/backend-java/common/pom.xml deleted file mode 100644 index 9e13092..0000000 --- a/backend-java/common/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - common - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-starter-validation - - - \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/config/FeignConfig.java b/backend-java/common/src/main/java/com/jiebanke/common/config/FeignConfig.java deleted file mode 100644 index a8023e5..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/config/FeignConfig.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/config/RabbitMQConfig.java b/backend-java/common/src/main/java/com/jiebanke/common/config/RabbitMQConfig.java deleted file mode 100644 index 9873443..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/config/RabbitMQConfig.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/config/RedisConfig.java b/backend-java/common/src/main/java/com/jiebanke/common/config/RedisConfig.java deleted file mode 100644 index 508350a..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/config/RedisConfig.java +++ /dev/null @@ -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 redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - template.setKeySerializer(new StringRedisSerializer()); - template.setValueSerializer(new StringRedisSerializer()); - return template; - } -} \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/entity/BaseEntity.java b/backend-java/common/src/main/java/com/jiebanke/common/entity/BaseEntity.java deleted file mode 100644 index a4ac4f6..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/entity/BaseEntity.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/exception/BusinessException.java b/backend-java/common/src/main/java/com/jiebanke/common/exception/BusinessException.java deleted file mode 100644 index 4269717..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/exception/BusinessException.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/exception/GlobalExceptionHandler.java b/backend-java/common/src/main/java/com/jiebanke/common/exception/GlobalExceptionHandler.java deleted file mode 100644 index af75f3f..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/exception/GlobalExceptionHandler.java +++ /dev/null @@ -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 handleBusinessException(BusinessException e) { - return ApiResponse.error(e.getCode(), e.getMessage()); - } - - @ExceptionHandler(Exception.class) - public ApiResponse handleException(Exception e) { - e.printStackTrace(); - return ApiResponse.error(500, "服务器内部错误"); - } -} \ No newline at end of file diff --git a/backend-java/common/src/main/java/com/jiebanke/common/vo/ApiResponse.java b/backend-java/common/src/main/java/com/jiebanke/common/vo/ApiResponse.java deleted file mode 100644 index 871ae2c..0000000 --- a/backend-java/common/src/main/java/com/jiebanke/common/vo/ApiResponse.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jiebanke.common.vo; - -import lombok.Data; - -@Data -public class ApiResponse { - private boolean success; - private int code; - private String message; - private T data; - private long timestamp; - - public static ApiResponse success(T data) { - ApiResponse response = new ApiResponse<>(); - response.success = true; - response.code = 200; - response.message = "操作成功"; - response.data = data; - response.timestamp = System.currentTimeMillis(); - return response; - } - - public static ApiResponse success(T data, String message) { - ApiResponse response = new ApiResponse<>(); - response.success = true; - response.code = 200; - response.message = message; - response.data = data; - response.timestamp = System.currentTimeMillis(); - return response; - } - - public static ApiResponse error(int code, String message) { - ApiResponse response = new ApiResponse<>(); - response.success = false; - response.code = code; - response.message = message; - response.timestamp = System.currentTimeMillis(); - return response; - } -} \ No newline at end of file diff --git a/backend-java/common/target/classes/com/jiebanke/common/config/FeignConfig.class b/backend-java/common/target/classes/com/jiebanke/common/config/FeignConfig.class deleted file mode 100644 index 69bac09..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/config/FeignConfig.class and /dev/null differ diff --git a/backend-java/common/target/classes/com/jiebanke/common/config/RabbitMQConfig.class b/backend-java/common/target/classes/com/jiebanke/common/config/RabbitMQConfig.class deleted file mode 100644 index 1c02901..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/config/RabbitMQConfig.class and /dev/null differ diff --git a/backend-java/common/target/classes/com/jiebanke/common/config/RedisConfig.class b/backend-java/common/target/classes/com/jiebanke/common/config/RedisConfig.class deleted file mode 100644 index 602795c..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/config/RedisConfig.class and /dev/null differ diff --git a/backend-java/common/target/classes/com/jiebanke/common/entity/BaseEntity.class b/backend-java/common/target/classes/com/jiebanke/common/entity/BaseEntity.class deleted file mode 100644 index f684fcd..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/entity/BaseEntity.class and /dev/null differ diff --git a/backend-java/common/target/classes/com/jiebanke/common/exception/BusinessException.class b/backend-java/common/target/classes/com/jiebanke/common/exception/BusinessException.class deleted file mode 100644 index 81b5a2f..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/exception/BusinessException.class and /dev/null differ diff --git a/backend-java/common/target/classes/com/jiebanke/common/exception/GlobalExceptionHandler.class b/backend-java/common/target/classes/com/jiebanke/common/exception/GlobalExceptionHandler.class deleted file mode 100644 index 6b85636..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/exception/GlobalExceptionHandler.class and /dev/null differ diff --git a/backend-java/common/target/classes/com/jiebanke/common/vo/ApiResponse.class b/backend-java/common/target/classes/com/jiebanke/common/vo/ApiResponse.class deleted file mode 100644 index b4db823..0000000 Binary files a/backend-java/common/target/classes/com/jiebanke/common/vo/ApiResponse.class and /dev/null differ diff --git a/backend-java/docker-compose.yml b/backend-java/docker-compose.yml deleted file mode 100644 index 6fa800e..0000000 --- a/backend-java/docker-compose.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/eureka-server/pom.xml b/backend-java/eureka-server/pom.xml deleted file mode 100644 index b97c511..0000000 --- a/backend-java/eureka-server/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - eureka-server - jar - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-server - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/eureka-server/src/main/java/com/jiebanke/eureka/EurekaServerApplication.java b/backend-java/eureka-server/src/main/java/com/jiebanke/eureka/EurekaServerApplication.java deleted file mode 100644 index 136165e..0000000 --- a/backend-java/eureka-server/src/main/java/com/jiebanke/eureka/EurekaServerApplication.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/backend-java/eureka-server/src/main/resources/application.yml b/backend-java/eureka-server/src/main/resources/application.yml deleted file mode 100644 index b4c0e4b..0000000 --- a/backend-java/eureka-server/src/main/resources/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/eureka-server/target/classes/application.yml b/backend-java/eureka-server/target/classes/application.yml deleted file mode 100644 index b4c0e4b..0000000 --- a/backend-java/eureka-server/target/classes/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/eureka-server/target/classes/com/jiebanke/eureka/EurekaServerApplication.class b/backend-java/eureka-server/target/classes/com/jiebanke/eureka/EurekaServerApplication.class deleted file mode 100644 index 471a7ad..0000000 Binary files a/backend-java/eureka-server/target/classes/com/jiebanke/eureka/EurekaServerApplication.class and /dev/null differ diff --git a/backend-java/gateway-service/pom.xml b/backend-java/gateway-service/pom.xml deleted file mode 100644 index 82c3ba4..0000000 --- a/backend-java/gateway-service/pom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - gateway-service - jar - - - - - org.springframework.cloud - spring-cloud-starter-gateway - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - runtime - - - io.jsonwebtoken - jjwt-jackson - runtime - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/gateway-service/src/main/java/com/jiebanke/gateway/GatewayApplication.java b/backend-java/gateway-service/src/main/java/com/jiebanke/gateway/GatewayApplication.java deleted file mode 100644 index f73b552..0000000 --- a/backend-java/gateway-service/src/main/java/com/jiebanke/gateway/GatewayApplication.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/backend-java/gateway-service/src/main/resources/application.yml b/backend-java/gateway-service/src/main/resources/application.yml deleted file mode 100644 index 20b2330..0000000 --- a/backend-java/gateway-service/src/main/resources/application.yml +++ /dev/null @@ -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/ \ No newline at end of file diff --git a/backend-java/gateway-service/target/classes/application.yml b/backend-java/gateway-service/target/classes/application.yml deleted file mode 100644 index 20b2330..0000000 --- a/backend-java/gateway-service/target/classes/application.yml +++ /dev/null @@ -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/ \ No newline at end of file diff --git a/backend-java/gateway-service/target/classes/com/jiebanke/gateway/GatewayApplication.class b/backend-java/gateway-service/target/classes/com/jiebanke/gateway/GatewayApplication.class deleted file mode 100644 index b9f4f5f..0000000 Binary files a/backend-java/gateway-service/target/classes/com/jiebanke/gateway/GatewayApplication.class and /dev/null differ diff --git a/backend-java/order-service/pom.xml b/backend-java/order-service/pom.xml deleted file mode 100644 index e641995..0000000 --- a/backend-java/order-service/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - order-service - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - mysql - mysql-connector-java - - - - - com.jiebanke - common - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/order-service/src/main/java/com/jiebanke/order/OrderApplication.java b/backend-java/order-service/src/main/java/com/jiebanke/order/OrderApplication.java deleted file mode 100644 index 70f64fc..0000000 --- a/backend-java/order-service/src/main/java/com/jiebanke/order/OrderApplication.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/backend-java/order-service/src/main/java/com/jiebanke/order/controller/OrderController.java b/backend-java/order-service/src/main/java/com/jiebanke/order/controller/OrderController.java deleted file mode 100644 index 5442b50..0000000 --- a/backend-java/order-service/src/main/java/com/jiebanke/order/controller/OrderController.java +++ /dev/null @@ -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> createOrder( - @RequestHeader("userId") Long userId, - @RequestBody Order order) { - - Long orderId = orderService.createOrder(order, userId); - Order createdOrder = orderService.getOrderById(orderId); - - Map result = new HashMap<>(); - result.put("order", createdOrder); - result.put("message", "订单创建成功"); - - return ApiResponse.success(result); - } - - /** - * 获取订单详情 - */ - @GetMapping("/{orderId}") - public ApiResponse getOrder(@PathVariable Long orderId) { - Order order = orderService.getOrderById(orderId); - return ApiResponse.success(order); - } - - /** - * 获取用户订单列表 - */ - @GetMapping - public ApiResponse> getUserOrders( - @RequestHeader("userId") Long userId, - @RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer pageSize, - @RequestParam(required = false) String status) { - - IPage orders = orderService.getUserOrders(userId, page, pageSize, status); - - Map 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> updateOrderStatus( - @PathVariable Long orderId, - @RequestBody Map requestBody, - @RequestHeader("userId") Long userId) { - - String status = requestBody.get("status"); - Order updatedOrder = orderService.updateOrderStatus(orderId, status, userId); - - Map result = new HashMap<>(); - result.put("order", updatedOrder); - result.put("message", "订单状态更新成功"); - - return ApiResponse.success(result); - } - - /** - * 删除订单 - */ - @DeleteMapping("/{orderId}") - public ApiResponse> deleteOrder( - @PathVariable Long orderId, - @RequestHeader("userId") Long userId) { - - boolean deleted = orderService.deleteOrder(orderId, userId); - - Map result = new HashMap<>(); - result.put("message", "订单删除成功"); - result.put("orderId", orderId); - - return ApiResponse.success(result); - } - - /** - * 获取订单统计信息 - */ - @GetMapping("/statistics") - public ApiResponse> getOrderStatistics(@RequestHeader("merchantId") Long merchantId) { - Map statistics = orderService.getOrderStats(merchantId); - return ApiResponse.success(statistics); - } - - /** - * 管理员获取所有订单 - */ - @GetMapping("/admin") - public ApiResponse> 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 orders = orderService.getAllOrders(page, pageSize, status, merchantId, userId); - - Map 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); - } -} \ No newline at end of file diff --git a/backend-java/order-service/src/main/java/com/jiebanke/order/entity/Order.java b/backend-java/order-service/src/main/java/com/jiebanke/order/entity/Order.java deleted file mode 100644 index d7e3494..0000000 --- a/backend-java/order-service/src/main/java/com/jiebanke/order/entity/Order.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/order-service/src/main/java/com/jiebanke/order/mapper/OrderMapper.java b/backend-java/order-service/src/main/java/com/jiebanke/order/mapper/OrderMapper.java deleted file mode 100644 index e4c78fd..0000000 --- a/backend-java/order-service/src/main/java/com/jiebanke/order/mapper/OrderMapper.java +++ /dev/null @@ -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 { - - /** - * 根据用户ID获取订单列表 - * @param userId 用户ID - * @return 订单列表 - */ - @Select("SELECT * FROM orders WHERE user_id = #{userId} ORDER BY created_at DESC") - List selectByUserId(@Param("userId") Long userId); - - /** - * 根据状态获取订单列表 - * @param status 状态 - * @return 订单列表 - */ - @Select("SELECT * FROM orders WHERE status = #{status} ORDER BY created_at DESC") - List selectByStatus(@Param("status") String status); - - /** - * 根据订单号获取订单 - * @param orderNo 订单号 - * @return 订单 - */ - @Select("SELECT * FROM orders WHERE order_no = #{orderNo}") - Order selectByOrderNo(@Param("orderNo") String orderNo); -} \ No newline at end of file diff --git a/backend-java/order-service/src/main/java/com/jiebanke/order/service/OrderService.java b/backend-java/order-service/src/main/java/com/jiebanke/order/service/OrderService.java deleted file mode 100644 index 8d5215e..0000000 --- a/backend-java/order-service/src/main/java/com/jiebanke/order/service/OrderService.java +++ /dev/null @@ -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 { - - /** - * 创建订单 - * @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 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 getOrderStats(Long merchantId); - - /** - * 获取所有订单(管理员) - * @param page 页码 - * @param pageSize 每页数量 - * @param status 状态 - * @param merchantId 商家ID - * @param userId 用户ID - * @return 订单分页列表 - */ - IPage getAllOrders(Integer page, Integer pageSize, String status, Long merchantId, Long userId); -} \ No newline at end of file diff --git a/backend-java/order-service/src/main/java/com/jiebanke/order/service/impl/OrderServiceImpl.java b/backend-java/order-service/src/main/java/com/jiebanke/order/service/impl/OrderServiceImpl.java deleted file mode 100644 index 306fd14..0000000 --- a/backend-java/order-service/src/main/java/com/jiebanke/order/service/impl/OrderServiceImpl.java +++ /dev/null @@ -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 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 getUserOrders(Long userId, Integer page, Integer pageSize, String status) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("user_id", userId); - - if (status != null && !status.isEmpty()) { - queryWrapper.eq("status", status); - } - - queryWrapper.orderByDesc("created_at"); - - Page 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 getOrderStats(Long merchantId) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("merchant_id", merchantId); - - int totalOrders = Math.toIntExact(this.count(queryWrapper)); - - QueryWrapper pendingWrapper = new QueryWrapper<>(); - pendingWrapper.eq("merchant_id", merchantId).eq("status", "pending"); - int pendingOrders = Math.toIntExact(this.count(pendingWrapper)); - - QueryWrapper processingWrapper = new QueryWrapper<>(); - processingWrapper.eq("merchant_id", merchantId).eq("status", "processing"); - int processingOrders = Math.toIntExact(this.count(processingWrapper)); - - QueryWrapper completedWrapper = new QueryWrapper<>(); - completedWrapper.eq("merchant_id", merchantId).eq("status", "completed"); - int completedOrders = Math.toIntExact(this.count(completedWrapper)); - - QueryWrapper cancelledWrapper = new QueryWrapper<>(); - cancelledWrapper.eq("merchant_id", merchantId).eq("status", "cancelled"); - int cancelledOrders = Math.toIntExact(this.count(cancelledWrapper)); - - QueryWrapper failedWrapper = new QueryWrapper<>(); - failedWrapper.eq("merchant_id", merchantId).eq("status", "failed"); - int failedOrders = Math.toIntExact(this.count(failedWrapper)); - - // 计算总收入 - QueryWrapper revenueWrapper = new QueryWrapper<>(); - revenueWrapper.eq("merchant_id", merchantId).select("SUM(amount) as totalRevenue"); - Map revenueMap = this.getMap(revenueWrapper); - BigDecimal totalRevenue = revenueMap != null && revenueMap.get("totalRevenue") != null ? - new BigDecimal(revenueMap.get("totalRevenue").toString()) : BigDecimal.ZERO; - - Map 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 getAllOrders(Integer page, Integer pageSize, String status, Long merchantId, Long userId) { - QueryWrapper 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 pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10); - return this.page(pageObj, queryWrapper); - } -} \ No newline at end of file diff --git a/backend-java/order-service/src/main/resources/application.yml b/backend-java/order-service/src/main/resources/application.yml deleted file mode 100644 index 883e008..0000000 --- a/backend-java/order-service/src/main/resources/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/order-service/target/classes/application.yml b/backend-java/order-service/target/classes/application.yml deleted file mode 100644 index 883e008..0000000 --- a/backend-java/order-service/target/classes/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/order-service/target/classes/com/jiebanke/order/OrderApplication.class b/backend-java/order-service/target/classes/com/jiebanke/order/OrderApplication.class deleted file mode 100644 index 3c67771..0000000 Binary files a/backend-java/order-service/target/classes/com/jiebanke/order/OrderApplication.class and /dev/null differ diff --git a/backend-java/order-service/target/classes/com/jiebanke/order/controller/OrderController.class b/backend-java/order-service/target/classes/com/jiebanke/order/controller/OrderController.class deleted file mode 100644 index 54ae73e..0000000 Binary files a/backend-java/order-service/target/classes/com/jiebanke/order/controller/OrderController.class and /dev/null differ diff --git a/backend-java/order-service/target/classes/com/jiebanke/order/entity/Order.class b/backend-java/order-service/target/classes/com/jiebanke/order/entity/Order.class deleted file mode 100644 index 25c0933..0000000 Binary files a/backend-java/order-service/target/classes/com/jiebanke/order/entity/Order.class and /dev/null differ diff --git a/backend-java/order-service/target/classes/com/jiebanke/order/mapper/OrderMapper.class b/backend-java/order-service/target/classes/com/jiebanke/order/mapper/OrderMapper.class deleted file mode 100644 index 2fcf551..0000000 Binary files a/backend-java/order-service/target/classes/com/jiebanke/order/mapper/OrderMapper.class and /dev/null differ diff --git a/backend-java/order-service/target/classes/com/jiebanke/order/service/OrderService.class b/backend-java/order-service/target/classes/com/jiebanke/order/service/OrderService.class deleted file mode 100644 index 0572608..0000000 Binary files a/backend-java/order-service/target/classes/com/jiebanke/order/service/OrderService.class and /dev/null differ diff --git a/backend-java/order-service/target/classes/com/jiebanke/order/service/impl/OrderServiceImpl.class b/backend-java/order-service/target/classes/com/jiebanke/order/service/impl/OrderServiceImpl.class deleted file mode 100644 index c2be512..0000000 Binary files a/backend-java/order-service/target/classes/com/jiebanke/order/service/impl/OrderServiceImpl.class and /dev/null differ diff --git a/backend-java/pom.xml b/backend-java/pom.xml deleted file mode 100644 index 8f56e35..0000000 --- a/backend-java/pom.xml +++ /dev/null @@ -1,163 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - pom - 结伴客Java后端 - 结伴客Java微服务架构后端系统 - - - eureka-server - gateway-service - auth-service - user-service - travel-service - animal-service - order-service - promotion-service - common - - - - 17 - 17 - UTF-8 - 3.1.0 - 2022.0.3 - 8.0.33 - 3.5.3.1 - 5.9.2 - 5.2.0 - - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring.boot.version} - pom - import - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring.cloud.version} - pom - import - - - - - mysql - mysql-connector-java - ${mysql.version} - - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis.plus.version} - - - - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - runtime - - - - - org.springframework.boot - spring-boot-starter-data-redis - ${spring.boot.version} - - - - - org.springframework.boot - spring-boot-starter-amqp - ${spring.boot.version} - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - ${spring.cloud.version} - - - - - org.junit.jupiter - junit-jupiter - ${junit.version} - test - - - - - org.mockito - mockito-core - ${mockito.version} - test - - - - - org.springframework.boot - spring-boot-starter-test - ${spring.boot.version} - test - - - - - com.jiebanke - common - ${project.version} - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - org.projectlombok - lombok - - - - - - - \ No newline at end of file diff --git a/backend-java/promotion-service/pom.xml b/backend-java/promotion-service/pom.xml deleted file mode 100644 index e7c423b..0000000 --- a/backend-java/promotion-service/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - promotion-service - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - mysql - mysql-connector-java - - - - - com.jiebanke - common - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/PromotionApplication.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/PromotionApplication.java deleted file mode 100644 index 887e2ee..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/PromotionApplication.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/controller/PromotionController.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/controller/PromotionController.java deleted file mode 100644 index 15326b7..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/controller/PromotionController.java +++ /dev/null @@ -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> getPromotionActivities( - @RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer pageSize, - @RequestParam(required = false) String name, - @RequestParam(required = false) String status) { - - IPage activities = promotionService.getPromotionActivities(page, pageSize, name, status); - - Map 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 getPromotionActivity(@PathVariable Long id) { - PromotionActivity activity = promotionService.getPromotionActivityById(id); - return ApiResponse.success(activity); - } - - /** - * 创建推广活动 - */ - @PostMapping("/activities") - public ApiResponse> createPromotionActivity(@RequestBody PromotionActivity activity) { - Long activityId = promotionService.createPromotionActivity(activity); - PromotionActivity createdActivity = promotionService.getPromotionActivityById(activityId); - - Map result = new HashMap<>(); - result.put("activity", createdActivity); - result.put("message", "推广活动创建成功"); - - return ApiResponse.success(result); - } - - /** - * 更新推广活动 - */ - @PutMapping("/activities/{id}") - public ApiResponse> updatePromotionActivity( - @PathVariable Long id, - @RequestBody PromotionActivity activity) { - - PromotionActivity updatedActivity = promotionService.updatePromotionActivity(id, activity); - - Map result = new HashMap<>(); - result.put("activity", updatedActivity); - result.put("message", "推广活动更新成功"); - - return ApiResponse.success(result); - } - - /** - * 删除推广活动 - */ - @DeleteMapping("/activities/{id}") - public ApiResponse> deletePromotionActivity(@PathVariable Long id) { - boolean deleted = promotionService.deletePromotionActivity(id); - - Map result = new HashMap<>(); - result.put("message", "推广活动删除成功"); - result.put("id", id); - - return ApiResponse.success(result); - } - - /** - * 暂停推广活动 - */ - @PostMapping("/activities/{id}/pause") - public ApiResponse> pausePromotionActivity(@PathVariable Long id) { - boolean paused = promotionService.pausePromotionActivity(id); - - Map result = new HashMap<>(); - result.put("message", "推广活动已暂停"); - result.put("id", id); - - return ApiResponse.success(result); - } - - /** - * 恢复推广活动 - */ - @PostMapping("/activities/{id}/resume") - public ApiResponse> resumePromotionActivity(@PathVariable Long id) { - boolean resumed = promotionService.resumePromotionActivity(id); - - Map result = new HashMap<>(); - result.put("message", "推广活动已恢复"); - result.put("id", id); - - return ApiResponse.success(result); - } - - /** - * 获取奖励记录列表 - */ - @GetMapping("/rewards") - public ApiResponse> 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 rewards = promotionService.getRewardRecords(page, pageSize, user, rewardType, status); - - Map 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> issueReward(@PathVariable Long id) { - boolean issued = promotionService.issueReward(id); - - Map result = new HashMap<>(); - result.put("message", "奖励已发放"); - result.put("id", id); - - return ApiResponse.success(result); - } - - /** - * 获取推广统计数据 - */ - @GetMapping("/statistics") - public ApiResponse> getPromotionStatistics() { - Map statistics = promotionService.getPromotionStatistics(); - return ApiResponse.success(statistics); - } -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/entity/PromotionActivity.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/entity/PromotionActivity.java deleted file mode 100644 index b58759e..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/entity/PromotionActivity.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/entity/RewardRecord.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/entity/RewardRecord.java deleted file mode 100644 index 0aabd52..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/entity/RewardRecord.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/mapper/PromotionActivityMapper.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/mapper/PromotionActivityMapper.java deleted file mode 100644 index 6728ce1..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/mapper/PromotionActivityMapper.java +++ /dev/null @@ -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 { - - /** - * 根据状态获取推广活动列表 - * @param status 状态 - * @return 推广活动列表 - */ - @Select("SELECT * FROM promotion_activities WHERE status = #{status} ORDER BY created_at DESC") - List selectByStatus(@Param("status") String status); - - /** - * 根据名称模糊查询推广活动 - * @param name 名称 - * @return 推广活动列表 - */ - @Select("SELECT * FROM promotion_activities WHERE name LIKE CONCAT('%', #{name}, '%') ORDER BY created_at DESC") - List 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); -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/mapper/RewardRecordMapper.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/mapper/RewardRecordMapper.java deleted file mode 100644 index f9e3568..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/mapper/RewardRecordMapper.java +++ /dev/null @@ -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 { - - /** - * 根据用户ID获取奖励记录列表 - * @param userId 用户ID - * @return 奖励记录列表 - */ - @Select("SELECT * FROM reward_records WHERE user_id = #{userId} ORDER BY created_at DESC") - List selectByUserId(@Param("userId") Long userId); - - /** - * 根据状态获取奖励记录列表 - * @param status 状态 - * @return 奖励记录列表 - */ - @Select("SELECT * FROM reward_records WHERE status = #{status} ORDER BY created_at DESC") - List 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); -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/service/PromotionService.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/service/PromotionService.java deleted file mode 100644 index 75892e8..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/service/PromotionService.java +++ /dev/null @@ -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 { - - /** - * 获取推广活动列表 - * @param page 页码 - * @param pageSize 每页数量 - * @param name 活动名称 - * @param status 状态 - * @return 推广活动分页列表 - */ - IPage 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 getRewardRecords(Integer page, Integer pageSize, String user, String rewardType, String status); - - /** - * 发放奖励 - * @param id 奖励记录ID - * @return 是否发放成功 - */ - boolean issueReward(Long id); - - /** - * 获取推广统计数据 - * @return 统计数据 - */ - Map getPromotionStatistics(); -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/service/impl/PromotionServiceImpl.java b/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/service/impl/PromotionServiceImpl.java deleted file mode 100644 index 3d4ed88..0000000 --- a/backend-java/promotion-service/src/main/java/com/jiebanke/promotion/service/impl/PromotionServiceImpl.java +++ /dev/null @@ -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 implements PromotionService { - - @Autowired - private PromotionActivityMapper promotionActivityMapper; - - @Autowired - private RewardRecordMapper rewardRecordMapper; - - @Override - public IPage getPromotionActivities(Integer page, Integer pageSize, String name, String status) { - QueryWrapper 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 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 getRewardRecords(Integer page, Integer pageSize, String user, String rewardType, String status) { - QueryWrapper 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 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 getPromotionStatistics() { - // 获取活动总数 - int totalActivities = Math.toIntExact(this.count()); - - // 获取进行中的活动数 - QueryWrapper activeWrapper = new QueryWrapper<>(); - activeWrapper.eq("status", "active"); - int activeActivities = Math.toIntExact(this.count(activeWrapper)); - - // 获取奖励记录总数 - int totalRewards = Math.toIntExact(rewardRecordMapper.selectCount(null)); - - // 获取已发放的奖励数 - QueryWrapper issuedWrapper = new QueryWrapper<>(); - issuedWrapper.eq("status", "issued"); - int issuedRewards = Math.toIntExact(rewardRecordMapper.selectCount(issuedWrapper)); - - // 计算总奖励金额 - QueryWrapper amountWrapper = new QueryWrapper<>(); - amountWrapper.eq("status", "issued").select("SUM(reward_amount) as totalAmount"); - Map amountMap = rewardRecordMapper.selectMap(amountWrapper); - BigDecimal totalAmount = amountMap != null && amountMap.get("totalAmount") != null ? - new BigDecimal(amountMap.get("totalAmount").toString()) : BigDecimal.ZERO; - - Map 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; - } -} \ No newline at end of file diff --git a/backend-java/promotion-service/src/main/resources/application.yml b/backend-java/promotion-service/src/main/resources/application.yml deleted file mode 100644 index 6d19d0a..0000000 --- a/backend-java/promotion-service/src/main/resources/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/promotion-service/target/classes/application.yml b/backend-java/promotion-service/target/classes/application.yml deleted file mode 100644 index 6d19d0a..0000000 --- a/backend-java/promotion-service/target/classes/application.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/PromotionApplication.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/PromotionApplication.class deleted file mode 100644 index e8e1fad..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/PromotionApplication.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/controller/PromotionController.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/controller/PromotionController.class deleted file mode 100644 index 480882e..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/controller/PromotionController.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/entity/PromotionActivity.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/entity/PromotionActivity.class deleted file mode 100644 index bcbb658..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/entity/PromotionActivity.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/entity/RewardRecord.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/entity/RewardRecord.class deleted file mode 100644 index fc95afa..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/entity/RewardRecord.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/mapper/PromotionActivityMapper.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/mapper/PromotionActivityMapper.class deleted file mode 100644 index 97a1fa1..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/mapper/PromotionActivityMapper.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/mapper/RewardRecordMapper.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/mapper/RewardRecordMapper.class deleted file mode 100644 index 6882f1a..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/mapper/RewardRecordMapper.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/service/PromotionService.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/service/PromotionService.class deleted file mode 100644 index d05276c..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/service/PromotionService.class and /dev/null differ diff --git a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/service/impl/PromotionServiceImpl.class b/backend-java/promotion-service/target/classes/com/jiebanke/promotion/service/impl/PromotionServiceImpl.class deleted file mode 100644 index ce78909..0000000 Binary files a/backend-java/promotion-service/target/classes/com/jiebanke/promotion/service/impl/PromotionServiceImpl.class and /dev/null differ diff --git a/backend-java/scripts/init-database.sql b/backend-java/scripts/init-database.sql deleted file mode 100644 index a08edcb..0000000 --- a/backend-java/scripts/init-database.sql +++ /dev/null @@ -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); \ No newline at end of file diff --git a/backend-java/start-services.sh b/backend-java/start-services.sh deleted file mode 100755 index 5a8040b..0000000 --- a/backend-java/start-services.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/backend-java/stop-services.sh b/backend-java/stop-services.sh deleted file mode 100755 index 03c9337..0000000 --- a/backend-java/stop-services.sh +++ /dev/null @@ -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 "清理完成" \ No newline at end of file diff --git a/backend-java/travel-service/pom.xml b/backend-java/travel-service/pom.xml deleted file mode 100644 index 197395b..0000000 --- a/backend-java/travel-service/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - travel-service - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - mysql - mysql-connector-java - - - - - com.jiebanke - common - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/TravelApplication.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/TravelApplication.java deleted file mode 100644 index 891088b..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/TravelApplication.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/controller/TravelPlanController.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/controller/TravelPlanController.java deleted file mode 100644 index 6f13b3a..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/controller/TravelPlanController.java +++ /dev/null @@ -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> getTravelPlans( - @RequestHeader("userId") Long userId, - @RequestParam(defaultValue = "1") Integer page, - @RequestParam(defaultValue = "10") Integer pageSize, - @RequestParam(required = false) String status) { - - IPage plans = travelPlanService.getTravelPlans(userId, page, pageSize, status); - - Map 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 getTravelPlan(@PathVariable Long planId) { - TravelPlan plan = travelPlanService.getTravelPlanById(planId); - return ApiResponse.success(plan); - } - - /** - * 创建旅行计划 - */ - @PostMapping("/plans") - public ApiResponse> createTravelPlan( - @RequestHeader("userId") Long userId, - @RequestBody TravelPlan travelPlan) { - - Long planId = travelPlanService.createTravelPlan(userId, travelPlan); - TravelPlan plan = travelPlanService.getTravelPlanById(planId); - - Map result = new HashMap<>(); - result.put("plan", plan); - result.put("message", "旅行计划创建成功"); - - return ApiResponse.success(result); - } - - /** - * 更新旅行计划 - */ - @PutMapping("/plans/{planId}") - public ApiResponse> updateTravelPlan( - @PathVariable Long planId, - @RequestHeader("userId") Long userId, - @RequestBody TravelPlan travelPlan) { - - TravelPlan updatedPlan = travelPlanService.updateTravelPlan(planId, userId, travelPlan); - - Map result = new HashMap<>(); - result.put("plan", updatedPlan); - result.put("message", "旅行计划更新成功"); - - return ApiResponse.success(result); - } - - /** - * 删除旅行计划 - */ - @DeleteMapping("/plans/{planId}") - public ApiResponse> deleteTravelPlan( - @PathVariable Long planId, - @RequestHeader("userId") Long userId) { - - boolean deleted = travelPlanService.deleteTravelPlan(planId, userId); - - Map result = new HashMap<>(); - result.put("message", "旅行计划删除成功"); - result.put("planId", planId); - - return ApiResponse.success(result); - } - - /** - * 获取用户旅行统计 - */ - @GetMapping("/stats") - public ApiResponse getTravelStats(@RequestHeader("userId") Long userId) { - TravelStats stats = travelPlanService.getUserTravelStats(userId); - return ApiResponse.success(stats); - } -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/entity/TravelPlan.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/entity/TravelPlan.java deleted file mode 100644 index 58f1c38..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/entity/TravelPlan.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.jiebanke.travel.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 TravelPlan extends BaseEntity { - private Long userId; - private String destination; - private LocalDate startDate; - private LocalDate endDate; - private BigDecimal budget; - private String interests; - private String description; - private String visibility; - private String status; -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/mapper/TravelPlanMapper.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/mapper/TravelPlanMapper.java deleted file mode 100644 index a05f476..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/mapper/TravelPlanMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jiebanke.travel.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.jiebanke.travel.entity.TravelPlan; -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 TravelPlanMapper extends BaseMapper { - - /** - * 根据用户ID获取旅行计划列表 - * @param userId 用户ID - * @return 旅行计划列表 - */ - @Select("SELECT * FROM travel_plans WHERE user_id = #{userId} ORDER BY created_at DESC") - List selectByUserId(@Param("userId") Long userId); - - /** - * 根据状态获取旅行计划列表 - * @param status 状态 - * @return 旅行计划列表 - */ - @Select("SELECT * FROM travel_plans WHERE status = #{status} ORDER BY created_at DESC") - List selectByStatus(@Param("status") String status); -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/TravelPlanService.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/TravelPlanService.java deleted file mode 100644 index 3a7f2f2..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/TravelPlanService.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.jiebanke.travel.service; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.IService; -import com.jiebanke.travel.entity.TravelPlan; - -import java.util.List; - -public interface TravelPlanService extends IService { - - /** - * 获取旅行计划列表 - * @param userId 用户ID - * @param page 页码 - * @param pageSize 每页数量 - * @param status 状态 - * @return 旅行计划分页列表 - */ - IPage getTravelPlans(Long userId, Integer page, Integer pageSize, String status); - - /** - * 获取单个旅行计划详情 - * @param planId 计划ID - * @return 旅行计划 - */ - TravelPlan getTravelPlanById(Long planId); - - /** - * 创建旅行计划 - * @param userId 用户ID - * @param travelPlan 旅行计划信息 - * @return 创建的旅行计划ID - */ - Long createTravelPlan(Long userId, TravelPlan travelPlan); - - /** - * 更新旅行计划 - * @param planId 计划ID - * @param userId 用户ID - * @param travelPlan 更新的旅行计划信息 - * @return 更新后的旅行计划 - */ - TravelPlan updateTravelPlan(Long planId, Long userId, TravelPlan travelPlan); - - /** - * 删除旅行计划 - * @param planId 计划ID - * @param userId 用户ID - * @return 是否删除成功 - */ - boolean deleteTravelPlan(Long planId, Long userId); - - /** - * 获取用户旅行统计 - * @param userId 用户ID - * @return 统计信息 - */ - TravelStats getUserTravelStats(Long userId); -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/TravelStats.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/TravelStats.java deleted file mode 100644 index e96d73b..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/TravelStats.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.jiebanke.travel.service; - -import lombok.Data; - -import java.math.BigDecimal; - -@Data -public class TravelStats { - private Integer totalPlans; - private Integer completedPlans; - private Integer planningPlans; - private Integer cancelledPlans; - private BigDecimal totalBudget; -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/impl/TravelPlanServiceImpl.java b/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/impl/TravelPlanServiceImpl.java deleted file mode 100644 index d9f49cb..0000000 --- a/backend-java/travel-service/src/main/java/com/jiebanke/travel/service/impl/TravelPlanServiceImpl.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.jiebanke.travel.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.travel.entity.TravelPlan; -import com.jiebanke.travel.mapper.TravelPlanMapper; -import com.jiebanke.travel.service.TravelPlanService; -import com.jiebanke.travel.service.TravelStats; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class TravelPlanServiceImpl extends ServiceImpl implements TravelPlanService { - - @Override - public IPage getTravelPlans(Long userId, Integer page, Integer pageSize, String status) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - - if (userId != null) { - queryWrapper.eq("user_id", userId); - } - - if (status != null && !status.isEmpty()) { - queryWrapper.eq("status", status); - } - - queryWrapper.orderByDesc("created_at"); - - Page pageObj = new Page<>(page != null ? page : 1, pageSize != null ? pageSize : 10); - return this.page(pageObj, queryWrapper); - } - - @Override - public TravelPlan getTravelPlanById(Long planId) { - TravelPlan travelPlan = this.getById(planId); - if (travelPlan == null) { - throw new BusinessException("旅行计划不存在"); - } - return travelPlan; - } - - @Override - public Long createTravelPlan(Long userId, TravelPlan travelPlan) { - travelPlan.setUserId(userId); - this.save(travelPlan); - return travelPlan.getId(); - } - - @Override - public TravelPlan updateTravelPlan(Long planId, Long userId, TravelPlan travelPlan) { - TravelPlan existingPlan = this.getById(planId); - if (existingPlan == null) { - throw new BusinessException("旅行计划不存在"); - } - - if (!existingPlan.getUserId().equals(userId)) { - throw new BusinessException("没有权限修改此旅行计划"); - } - - // 更新字段 - if (travelPlan.getDestination() != null) { - existingPlan.setDestination(travelPlan.getDestination()); - } - if (travelPlan.getStartDate() != null) { - existingPlan.setStartDate(travelPlan.getStartDate()); - } - if (travelPlan.getEndDate() != null) { - existingPlan.setEndDate(travelPlan.getEndDate()); - } - if (travelPlan.getBudget() != null) { - existingPlan.setBudget(travelPlan.getBudget()); - } - if (travelPlan.getInterests() != null) { - existingPlan.setInterests(travelPlan.getInterests()); - } - if (travelPlan.getDescription() != null) { - existingPlan.setDescription(travelPlan.getDescription()); - } - if (travelPlan.getVisibility() != null) { - existingPlan.setVisibility(travelPlan.getVisibility()); - } - if (travelPlan.getStatus() != null) { - existingPlan.setStatus(travelPlan.getStatus()); - } - - this.updateById(existingPlan); - return existingPlan; - } - - @Override - public boolean deleteTravelPlan(Long planId, Long userId) { - TravelPlan existingPlan = this.getById(planId); - if (existingPlan == null) { - throw new BusinessException("旅行计划不存在"); - } - - if (!existingPlan.getUserId().equals(userId)) { - throw new BusinessException("没有权限删除此旅行计划"); - } - - return this.removeById(planId); - } - - @Override - public TravelStats getUserTravelStats(Long userId) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("user_id", userId); - - List plans = this.list(queryWrapper); - - TravelStats stats = new TravelStats(); - stats.setTotalPlans(plans.size()); - - int completedPlans = 0; - int planningPlans = 0; - int cancelledPlans = 0; - java.math.BigDecimal totalBudget = java.math.BigDecimal.ZERO; - - for (TravelPlan plan : plans) { - switch (plan.getStatus()) { - case "completed": - completedPlans++; - break; - case "planning": - planningPlans++; - break; - case "cancelled": - cancelledPlans++; - break; - } - - if (plan.getBudget() != null) { - totalBudget = totalBudget.add(plan.getBudget()); - } - } - - stats.setCompletedPlans(completedPlans); - stats.setPlanningPlans(planningPlans); - stats.setCancelledPlans(cancelledPlans); - stats.setTotalBudget(totalBudget); - - return stats; - } -} \ No newline at end of file diff --git a/backend-java/travel-service/src/main/resources/application.yml b/backend-java/travel-service/src/main/resources/application.yml deleted file mode 100644 index 7421ff9..0000000 --- a/backend-java/travel-service/src/main/resources/application.yml +++ /dev/null @@ -1,32 +0,0 @@ -server: - port: 8083 - -spring: - application: - name: travel-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 \ No newline at end of file diff --git a/backend-java/travel-service/target/classes/application.yml b/backend-java/travel-service/target/classes/application.yml deleted file mode 100644 index 7421ff9..0000000 --- a/backend-java/travel-service/target/classes/application.yml +++ /dev/null @@ -1,32 +0,0 @@ -server: - port: 8083 - -spring: - application: - name: travel-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 \ No newline at end of file diff --git a/backend-java/user-service/pom.xml b/backend-java/user-service/pom.xml deleted file mode 100644 index 9b148ce..0000000 --- a/backend-java/user-service/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - com.jiebanke - backend-java - 1.0.0 - - - user-service - jar - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - mysql - mysql-connector-java - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.springframework.boot - spring-boot-starter-amqp - - - - - com.jiebanke - common - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/backend-java/user-service/src/main/java/com/jiebanke/user/UserApplication.java b/backend-java/user-service/src/main/java/com/jiebanke/user/UserApplication.java deleted file mode 100644 index 22e22c8..0000000 --- a/backend-java/user-service/src/main/java/com/jiebanke/user/UserApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jiebanke.user; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.openfeign.EnableFeignClients; - -@SpringBootApplication -@EnableDiscoveryClient -@EnableFeignClients(basePackages = "com.jiebanke.user.client") -public class UserApplication { - public static void main(String[] args) { - SpringApplication.run(UserApplication.class, args); - } -} \ No newline at end of file diff --git a/backend-java/user-service/src/main/java/com/jiebanke/user/client/AuthServiceClient.java b/backend-java/user-service/src/main/java/com/jiebanke/user/client/AuthServiceClient.java deleted file mode 100644 index 5ed1d77..0000000 --- a/backend-java/user-service/src/main/java/com/jiebanke/user/client/AuthServiceClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.jiebanke.user.client; - -import com.jiebanke.common.vo.ApiResponse; -import com.jiebanke.user.config.FeignConfig; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; - -@FeignClient(name = "auth-service", configuration = FeignConfig.class) -public interface AuthServiceClient { - - @GetMapping("/api/auth/validate") - ApiResponse validateToken(@RequestHeader("Authorization") String token); -} \ No newline at end of file diff --git a/backend-java/user-service/src/main/java/com/jiebanke/user/config/FeignConfig.java b/backend-java/user-service/src/main/java/com/jiebanke/user/config/FeignConfig.java deleted file mode 100644 index 690119c..0000000 --- a/backend-java/user-service/src/main/java/com/jiebanke/user/config/FeignConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.jiebanke.user.config; - -import feign.RequestInterceptor; -import feign.RequestTemplate; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import jakarta.servlet.http.HttpServletRequest; - -@Configuration -public class FeignConfig { - - @Bean - public RequestInterceptor requestInterceptor() { - return new RequestInterceptor() { - @Override - public void apply(RequestTemplate template) { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (attributes != null) { - HttpServletRequest request = attributes.getRequest(); - String authorization = request.getHeader("Authorization"); - if (authorization != null) { - template.header("Authorization", authorization); - } - } - } - }; - } -} \ No newline at end of file diff --git a/backend-java/user-service/src/main/java/com/jiebanke/user/service/UserRabbitMQService.java b/backend-java/user-service/src/main/java/com/jiebanke/user/service/UserRabbitMQService.java deleted file mode 100644 index 2645fe9..0000000 --- a/backend-java/user-service/src/main/java/com/jiebanke/user/service/UserRabbitMQService.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jiebanke.user.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 UserRabbitMQService { - - @Autowired - private RabbitTemplate rabbitTemplate; - - // 发送用户注册消息 - public void sendUserRegistrationMessage(Long userId, String username) { - Map message = new HashMap<>(); - message.put("userId", userId); - message.put("username", username); - message.put("eventType", "USER_REGISTERED"); - - rabbitTemplate.convertAndSend( - RabbitMQConfig.EXCHANGE_NAME, - RabbitMQConfig.USER_ROUTING_KEY, - message - ); - } - - // 发送用户更新消息 - public void sendUserUpdateMessage(Long userId, String username) { - Map message = new HashMap<>(); - message.put("userId", userId); - message.put("username", username); - message.put("eventType", "USER_UPDATED"); - - rabbitTemplate.convertAndSend( - RabbitMQConfig.EXCHANGE_NAME, - RabbitMQConfig.USER_ROUTING_KEY, - message - ); - } -} \ No newline at end of file diff --git a/backend-java/user-service/src/main/java/com/jiebanke/user/service/UserRedisService.java b/backend-java/user-service/src/main/java/com/jiebanke/user/service/UserRedisService.java deleted file mode 100644 index 93546db..0000000 --- a/backend-java/user-service/src/main/java/com/jiebanke/user/service/UserRedisService.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jiebanke.user.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 UserRedisService { - - @Autowired - private RedisTemplate redisTemplate; - - // 缓存用户信息 - public void cacheUserInfo(Long userId, Object userInfo) { - redisTemplate.opsForValue().set("user:" + userId, userInfo, 30, TimeUnit.MINUTES); - } - - // 获取缓存的用户信息 - public Object getCachedUserInfo(Long userId) { - return redisTemplate.opsForValue().get("user:" + userId); - } - - // 删除缓存的用户信息 - public void removeCachedUserInfo(Long userId) { - redisTemplate.delete("user:" + userId); - } - - // 缓存用户登录状态 - public void cacheUserLoginStatus(String token, Long userId) { - redisTemplate.opsForValue().set("login:token:" + token, userId, 7, TimeUnit.DAYS); - } - - // 获取用户登录状态 - public Long getUserLoginStatus(String token) { - return (Long) redisTemplate.opsForValue().get("login:token:" + token); - } - - // 删除用户登录状态 - public void removeUserLoginStatus(String token) { - redisTemplate.delete("login:token:" + token); - } -} \ No newline at end of file diff --git a/backend-java/user-service/src/main/resources/application.yml b/backend-java/user-service/src/main/resources/application.yml deleted file mode 100644 index efcdc3f..0000000 --- a/backend-java/user-service/src/main/resources/application.yml +++ /dev/null @@ -1,25 +0,0 @@ -server: - port: 8082 - -spring: - application: - name: user-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/ \ No newline at end of file diff --git a/backend-java/user-service/src/test/java/com/jiebanke/user/service/UserRabbitMQServiceTest.java b/backend-java/user-service/src/test/java/com/jiebanke/user/service/UserRabbitMQServiceTest.java deleted file mode 100644 index 5105a95..0000000 --- a/backend-java/user-service/src/test/java/com/jiebanke/user/service/UserRabbitMQServiceTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jiebanke.user.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 UserRabbitMQServiceTest { - - @Mock - private RabbitTemplate rabbitTemplate; - - @InjectMocks - private UserRabbitMQService userRabbitMQService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - @Test - void testSendUserRegistrationMessage() { - Long userId = 1L; - String username = "testUser"; - - userRabbitMQService.sendUserRegistrationMessage(userId, username); - - verify(rabbitTemplate).convertAndSend( - RabbitMQConfig.EXCHANGE_NAME, - RabbitMQConfig.USER_ROUTING_KEY, - anyMap() - ); - } - - @Test - void testSendUserUpdateMessage() { - Long userId = 1L; - String username = "testUser"; - - userRabbitMQService.sendUserUpdateMessage(userId, username); - - verify(rabbitTemplate).convertAndSend( - RabbitMQConfig.EXCHANGE_NAME, - RabbitMQConfig.USER_ROUTING_KEY, - anyMap() - ); - } -} \ No newline at end of file diff --git a/backend-java/user-service/src/test/java/com/jiebanke/user/service/UserRedisServiceTest.java b/backend-java/user-service/src/test/java/com/jiebanke/user/service/UserRedisServiceTest.java deleted file mode 100644 index 0029b98..0000000 --- a/backend-java/user-service/src/test/java/com/jiebanke/user/service/UserRedisServiceTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.jiebanke.user.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 UserRedisServiceTest { - - @Mock - private RedisTemplate redisTemplate; - - @Mock - private ValueOperations valueOperations; - - @InjectMocks - private UserRedisService userRedisService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - when(redisTemplate.opsForValue()).thenReturn(valueOperations); - } - - @Test - void testCacheUserInfo() { - Long userId = 1L; - String userInfo = "testUser"; - - userRedisService.cacheUserInfo(userId, userInfo); - - verify(valueOperations).set(eq("user:" + userId), eq(userInfo), anyLong(), any()); - } - - @Test - void testGetCachedUserInfo() { - Long userId = 1L; - String userInfo = "testUser"; - when(valueOperations.get("user:" + userId)).thenReturn(userInfo); - - Object result = userRedisService.getCachedUserInfo(userId); - - assertEquals(userInfo, result); - verify(valueOperations).get("user:" + userId); - } - - @Test - void testRemoveCachedUserInfo() { - Long userId = 1L; - - userRedisService.removeCachedUserInfo(userId); - - verify(redisTemplate).delete("user:" + userId); - } - - @Test - void testCacheUserLoginStatus() { - String token = "testToken"; - Long userId = 1L; - - userRedisService.cacheUserLoginStatus(token, userId); - - verify(valueOperations).set(eq("login:token:" + token), eq(userId), anyLong(), any()); - } - - @Test - void testGetUserLoginStatus() { - String token = "testToken"; - Long userId = 1L; - when(valueOperations.get("login:token:" + token)).thenReturn(userId); - - Long result = userRedisService.getUserLoginStatus(token); - - assertEquals(userId, result); - verify(valueOperations).get("login:token:" + token); - } - - @Test - void testRemoveUserLoginStatus() { - String token = "testToken"; - - userRedisService.removeUserLoginStatus(token); - - verify(redisTemplate).delete("login:token:" + token); - } -} \ No newline at end of file diff --git a/backend-java/user-service/target/classes/application.yml b/backend-java/user-service/target/classes/application.yml deleted file mode 100644 index efcdc3f..0000000 --- a/backend-java/user-service/target/classes/application.yml +++ /dev/null @@ -1,25 +0,0 @@ -server: - port: 8082 - -spring: - application: - name: user-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/ \ No newline at end of file diff --git a/backend/scripts/start-server.sh b/backend/scripts/start-server.sh index 64b99d2..1daada9 100644 --- a/backend/scripts/start-server.sh +++ b/backend/scripts/start-server.sh @@ -8,7 +8,7 @@ set -e # 配置参数 APP_NAME="jiebanke-backend" -APP_DIR="/data/nodejsjiebanke" +APP_DIR="/data/nodejs/jiebanke" PORT="3310" LOG_DIR="$APP_DIR/logs" diff --git a/backend/start_server.bat b/backend/start_server.bat new file mode 100644 index 0000000..94b71e5 --- /dev/null +++ b/backend/start_server.bat @@ -0,0 +1,266 @@ +#!/bin/bash + +# 结伴客后端服务启动脚本 +# 用于启动Node.js后端服务 + +# 设置颜色变量 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 显示脚本使用说明 +show_usage() { + echo "用法: $0 [选项]" + echo "选项:" + echo " start 启动后端服务" + echo " stop 停止后端服务" + echo " restart 重启后端服务" + echo " status 查看服务状态" + echo " help 显示帮助信息" +} + +# 检查是否安装了Node.js +check_node() { + if ! command -v node &> /dev/null; then + echo -e "${RED}错误: 未找到Node.js,请先安装Node.js${NC}" + exit 1 + fi + + NODE_VERSION=$(node -v) + echo -e "${GREEN}✓ Node.js版本: ${NODE_VERSION}${NC}" +} + +# 检查是否安装了npm +check_npm() { + if ! command -v npm &> /dev/null; then + echo -e "${RED}错误: 未找到npm,请先安装npm${NC}" + exit 1 + fi + + NPM_VERSION=$(npm -v) + echo -e "${GREEN}✓ npm版本: ${NPM_VERSION}${NC}" +} + +# 检查依赖是否已安装 +check_dependencies() { + if [ ! -d "node_modules" ]; then + echo -e "${YELLOW}警告: 未找到node_modules目录,正在安装依赖...${NC}" + npm install + if [ $? -ne 0 ]; then + echo -e "${RED}错误: 依赖安装失败${NC}" + exit 1 + fi + echo -e "${GREEN}✓ 依赖安装完成${NC}" + else + echo -e "${GREEN}✓ 依赖已安装${NC}" + fi +} + +# 启动服务 +start_service() { + # 检查服务是否已经在运行 + if pgrep -f "node.*server.js" > /dev/null; then + echo -e "${YELLOW}警告: 服务已在运行中${NC}" + exit 1 + fi + + echo -e "${GREEN}正在启动结伴客后端服务...${NC}" + + # 设置环境变量 + export NODE_ENV=production + export PORT=${PORT:-3200} + + # 启动服务并将其放到后台运行 + nohup npm start > server.log 2>&1 & + SERVER_PID=$! + + # 将PID保存到文件 + echo $SERVER_PID > server.pid + + # 等待几秒钟让服务启动 + sleep 3 + + # 检查服务是否成功启动 + if pgrep -f "node.*server.js" > /dev/null; then + echo -e "${GREEN}✓ 服务启动成功${NC}" + echo -e "${GREEN}✓ PID: ${SERVER_PID}${NC}" + echo -e "${GREEN}✓ 日志文件: server.log${NC}" + echo -e "${GREEN}✓ 访问地址: http://localhost:${PORT}${NC}" + else + echo -e "${RED}✗ 服务启动失败,请检查日志文件 server.log${NC}" + rm -f server.pid + exit 1 + fi +} + +# 停止服务 +stop_service() { + if [ ! -f server.pid ]; then + if pgrep -f "node.*server.js" > /dev/null; then + echo -e "${YELLOW}警告: 找到正在运行的服务,但PID文件不存在${NC}" + echo -e "${YELLOW}尝试查找并终止服务...${NC}" + pkill -f "node.*server.js" + sleep 2 + if pgrep -f "node.*server.js" > /dev/null; then + echo -e "${RED}✗ 无法终止服务${NC}" + exit 1 + else + echo -e "${GREEN}✓ 服务已终止${NC}" + fi + else + echo -e "${YELLOW}服务未在运行${NC}" + fi + else + SERVER_PID=$(cat server.pid) + if ps -p $SERVER_PID > /dev/null; then + echo -e "${GREEN}正在停止服务 (PID: ${SERVER_PID})...${NC}" + kill $SERVER_PID + sleep 3 + if ps -p $SERVER_PID > /dev/null; then + echo -e "${YELLOW}服务未响应,强制终止...${NC}" + kill -9 $SERVER_PID + fi + rm -f server.pid + echo -e "${GREEN}✓ 服务已停止${NC}" + else + echo -e "${YELLOW}PID文件存在但服务未在运行${NC}" + rm -f server.pid + fi + fi +} + +# 查看服务状态 +status_service() { + if [ -f server.pid ]; then + SERVER_PID=$(cat server.pid) + if ps -p $SERVER_PID > /dev/null; then + echo -e "${GREEN}✓ 服务正在运行 (PID: ${SERVER_PID})${NC}" + else + echo -e "${RED}✗ PID文件存在但服务未在运行${NC}" + fi + else + if pgrep -f "node.*server.js" > /dev/null; then + echo -e "${GREEN}✓ 服务正在运行${NC}" + else + echo -e "${YELLOW}服务未在运行${NC}" + fi + fi +} + +# 查看日志 +tail_logs() { + if [ -f server.log ]; then + tail -f server.log + else + echo -e "${RED}日志文件不存在${NC}" + fi +} + +# 主逻辑 +main() { + # 获取命令行参数 + ACTION=${1:-"start"} + + case "$ACTION" in + start) + check_node + check_npm + check_dependencies + start_service + ;; + stop) + stop_service + ;; + restart) + stop_service + sleep 2 + check_node + check_npm + check_dependencies + start_service + ;; + status) + status_service + ;; + logs) + tail_logs + ;; + help) + show_usage + ;; + *) + echo -e "${RED}未知选项: $ACTION${NC}" + show_usage + exit 1 + ;; + esac +} + +# 执行主逻辑 +main "$@" +``` +``` +@echo off +title 结伴客后端服务启动脚本 + +setlocal enabledelayedexpansion + +:: 设置颜色 +for /f "delims=" %%i in ('echo prompt $E^| cmd') do set "ESC=%%i" + +:: 检查Node.js是否安装 +echo 检查Node.js环境... +node -v >nul 2>&1 +if %errorlevel% neq 0 ( + echo %ESC%[91m错误: 未找到Node.js,请先安装Node.js%ESC%[0m + pause + exit /b 1 +) else ( + for /f %%i in ('node -v') do set NODE_VERSION=%%i + echo %ESC%[92m✓ Node.js版本: %NODE_VERSION%%ESC%[0m +) + +:: 检查npm是否安装 +echo 检查npm环境... +npm -v >nul 2>&1 +if %errorlevel% neq 0 ( + echo %ESC%[91m错误: 未找到npm,请先安装npm%ESC%[0m + pause + exit /b 1 +) else ( + for /f %%i in ('npm -v') do set NPM_VERSION=%%i + echo %ESC%[92m✓ npm版本: %NPM_VERSION%%ESC%[0m +) + +:: 检查依赖是否安装 +if not exist "node_modules" ( + echo %ESC%[93m警告: 未找到node_modules目录,正在安装依赖...%ESC%[0m + npm install + if !errorlevel! neq 0 ( + echo %ESC%[91m错误: 依赖安装失败%ESC%[0m + pause + exit /b 1 + ) + echo %ESC%[92m✓ 依赖安装完成%ESC%[0m +) else ( + echo %ESC%[92m✓ 依赖已安装%ESC%[0m +) + +:: 设置默认端口 +if "%PORT%"=="" set PORT=3200 + +:: 启动服务 +echo %ESC%[92m正在启动结伴客后端服务...%ESC%[0m +echo 服务将在端口 %PORT% 上运行 +echo 日志将输出到 server.log 文件 + +npm start > server.log 2>&1 + +if %errorlevel% equ 0 ( + echo %ESC%[92m✓ 服务启动成功%ESC%[0m +) else ( + echo %ESC%[91m✗ 服务启动失败,请检查日志文件 server.log%ESC%[0m +) + +pause diff --git a/fastapi-backend/alembic.ini b/fastapi-backend/alembic.ini new file mode 100644 index 0000000..52cd8d1 --- /dev/null +++ b/fastapi-backend/alembic.ini @@ -0,0 +1,41 @@ +[alembic] +# 模板路径 +script_location = fastapi-backend/alembic + +# 数据库连接URL +sqlalchemy.url = mysql+pymysql://jiebanke:aiot741$12346@nj-cdb-3pwh2kz1.sql.tencentcdb.com:20784/jbkdata + +# 日志配置 +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S \ No newline at end of file diff --git a/fastapi-backend/alembic/env.py b/fastapi-backend/alembic/env.py new file mode 100644 index 0000000..99b9f96 --- /dev/null +++ b/fastapi-backend/alembic/env.py @@ -0,0 +1,79 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# 导入模型 +from app.models.user import Base + +# 导入配置 +from app.core.config import settings + +# 确保Python能够找到app模块 +import os +import sys +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +# 这是Alembic Config对象,它提供对.ini文件中值的访问 +config = context.config + +# 解释配置文件并设置日志记录器 +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# 添加你的模型元数据对象 +target_metadata = Base.metadata + +# 其他值来自config,可以通过以下方式定义: +# my_important_option = config.get_main_option("my_important_option") +# ... 等等。 + + +def run_migrations_offline() -> None: + """在'offline'模式下运行迁移。 + + 这配置了上下文,只需要一个URL,并且不要求引擎可用。 + 跳过引擎创建,甚至不需要DBAPI可用。 + + 调用context.execute()来执行迁移。 + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """在'online'模式下运行迁移。 + + 在这种情况下,我们创建了一个Engine并将其与迁移上下文关联。 + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() \ No newline at end of file diff --git a/fastapi-backend/alembic/versions/0001_initial_migration.py b/fastapi-backend/alembic/versions/0001_initial_migration.py new file mode 100644 index 0000000..f568c27 --- /dev/null +++ b/fastapi-backend/alembic/versions/0001_initial_migration.py @@ -0,0 +1,62 @@ +"""initial migration + +Revision ID: 0001 +Revises: +Create Date: 2025-09-11 16:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0001' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # 创建用户表 + op.create_table( + 'users', + sa.Column('id', sa.Integer, primary_key=True, index=True), + sa.Column('username', sa.String(50), unique=True, index=True, nullable=False), + sa.Column('password_hash', sa.String(255), nullable=False), + sa.Column('user_type', sa.Enum('farmer', 'merchant', 'admin', 'super_admin'), server_default='farmer'), + sa.Column('real_name', sa.String(50)), + sa.Column('nickname', sa.String(50)), + sa.Column('avatar_url', sa.String(255)), + sa.Column('email', sa.String(100), unique=True, index=True), + sa.Column('phone', sa.String(20), unique=True, index=True), + sa.Column('gender', sa.Enum('male', 'female', 'other'), server_default='other'), + sa.Column('birthday', sa.DateTime), + sa.Column('status', sa.Enum('active', 'inactive'), server_default='active'), + sa.Column('wechat_openid', sa.String(100), unique=True, index=True), + sa.Column('wechat_unionid', sa.String(100), unique=True, index=True), + sa.Column('level', sa.Integer, server_default='1'), + sa.Column('created_at', sa.DateTime, server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime, server_default=sa.func.now(), onupdate=sa.func.now()), + sa.Column('last_login', sa.DateTime) + ) + + # 创建管理员表 + op.create_table( + 'admins', + sa.Column('id', sa.Integer, primary_key=True, index=True), + sa.Column('username', sa.String(50), unique=True, index=True, nullable=False), + sa.Column('password', sa.String(255), nullable=False), + sa.Column('email', sa.String(100), unique=True, index=True), + sa.Column('nickname', sa.String(50)), + sa.Column('avatar', sa.String(255)), + sa.Column('role', sa.Enum('admin', 'super_admin'), server_default='admin'), + sa.Column('status', sa.Enum('active', 'inactive'), server_default='active'), + sa.Column('last_login', sa.DateTime), + sa.Column('created_at', sa.DateTime, server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime, server_default=sa.func.now(), onupdate=sa.func.now()) + ) + + +def downgrade(): + op.drop_table('admins') + op.drop_table('users') \ No newline at end of file diff --git a/fastapi-backend/app/api/api.py b/fastapi-backend/app/api/api.py new file mode 100644 index 0000000..d34f3a1 --- /dev/null +++ b/fastapi-backend/app/api/api.py @@ -0,0 +1,121 @@ +from fastapi import APIRouter, FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from fastapi.exceptions import RequestValidationError +from starlette.exceptions import HTTPException as StarletteHTTPException + +from app.api.endpoints import auth, users +from app.core.config import settings +from app.utils.response import error_response + +# 创建FastAPI应用 +app = FastAPI( + title=settings.PROJECT_NAME, + openapi_url=f"{settings.API_V1_STR}/openapi.json", + docs_url="/api-docs", + redoc_url="/redoc", +) + +# 配置CORS +if settings.BACKEND_CORS_ORIGINS: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) +else: + # 默认CORS配置 + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + +# 创建API路由 +api_router = APIRouter() + +# 添加各个端点路由 +api_router.include_router(auth.router, prefix="/auth", tags=["认证"]) +api_router.include_router(users.router, prefix="/users", tags=["用户"]) + +# 将API路由添加到应用 +app.include_router(api_router, prefix=settings.API_V1_STR) + + +# 自定义异常处理 +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request, exc): + return JSONResponse( + status_code=exc.status_code, + content=error_response( + message=str(exc.detail), + code=exc.status_code + ) + ) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request, exc): + errors = [] + for error in exc.errors(): + error_msg = f"{error['loc'][-1]}: {error['msg']}" + errors.append(error_msg) + + return JSONResponse( + status_code=400, + content=error_response( + message="请求参数验证失败", + code=400, + data={"errors": errors} + ) + ) + + +# 健康检查路由 +@app.get("/health") +async def health_check(): + import platform + import psutil + from datetime import datetime + + return { + "status": "OK", + "timestamp": datetime.now().isoformat(), + "uptime": psutil.boot_time(), + "environment": settings.DEBUG and "development" or "production", + "no_db_mode": settings.NO_DB_MODE, + "system_info": { + "python_version": platform.python_version(), + "platform": platform.platform(), + "cpu_count": psutil.cpu_count(), + "memory": { + "total": psutil.virtual_memory().total, + "available": psutil.virtual_memory().available, + } + } + } + + +# 系统统计路由 +@app.get("/system-stats") +async def system_stats(): + import platform + import psutil + from datetime import datetime + + return { + "status": "OK", + "timestamp": datetime.now().isoformat(), + "environment": settings.DEBUG and "development" or "production", + "python_version": platform.python_version(), + "memory_usage": dict(psutil.virtual_memory()._asdict()), + "uptime": psutil.boot_time(), + "cpu_count": psutil.cpu_count(), + "platform": platform.platform(), + "architecture": platform.architecture(), + "no_db_mode": settings.NO_DB_MODE + } \ No newline at end of file diff --git a/fastapi-backend/app/api/deps.py b/fastapi-backend/app/api/deps.py new file mode 100644 index 0000000..56fe47e --- /dev/null +++ b/fastapi-backend/app/api/deps.py @@ -0,0 +1,115 @@ +from typing import Generator, Optional +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from jose import jwt, JWTError +from pydantic import ValidationError +from sqlalchemy.orm import Session + +from app.core.config import settings +from app.core.security import verify_password +from app.crud.user import user, admin +from app.db.session import SessionLocal +from app.models.user import User, Admin +from app.schemas.user import TokenPayload + +# OAuth2密码承载令牌 +oauth2_scheme = OAuth2PasswordBearer( + tokenUrl=f"{settings.API_V1_STR}/auth/login" +) + +# 获取数据库会话 +def get_db() -> Generator: + try: + db = SessionLocal() + yield db + finally: + db.close() + +# 获取当前用户 +def get_current_user( + db: Session = Depends(get_db), token: str = Depends(oauth2_scheme) +) -> User: + try: + payload = jwt.decode( + token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] + ) + token_data = TokenPayload(**payload) + + # 检查令牌是否过期 + if token_data.exp is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="令牌无效", + headers={"WWW-Authenticate": "Bearer"}, + ) + except (JWTError, ValidationError): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="无法验证凭据", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # 获取用户 + current_user = user.get(db, user_id=token_data.sub) + if not current_user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在") + + # 检查用户是否活跃 + if not user.is_active(current_user): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账户已被禁用") + + return current_user + +# 获取当前活跃用户 +def get_current_active_user( + current_user: User = Depends(get_current_user), +) -> User: + if not user.is_active(current_user): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账户已被禁用") + return current_user + +# 获取当前管理员 +def get_current_admin( + db: Session = Depends(get_db), token: str = Depends(oauth2_scheme) +) -> Admin: + try: + payload = jwt.decode( + token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] + ) + token_data = TokenPayload(**payload) + except (JWTError, ValidationError): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="无法验证凭据", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # 获取管理员 + current_admin = admin.get(db, admin_id=token_data.sub) + if not current_admin: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="管理员不存在") + + # 检查管理员是否活跃 + if not admin.is_active(current_admin): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账户已被禁用") + + return current_admin + +# 获取当前活跃管理员 +def get_current_active_admin( + current_admin: Admin = Depends(get_current_admin), +) -> Admin: + if not admin.is_active(current_admin): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账户已被禁用") + return current_admin + +# 获取超级管理员 +def get_current_super_admin( + current_admin: Admin = Depends(get_current_admin), +) -> Admin: + if current_admin.role != "super_admin": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="需要超级管理员权限" + ) + return current_admin \ No newline at end of file diff --git a/fastapi-backend/app/api/endpoints/auth.py b/fastapi-backend/app/api/endpoints/auth.py new file mode 100644 index 0000000..e20fa85 --- /dev/null +++ b/fastapi-backend/app/api/endpoints/auth.py @@ -0,0 +1,332 @@ +from datetime import timedelta +from typing import Any + +from fastapi import APIRouter, Body, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm import Session + +from app.api.deps import get_db, get_current_user +from app.core.config import settings +from app.core.security import create_access_token, create_refresh_token +from app.crud.user import user, admin +from app.schemas.user import ( + UserCreate, UserResponse, UserLogin, UserWithToken, + Token, PasswordChange, AdminWithToken +) +from app.utils.response import success_response, error_response +from app.utils.errors import BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError + +router = APIRouter() + + +@router.post("/register", response_model=UserWithToken, status_code=status.HTTP_201_CREATED) +def register( + *, + db: Session = Depends(get_db), + user_in: UserCreate, +) -> Any: + """ + 用户注册 + """ + # 检查用户名是否已存在 + if user.get_by_username(db, username=user_in.username): + raise BadRequestError("用户名已存在") + + # 检查邮箱是否已存在 + if user_in.email and user.get_by_email(db, email=user_in.email): + raise BadRequestError("邮箱已存在") + + # 检查手机号是否已存在 + if user_in.phone and user.get_by_phone(db, phone=user_in.phone): + raise BadRequestError("手机号已存在") + + # 创建新用户 + db_user = user.create(db, obj_in=user_in) + + # 更新最后登录时间 + user.update_last_login(db, db_obj=db_user) + + # 生成令牌 + access_token = create_access_token(db_user.id) + refresh_token = create_refresh_token(db_user.id) + + return success_response( + data={ + "user": db_user, + "token": access_token, + "refresh_token": refresh_token + }, + message="注册成功", + code=201 + ) + + +@router.post("/login", response_model=UserWithToken) +def login( + *, + db: Session = Depends(get_db), + form_data: OAuth2PasswordRequestForm = Depends() +) -> Any: + """ + 用户登录 + """ + # 验证用户 + db_user = user.authenticate(db, username=form_data.username, password=form_data.password) + if not db_user: + raise UnauthorizedError("用户名或密码错误") + + # 检查用户状态 + if not user.is_active(db_user): + raise ForbiddenError("账户已被禁用") + + # 更新最后登录时间 + user.update_last_login(db, db_obj=db_user) + + # 生成令牌 + access_token = create_access_token(db_user.id) + refresh_token = create_refresh_token(db_user.id) + + return success_response( + data={ + "user": db_user, + "token": access_token, + "refresh_token": refresh_token + }, + message="登录成功" + ) + + +@router.post("/login/json", response_model=UserWithToken) +def login_json( + *, + db: Session = Depends(get_db), + login_in: UserLogin +) -> Any: + """ + 用户登录(JSON格式) + """ + # 验证用户 + db_user = user.authenticate(db, username=login_in.username, password=login_in.password) + if not db_user: + raise UnauthorizedError("用户名或密码错误") + + # 检查用户状态 + if not user.is_active(db_user): + raise ForbiddenError("账户已被禁用") + + # 更新最后登录时间 + user.update_last_login(db, db_obj=db_user) + + # 生成令牌 + access_token = create_access_token(db_user.id) + refresh_token = create_refresh_token(db_user.id) + + return success_response( + data={ + "user": db_user, + "token": access_token, + "refresh_token": refresh_token + }, + message="登录成功" + ) + + +@router.post("/refresh-token", response_model=Token) +def refresh_token( + *, + db: Session = Depends(get_db), + refresh_token: str = Body(..., embed=True) +) -> Any: + """ + 刷新访问令牌 + """ + try: + from jose import jwt + from pydantic import ValidationError + from app.schemas.user import TokenPayload + + payload = jwt.decode( + refresh_token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] + ) + token_data = TokenPayload(**payload) + + # 检查令牌类型 + if token_data.type != "refresh": + raise UnauthorizedError("无效的刷新令牌") + + # 检查用户是否存在 + db_user = user.get(db, user_id=token_data.sub) + if not db_user: + raise NotFoundError("用户不存在") + + # 检查用户状态 + if not user.is_active(db_user): + raise ForbiddenError("账户已被禁用") + + # 生成新令牌 + access_token = create_access_token(db_user.id) + new_refresh_token = create_refresh_token(db_user.id) + + return success_response( + data={ + "access_token": access_token, + "refresh_token": new_refresh_token, + "token_type": "bearer" + } + ) + except (jwt.JWTError, ValidationError): + raise UnauthorizedError("无效的刷新令牌") + + +@router.get("/me", response_model=UserResponse) +def get_current_user_info( + current_user: UserResponse = Depends(get_current_user) +) -> Any: + """ + 获取当前用户信息 + """ + return success_response(data=current_user) + + +@router.put("/profile", response_model=UserResponse) +def update_profile( + *, + db: Session = Depends(get_db), + current_user: UserResponse = Depends(get_current_user), + profile_in: dict = Body(...) +) -> Any: + """ + 更新用户个人信息 + """ + # 更新用户信息 + db_user = user.update(db, db_obj=current_user, obj_in=profile_in) + + return success_response( + data=db_user, + message="个人信息更新成功" + ) + + +@router.put("/password", response_model=dict) +def change_password( + *, + db: Session = Depends(get_db), + current_user: UserResponse = Depends(get_current_user), + password_in: PasswordChange +) -> Any: + """ + 修改密码 + """ + from app.core.security import verify_password + + # 验证当前密码 + if not verify_password(password_in.current_password, current_user.password_hash): + raise UnauthorizedError("当前密码错误") + + # 更新密码 + user.update_password(db, db_obj=current_user, new_password=password_in.new_password) + + return success_response(message="密码修改成功") + + +@router.post("/admin/login", response_model=AdminWithToken) +def admin_login( + *, + db: Session = Depends(get_db), + login_in: UserLogin +) -> Any: + """ + 管理员登录 + """ + # 验证管理员 + db_admin = admin.authenticate(db, username=login_in.username, password=login_in.password) + if not db_admin: + raise UnauthorizedError("用户名或密码错误") + + # 检查管理员状态 + if not admin.is_active(db_admin): + raise ForbiddenError("账户已被禁用") + + # 更新最后登录时间 + admin.update_last_login(db, db_obj=db_admin) + + # 生成令牌 + access_token = create_access_token(db_admin.id) + refresh_token = create_refresh_token(db_admin.id) + + return success_response( + data={ + "admin": db_admin, + "token": access_token, + "refresh_token": refresh_token + }, + message="管理员登录成功" + ) + + +@router.post("/wechat", response_model=UserWithToken) +def wechat_login( + *, + db: Session = Depends(get_db), + code: str = Body(...), + user_info: dict = Body(None) +) -> Any: + """ + 微信登录/注册 + """ + # 模拟获取微信用户信息 + wechat_user_info = { + "openid": f"mock_openid_{code}", + "unionid": f"mock_unionid_{code}", + "nickname": user_info.get("nickName") if user_info else "微信用户", + "avatar": user_info.get("avatarUrl") if user_info else "", + "gender": "male" if user_info and user_info.get("gender") == 1 else + "female" if user_info and user_info.get("gender") == 2 else "other" + } + + # 查找是否已存在微信用户 + db_user = user.get_by_wechat_openid(db, openid=wechat_user_info["openid"]) + + if db_user: + # 更新最后登录时间 + user.update_last_login(db, db_obj=db_user) + else: + # 创建新用户(微信注册) + import secrets + import string + from app.schemas.user import UserCreate + + # 生成随机密码 + alphabet = string.ascii_letters + string.digits + random_password = ''.join(secrets.choice(alphabet) for _ in range(12)) + + # 创建用户 + user_in = UserCreate( + username=f"wx_{wechat_user_info['openid'][-8:]}", + password=random_password, + nickname=wechat_user_info["nickname"], + user_type="farmer" + ) + + db_user = user.create(db, obj_in=user_in) + + # 更新微信信息 + user.update(db, db_obj=db_user, obj_in={ + "wechat_openid": wechat_user_info["openid"], + "wechat_unionid": wechat_user_info["unionid"], + "avatar_url": wechat_user_info["avatar"], + "gender": wechat_user_info["gender"] + }) + + # 生成令牌 + access_token = create_access_token(db_user.id) + refresh_token = create_refresh_token(db_user.id) + + return success_response( + data={ + "user": db_user, + "token": access_token, + "refresh_token": refresh_token + }, + message="微信登录成功" + ) \ No newline at end of file diff --git a/fastapi-backend/app/api/endpoints/users.py b/fastapi-backend/app/api/endpoints/users.py new file mode 100644 index 0000000..220b5c5 --- /dev/null +++ b/fastapi-backend/app/api/endpoints/users.py @@ -0,0 +1,174 @@ +from typing import Any, List + +from fastapi import APIRouter, Body, Depends, Query, Path, status +from sqlalchemy.orm import Session + +from app.api.deps import get_db, get_current_user, get_current_admin, get_current_super_admin +from app.crud.user import user +from app.models.user import User +from app.schemas.user import ( + UserResponse, UserUpdate, UserListResponse, + PaginationResponse, UserStatistics, BatchUserStatusUpdate +) +from app.utils.response import success_response +from app.utils.errors import NotFoundError, BadRequestError + +router = APIRouter() + + +@router.get("/profile", response_model=UserResponse) +def get_user_profile( + current_user: User = Depends(get_current_user), +) -> Any: + """ + 获取当前用户个人信息 + """ + return success_response(data=current_user) + + +@router.put("/profile", response_model=UserResponse) +def update_user_profile( + *, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user), + user_in: UserUpdate, +) -> Any: + """ + 更新当前用户个人信息 + """ + # 更新用户信息 + db_user = user.update(db, db_obj=current_user, obj_in=user_in) + + return success_response( + data=db_user, + message="个人信息更新成功" + ) + + +@router.get("", response_model=UserListResponse) +def get_users( + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_admin), + page: int = Query(1, ge=1, description="页码"), + page_size: int = Query(20, ge=1, le=100, description="每页数量"), + user_type: str = Query(None, description="用户类型"), + status: str = Query(None, description="用户状态"), + keyword: str = Query(None, description="搜索关键词"), +) -> Any: + """ + 获取用户列表(管理员) + """ + # 计算分页参数 + skip = (page - 1) * page_size + + # 获取用户列表 + users_list = user.get_multi( + db, skip=skip, limit=page_size, + user_type=user_type, status=status, keyword=keyword + ) + + # 获取用户总数 + total = user.count( + db, user_type=user_type, status=status, keyword=keyword + ) + + # 计算总页数 + total_pages = (total + page_size - 1) // page_size + + # 构建分页信息 + pagination = PaginationResponse( + page=page, + page_size=page_size, + total=total, + total_pages=total_pages + ) + + return success_response( + data={ + "users": users_list, + "pagination": pagination + } + ) + + +@router.get("/{user_id}", response_model=UserResponse) +def get_user_by_id( + user_id: int = Path(..., ge=1), + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_admin), +) -> Any: + """ + 获取用户详情(管理员) + """ + # 获取用户 + db_user = user.get(db, user_id=user_id) + if not db_user: + raise NotFoundError("用户不存在") + + return success_response(data=db_user) + + +@router.get("/statistics", response_model=UserStatistics) +def get_user_statistics( + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_admin), +) -> Any: + """ + 获取用户统计信息(管理员) + """ + # 获取统计信息 + statistics = user.get_statistics(db) + + return success_response(data=statistics) + + +@router.post("/batch-status", response_model=dict) +def batch_update_user_status( + *, + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_admin), + batch_in: BatchUserStatusUpdate, +) -> Any: + """ + 批量操作用户状态(管理员) + """ + # 检查用户ID列表是否为空 + if not batch_in.user_ids: + raise BadRequestError("用户ID列表不能为空") + + # 批量更新用户状态 + affected_rows = user.batch_update_status( + db, user_ids=batch_in.user_ids, status=batch_in.status + ) + + return success_response( + data={ + "message": f"成功更新{affected_rows}个用户的状态", + "affected_rows": affected_rows + } + ) + + +@router.delete("/{user_id}", response_model=dict) +def delete_user( + user_id: int = Path(..., ge=1), + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_super_admin), +) -> Any: + """ + 删除用户(超级管理员) + """ + # 获取用户 + db_user = user.get(db, user_id=user_id) + if not db_user: + raise NotFoundError("用户不存在") + + # 删除用户 + user.remove(db, user_id=user_id) + + return success_response( + data={ + "message": "用户删除成功", + "user_id": user_id + } + ) \ No newline at end of file diff --git a/fastapi-backend/app/core/config.py b/fastapi-backend/app/core/config.py new file mode 100644 index 0000000..985a7ef --- /dev/null +++ b/fastapi-backend/app/core/config.py @@ -0,0 +1,59 @@ +import os +from typing import Any, Dict, List, Optional, Union +from pydantic import AnyHttpUrl, field_validator +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + # 基本配置 + PROJECT_NAME: str = "结伴客API" + API_V1_STR: str = "/api/v1" + DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" + + # 服务器配置 + HOST: str = os.getenv("HOST", "0.0.0.0") + PORT: int = int(os.getenv("PORT", "3110")) + + # 安全配置 + SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-jwt-secret-key-2024") + ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "10080")) # 7天 + REFRESH_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("REFRESH_TOKEN_EXPIRE_MINUTES", "43200")) # 30天 + ALGORITHM: str = "HS256" + + # 数据库配置 + DB_HOST: str = os.getenv("DB_HOST", "nj-cdb-3pwh2kz1.sql.tencentcdb.com") + DB_PORT: int = int(os.getenv("DB_PORT", "20784")) + DB_USER: str = os.getenv("DB_USER", "jiebanke") + DB_PASSWORD: str = os.getenv("DB_PASSWORD", "aiot741$12346") + DB_NAME: str = os.getenv("DB_NAME", "jbkdata") + + # 构建数据库URL + @property + def SQLALCHEMY_DATABASE_URI(self) -> str: + return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" + + # CORS配置 + BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [] + + @field_validator("BACKEND_CORS_ORIGINS", mode="before") + def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]: + if isinstance(v, str) and not v.startswith("["): + return [i.strip() for i in v.split(",")] + elif isinstance(v, (list, str)): + return v + raise ValueError(v) + + # 上传文件配置 + UPLOAD_DIR: str = "uploads" + MAX_FILE_SIZE: int = 10 * 1024 * 1024 # 10MB + ALLOWED_EXTENSIONS: List[str] = ["jpg", "jpeg", "png", "gif", "webp"] + + # 无数据库模式 + NO_DB_MODE: bool = os.getenv("NO_DB_MODE", "False").lower() == "true" + + class Config: + case_sensitive = True + env_file = ".env" + + +settings = Settings() \ No newline at end of file diff --git a/fastapi-backend/app/core/security.py b/fastapi-backend/app/core/security.py new file mode 100644 index 0000000..b5533dd --- /dev/null +++ b/fastapi-backend/app/core/security.py @@ -0,0 +1,46 @@ +from datetime import datetime, timedelta +from typing import Any, Optional, Union + +from jose import jwt +from passlib.context import CryptContext + +from app.core.config import settings + +# 密码上下文 +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +# 验证密码 +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +# 获取密码哈希 +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + +# 创建访问令牌 +def create_access_token( + subject: Union[str, Any], expires_delta: Optional[timedelta] = None +) -> str: + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta( + minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES + ) + to_encode = {"exp": expire, "sub": str(subject)} + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + +# 创建刷新令牌 +def create_refresh_token( + subject: Union[str, Any], expires_delta: Optional[timedelta] = None +) -> str: + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta( + minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES + ) + to_encode = {"exp": expire, "sub": str(subject), "type": "refresh"} + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt \ No newline at end of file diff --git a/fastapi-backend/app/crud/user.py b/fastapi-backend/app/crud/user.py new file mode 100644 index 0000000..57bdce2 --- /dev/null +++ b/fastapi-backend/app/crud/user.py @@ -0,0 +1,287 @@ +from datetime import datetime +from typing import Any, Dict, Optional, Union, List +from sqlalchemy.orm import Session +from sqlalchemy import func, or_ + +from app.core.security import get_password_hash, verify_password +from app.models.user import User, Admin +from app.schemas.user import UserCreate, UserUpdate, AdminCreate, AdminUpdate + + +# 用户CRUD操作 +class CRUDUser: + # 根据ID获取用户 + def get(self, db: Session, user_id: int) -> Optional[User]: + return db.query(User).filter(User.id == user_id).first() + + # 根据用户名获取用户 + def get_by_username(self, db: Session, username: str) -> Optional[User]: + return db.query(User).filter(User.username == username).first() + + # 根据邮箱获取用户 + def get_by_email(self, db: Session, email: str) -> Optional[User]: + return db.query(User).filter(User.email == email).first() + + # 根据手机号获取用户 + def get_by_phone(self, db: Session, phone: str) -> Optional[User]: + return db.query(User).filter(User.phone == phone).first() + + # 根据微信OpenID获取用户 + def get_by_wechat_openid(self, db: Session, openid: str) -> Optional[User]: + return db.query(User).filter(User.wechat_openid == openid).first() + + # 创建用户 + def create(self, db: Session, obj_in: UserCreate) -> User: + db_obj = User( + username=obj_in.username, + password_hash=get_password_hash(obj_in.password), + user_type=obj_in.user_type, + real_name=obj_in.real_name or obj_in.username, + nickname=obj_in.nickname or obj_in.username, + email=obj_in.email, + phone=obj_in.phone, + created_at=datetime.now(), + updated_at=datetime.now() + ) + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 更新用户 + def update( + self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]] + ) -> User: + if isinstance(obj_in, dict): + update_data = obj_in + else: + update_data = obj_in.dict(exclude_unset=True) + + # 更新时间 + update_data["updated_at"] = datetime.now() + + for field in update_data: + if field in update_data: + setattr(db_obj, field, update_data[field]) + + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 更新用户密码 + def update_password(self, db: Session, db_obj: User, new_password: str) -> User: + db_obj.password_hash = get_password_hash(new_password) + db_obj.updated_at = datetime.now() + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 更新用户最后登录时间 + def update_last_login(self, db: Session, db_obj: User) -> User: + db_obj.last_login = datetime.now() + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 验证用户密码 + def authenticate(self, db: Session, username: str, password: str) -> Optional[User]: + user = self.get_by_username(db, username) + if not user: + user = self.get_by_email(db, username) + if not user: + user = self.get_by_phone(db, username) + if not user: + return None + if not verify_password(password, user.password_hash): + return None + return user + + # 检查用户是否活跃 + def is_active(self, user: User) -> bool: + return user.status == "active" + + # 获取用户列表(带分页) + def get_multi( + self, db: Session, *, skip: int = 0, limit: int = 100, + user_type: Optional[str] = None, status: Optional[str] = None, + keyword: Optional[str] = None + ) -> List[User]: + query = db.query(User) + + # 应用过滤条件 + if user_type: + query = query.filter(User.user_type == user_type) + if status: + query = query.filter(User.status == status) + if keyword: + query = query.filter( + or_( + User.username.like(f"%{keyword}%"), + User.real_name.like(f"%{keyword}%"), + User.nickname.like(f"%{keyword}%"), + User.email.like(f"%{keyword}%"), + User.phone.like(f"%{keyword}%") + ) + ) + + return query.offset(skip).limit(limit).all() + + # 获取用户总数 + def count( + self, db: Session, *, user_type: Optional[str] = None, + status: Optional[str] = None, keyword: Optional[str] = None + ) -> int: + query = db.query(func.count(User.id)) + + # 应用过滤条件 + if user_type: + query = query.filter(User.user_type == user_type) + if status: + query = query.filter(User.status == status) + if keyword: + query = query.filter( + or_( + User.username.like(f"%{keyword}%"), + User.real_name.like(f"%{keyword}%"), + User.nickname.like(f"%{keyword}%"), + User.email.like(f"%{keyword}%"), + User.phone.like(f"%{keyword}%") + ) + ) + + return query.scalar() + + # 批量更新用户状态 + def batch_update_status( + self, db: Session, user_ids: List[int], status: str + ) -> int: + result = db.query(User).filter(User.id.in_(user_ids)).update( + {"status": status, "updated_at": datetime.now()}, + synchronize_session=False + ) + db.commit() + return result + + # 删除用户 + def remove(self, db: Session, *, user_id: int) -> Optional[User]: + user = db.query(User).filter(User.id == user_id).first() + if user: + db.delete(user) + db.commit() + return user + + # 获取用户统计信息 + def get_statistics(self, db: Session) -> Dict[str, Any]: + total_users = db.query(func.count(User.id)).scalar() + farmers = db.query(func.count(User.id)).filter(User.user_type == "farmer").scalar() + merchants = db.query(func.count(User.id)).filter(User.user_type == "merchant").scalar() + admins = db.query(func.count(User.id)).filter( + or_(User.user_type == "admin", User.user_type == "super_admin") + ).scalar() + active_users = db.query(func.count(User.id)).filter(User.status == "active").scalar() + inactive_users = db.query(func.count(User.id)).filter(User.status == "inactive").scalar() + + return { + "total_users": total_users, + "farmers": farmers, + "merchants": merchants, + "admins": admins, + "active_users": active_users, + "inactive_users": inactive_users, + "date": datetime.now() + } + + +# 管理员CRUD操作 +class CRUDAdmin: + # 根据ID获取管理员 + def get(self, db: Session, admin_id: int) -> Optional[Admin]: + return db.query(Admin).filter(Admin.id == admin_id).first() + + # 根据用户名获取管理员 + def get_by_username(self, db: Session, username: str) -> Optional[Admin]: + return db.query(Admin).filter(Admin.username == username).first() + + # 创建管理员 + def create(self, db: Session, obj_in: AdminCreate) -> Admin: + db_obj = Admin( + username=obj_in.username, + password=get_password_hash(obj_in.password), + email=obj_in.email, + nickname=obj_in.nickname or obj_in.username, + avatar=obj_in.avatar, + role=obj_in.role, + created_at=datetime.now(), + updated_at=datetime.now() + ) + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 更新管理员 + def update( + self, db: Session, *, db_obj: Admin, obj_in: Union[AdminUpdate, Dict[str, Any]] + ) -> Admin: + if isinstance(obj_in, dict): + update_data = obj_in + else: + update_data = obj_in.dict(exclude_unset=True) + + # 更新时间 + update_data["updated_at"] = datetime.now() + + for field in update_data: + if field in update_data: + setattr(db_obj, field, update_data[field]) + + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 更新管理员密码 + def update_password(self, db: Session, db_obj: Admin, new_password: str) -> Admin: + db_obj.password = get_password_hash(new_password) + db_obj.updated_at = datetime.now() + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 更新管理员最后登录时间 + def update_last_login(self, db: Session, db_obj: Admin) -> Admin: + db_obj.last_login = datetime.now() + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + # 验证管理员密码 + def authenticate(self, db: Session, username: str, password: str) -> Optional[Admin]: + admin = self.get_by_username(db, username) + if not admin: + return None + if not verify_password(password, admin.password): + return None + return admin + + # 检查管理员是否活跃 + def is_active(self, admin: Admin) -> bool: + return admin.status == "active" + + # 删除管理员 + def remove(self, db: Session, *, admin_id: int) -> Optional[Admin]: + admin = db.query(Admin).filter(Admin.id == admin_id).first() + if admin: + db.delete(admin) + db.commit() + return admin + + +# 实例化CRUD对象 +user = CRUDUser() +admin = CRUDAdmin() \ No newline at end of file diff --git a/fastapi-backend/app/db/session.py b/fastapi-backend/app/db/session.py new file mode 100644 index 0000000..c11086b --- /dev/null +++ b/fastapi-backend/app/db/session.py @@ -0,0 +1,29 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +from app.core.config import settings + +# 创建数据库引擎 +engine = create_engine( + settings.SQLALCHEMY_DATABASE_URI, + pool_pre_ping=True, + pool_recycle=3600, + pool_size=20, + max_overflow=0, + echo=settings.DEBUG +) + +# 创建会话工厂 +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# 创建基础模型类 +Base = declarative_base() + +# 获取数据库会话 +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() \ No newline at end of file diff --git a/fastapi-backend/app/models/user.py b/fastapi-backend/app/models/user.py new file mode 100644 index 0000000..607add5 --- /dev/null +++ b/fastapi-backend/app/models/user.py @@ -0,0 +1,44 @@ +from datetime import datetime +from sqlalchemy import Boolean, Column, String, Integer, DateTime, Enum +from sqlalchemy.sql import func + +from app.db.session import Base + + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String(50), unique=True, index=True, nullable=False) + password_hash = Column(String(255), nullable=False) + user_type = Column(Enum('farmer', 'merchant', 'admin', 'super_admin'), default='farmer') + real_name = Column(String(50)) + nickname = Column(String(50)) + avatar_url = Column(String(255)) + email = Column(String(100), unique=True, index=True) + phone = Column(String(20), unique=True, index=True) + gender = Column(Enum('male', 'female', 'other'), default='other') + birthday = Column(DateTime) + status = Column(Enum('active', 'inactive'), default='active') + wechat_openid = Column(String(100), unique=True, index=True) + wechat_unionid = Column(String(100), unique=True, index=True) + level = Column(Integer, default=1) + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) + last_login = Column(DateTime) + + +class Admin(Base): + __tablename__ = "admins" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String(50), unique=True, index=True, nullable=False) + password = Column(String(255), nullable=False) + email = Column(String(100), unique=True, index=True) + nickname = Column(String(50)) + avatar = Column(String(255)) + role = Column(Enum('admin', 'super_admin'), default='admin') + status = Column(Enum('active', 'inactive'), default='active') + last_login = Column(DateTime) + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file diff --git a/fastapi-backend/app/schemas/user.py b/fastapi-backend/app/schemas/user.py new file mode 100644 index 0000000..9fabbd9 --- /dev/null +++ b/fastapi-backend/app/schemas/user.py @@ -0,0 +1,167 @@ +from datetime import datetime +from typing import Optional, List +from pydantic import BaseModel, EmailStr, Field, validator + + +# 用户基础模式 +class UserBase(BaseModel): + username: Optional[str] = None + email: Optional[EmailStr] = None + phone: Optional[str] = None + user_type: Optional[str] = None + real_name: Optional[str] = None + nickname: Optional[str] = None + avatar_url: Optional[str] = None + gender: Optional[str] = None + birthday: Optional[datetime] = None + + +# 创建用户请求模式 +class UserCreate(BaseModel): + username: str = Field(..., min_length=3, max_length=50) + password: str = Field(..., min_length=6) + email: Optional[EmailStr] = None + phone: Optional[str] = None + real_name: Optional[str] = None + nickname: Optional[str] = None + user_type: str = "farmer" + + +# 更新用户请求模式 +class UserUpdate(BaseModel): + nickname: Optional[str] = None + avatar_url: Optional[str] = None + gender: Optional[str] = None + birthday: Optional[datetime] = None + email: Optional[EmailStr] = None + phone: Optional[str] = None + + +# 用户登录请求模式 +class UserLogin(BaseModel): + username: str + password: str + + +# 密码更改请求模式 +class PasswordChange(BaseModel): + current_password: str + new_password: str = Field(..., min_length=6) + + +# 用户响应模式 +class UserResponse(UserBase): + id: int + status: str + created_at: datetime + updated_at: datetime + last_login: Optional[datetime] = None + + class Config: + orm_mode = True + + +# 带令牌的用户响应模式 +class UserWithToken(BaseModel): + user: UserResponse + token: str + refresh_token: Optional[str] = None + + +# 令牌响应模式 +class Token(BaseModel): + access_token: str + refresh_token: str + token_type: str = "bearer" + + +# 令牌数据模式 +class TokenPayload(BaseModel): + sub: Optional[int] = None + exp: Optional[int] = None + type: Optional[str] = None + + +# 管理员基础模式 +class AdminBase(BaseModel): + username: str + email: Optional[EmailStr] = None + nickname: Optional[str] = None + avatar: Optional[str] = None + role: str = "admin" + + +# 创建管理员请求模式 +class AdminCreate(AdminBase): + password: str = Field(..., min_length=6) + + +# 更新管理员请求模式 +class AdminUpdate(BaseModel): + nickname: Optional[str] = None + avatar: Optional[str] = None + email: Optional[EmailStr] = None + role: Optional[str] = None + status: Optional[str] = None + + +# 管理员响应模式 +class AdminResponse(AdminBase): + id: int + status: str + created_at: datetime + updated_at: datetime + last_login: Optional[datetime] = None + + class Config: + orm_mode = True + + +# 带令牌的管理员响应模式 +class AdminWithToken(BaseModel): + admin: AdminResponse + token: str + refresh_token: Optional[str] = None + + +# 微信登录请求模式 +class WechatLogin(BaseModel): + code: str + user_info: Optional[dict] = None + + +# 分页响应模式 +class PaginationResponse(BaseModel): + page: int + page_size: int + total: int + total_pages: int + + +# 用户列表响应模式 +class UserListResponse(BaseModel): + users: List[UserResponse] + pagination: PaginationResponse + + +# 用户统计响应模式 +class UserStatistics(BaseModel): + total_users: int + farmers: int + merchants: int + admins: int + active_users: int + inactive_users: int + date: Optional[datetime] = None + + +# 批量更新用户状态请求模式 +class BatchUserStatusUpdate(BaseModel): + user_ids: List[int] + status: str + + @validator("status") + def validate_status(cls, v): + if v not in ["active", "inactive"]: + raise ValueError("状态必须是 'active' 或 'inactive'") + return v \ No newline at end of file diff --git a/fastapi-backend/app/utils/errors.py b/fastapi-backend/app/utils/errors.py new file mode 100644 index 0000000..599d063 --- /dev/null +++ b/fastapi-backend/app/utils/errors.py @@ -0,0 +1,59 @@ +from fastapi import HTTPException, status + + +class AppError(HTTPException): + """ + 应用程序自定义错误类 + """ + def __init__( + self, + detail: str, + status_code: int = status.HTTP_400_BAD_REQUEST, + headers: dict = None + ): + super().__init__(status_code=status_code, detail=detail, headers=headers) + + +# 常用错误 +class NotFoundError(AppError): + """ + 资源未找到错误 + """ + def __init__(self, detail: str = "资源未找到"): + super().__init__(detail=detail, status_code=status.HTTP_404_NOT_FOUND) + + +class UnauthorizedError(AppError): + """ + 未授权错误 + """ + def __init__(self, detail: str = "未授权"): + super().__init__( + detail=detail, + status_code=status.HTTP_401_UNAUTHORIZED, + headers={"WWW-Authenticate": "Bearer"} + ) + + +class ForbiddenError(AppError): + """ + 禁止访问错误 + """ + def __init__(self, detail: str = "禁止访问"): + super().__init__(detail=detail, status_code=status.HTTP_403_FORBIDDEN) + + +class BadRequestError(AppError): + """ + 请求参数错误 + """ + def __init__(self, detail: str = "请求参数错误"): + super().__init__(detail=detail, status_code=status.HTTP_400_BAD_REQUEST) + + +class ConflictError(AppError): + """ + 资源冲突错误 + """ + def __init__(self, detail: str = "资源冲突"): + super().__init__(detail=detail, status_code=status.HTTP_409_CONFLICT) \ No newline at end of file diff --git a/fastapi-backend/app/utils/response.py b/fastapi-backend/app/utils/response.py new file mode 100644 index 0000000..9d33794 --- /dev/null +++ b/fastapi-backend/app/utils/response.py @@ -0,0 +1,43 @@ +from typing import Any, Dict, Optional + + +def success_response( + data: Any = None, + message: Optional[str] = None, + code: int = 200 +) -> Dict[str, Any]: + """ + 标准成功响应格式 + """ + response = { + "success": True, + "code": code + } + + if data is not None: + response["data"] = data + + if message: + response["message"] = message + + return response + + +def error_response( + message: str, + code: int = 400, + data: Any = None +) -> Dict[str, Any]: + """ + 标准错误响应格式 + """ + response = { + "success": False, + "code": code, + "message": message + } + + if data is not None: + response["data"] = data + + return response \ No newline at end of file diff --git a/fastapi-backend/main.py b/fastapi-backend/main.py new file mode 100644 index 0000000..cbf0984 --- /dev/null +++ b/fastapi-backend/main.py @@ -0,0 +1,15 @@ +import uvicorn +from app.core.config import settings +from app.api.api import app as application + +# 导出应用实例 +app = application + +if __name__ == "__main__": + uvicorn.run( + "app.api.api:app", + host=settings.HOST, + port=settings.PORT, + reload=settings.DEBUG, + log_level="info" + ) \ No newline at end of file diff --git a/fastapi-backend/migrations/env.py b/fastapi-backend/migrations/env.py new file mode 100644 index 0000000..064d073 --- /dev/null +++ b/fastapi-backend/migrations/env.py @@ -0,0 +1,74 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# 导入模型 +from app.models.user import Base + +# 导入配置 +from app.core.config import settings + +# 这是Alembic Config对象,它提供对.ini文件中值的访问 +config = context.config + +# 解释配置文件并设置日志记录器 +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# 添加你的模型元数据对象 +target_metadata = Base.metadata + +# 其他值来自config,可以通过以下方式定义: +# my_important_option = config.get_main_option("my_important_option") +# ... 等等。 + + +def run_migrations_offline() -> None: + """在'offline'模式下运行迁移。 + + 这配置了上下文,只需要一个URL,并且不要求引擎可用。 + 跳过引擎创建,甚至不需要DBAPI可用。 + + 调用context.execute()来执行迁移。 + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """在'online'模式下运行迁移。 + + 在这种情况下,我们创建了一个Engine并将其与迁移上下文关联。 + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() \ No newline at end of file diff --git a/fastapi-backend/requirements.txt b/fastapi-backend/requirements.txt new file mode 100644 index 0000000..86c38d8 --- /dev/null +++ b/fastapi-backend/requirements.txt @@ -0,0 +1,16 @@ +fastapi==0.110.0 +uvicorn==0.27.1 +pydantic==2.6.1 +pydantic-settings==2.1.0 +sqlalchemy==2.0.27 +pymysql==1.1.0 +cryptography==42.0.2 +python-jose==3.3.0 +passlib==1.7.4 +python-multipart==0.0.9 +email-validator==2.1.0 +python-dotenv==1.0.1 +alembic==1.13.1 +pytest==7.4.3 +httpx==0.26.0 +bcrypt==4.1.2 \ No newline at end of file